<?php
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\CurlHandler;
use Http\Factory\Guzzle\RequestFactory;
use Http\Factory\Guzzle\StreamFactory;
use setasign\SetaPDF\Signer\Module\SwisscomAIS\AsyncModule;
use setasign\SetaPDF\Signer\Module\SwisscomAIS\ProcessData;
use setasign\SetaPDF\Signer\Module\SwisscomAIS\SignException;
if (!isset($_GET['action'])) {
die();
}
require_once('vendor/autoload.php');
$fileToSign = '/path/to/Laboratory-Report.pdf';
$customerId = 'ais-90days-trial:OnDemand-Advanced4.1-EU';
$verify = 'ais-ca-ssl.crt';
$cert = 'your-certificate.pem';
$sslKey = 'your-private-key.pem';
// for demonstration purpose we use a session for state handling
// in a production environment you may use a more reasonable solution
session_start();
// a simple "controller":
switch ($_GET['action']) {
// Display the step-up authentication from
case 'init':
$html = <<<HTML
<h1>Step-Up Authentication</h1>
<p>To start the signature workflow, fill in the following form fields.<br/>
You have to approve the signature with an OTP send to your mobile or with your Mobile ID app.</p>
<form action="controller.php?action=start" method="post">
<label for="lastname">Lastname</label><br/>
<input type="text" id="lastname" name="lastname" value="Tester">
<br/><br/>
<label for="firstname">Firstname</label><br/>
<input type="text" id="firstname" name="firstname" value="Joe">
<br/><br/>
<label for="mobile">Mobile Number (incl. country code - without any extra character - e.g. 4912345678)</label><br/>
<input type="text" id="mobile" name="mobile">
<br/><br/>
<button class="btnContinue">Sign document</button>
</form>
HTML;
$hideRestart = true;
include __DIR__ . '/dialog.php';
break;
// prepare the PDF document, and start the signature process
case 'start':
// reset the current state
unset($_SESSION[__FILE__]);
$guzzleOptions = [
'handler' => new CurlHandler(),
'http_errors' => false,
'verify' => $verify,
'cert' => $cert,
'ssl_key' => $sslKey,
];
$httpClient = new GuzzleClient($guzzleOptions);
// let's get the document
$document = \SetaPDF_Core_Document::loadByFilename($fileToSign);
// now let's create a signer instance
$signer = new \SetaPDF_Signer($document);
// create a Swisscom AIS module instance
$swisscomModule = new AsyncModule($customerId, $httpClient, new RequestFactory(), new StreamFactory());
$signer->setAllowSignatureContentLengthChange(false);
$signer->setSignatureContentLength(34000);
// set some signature properties
$signer->setLocation('www.setasign.com');
$signer->setContactInfo($_POST['mobile'] ?? '');
$signer->setReason('Testing Swisscom AIS');
$fieldName = $signer->addSignatureField()->getQualifiedName();
$signer->setSignatureFieldName($fieldName);
// additionally, the signature should include a timestamp
$swisscomModule->setAddTimestamp();
$lastname = str_replace(',', '', $_POST['lastname'] ?? '');
$firstname = str_replace(',', '', $_POST['firstname'] ?? '');
// set on-demand options
$swisscomModule->setOnDemandCertificate(
'cn=TEST ' . $firstname . ' ' . $lastname . ',givenname=' . $firstname . ',surname=' . $lastname . ', c=de, emailaddress=demos@setasign.com'
);
try {
$swisscomModule->setStepUpAuthorisation(
$_POST['mobile'] ?? '',
'Please confirm to sign "' . basename($fileToSign) . '"',
'en'
);
} catch (\Throwable $e) {
$html = '<h1>Invalid mobile number</h1><p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>';
include __DIR__ . '/dialog.php';
break;
}
// you may use an own temporary file handler
$tempPath = \SetaPDF_Core_Writer_TempFile::createTempPath();
try {
// prepare the PDF
$tmpDocument = $signer->preSign(
new \SetaPDF_Core_Writer_File($tempPath),
$swisscomModule
);
$processData = $swisscomModule->initSignature($tmpDocument, $fieldName);
} catch (\Throwable $e) {
$html = '<h1>Error on presign</h1><p>' . htmlspecialchars($e->getMessage()) . '</p>' .
'<p>Error: '. htmlspecialchars($e->getMessage()) . '</p>';
include __DIR__ . '/dialog.php';
break;
}
// inject individual metadata into the process data
$processData->setMetadata(['filename' => 'Swisscom-with-step-up-authentication.pdf']);
// For the purpose of this demo we just serialize the processData into the session.
// You could use e.g. a database or a dedicated directory on your server.
$_SESSION[__FILE__]['processData'] = $processData;
$response = $swisscomModule->getLastResponseData();
// if your mobile number isn't registered for mobile id authentication, you'll fall back to sms authentication
if (isset($response['SignResponse']['OptionalOutputs']['sc.StepUpAuthorisationInfo']['sc.Result']['sc.ConsentURL'])) {
// The content of the website pointed by the consent URL can change over time and therefore the page
// must be displayed as it is. The recommended methods for showing the content hosted under the consent
// URL are:
// • To embed an iFrame in the application (see [IFR - https://github.com/SwisscomTrustServices/AIS/wiki/SAS-iFrame-Embedding-Guide] for guidelines)
// • To send an SMS to the user with the consent URL, so the user can open it directly on his phone browser
$url = json_encode($response['SignResponse']['OptionalOutputs']['sc.StepUpAuthorisationInfo']['sc.Result']['sc.ConsentURL']);
$html = '<h1>Signing process started...</h1>' .
'<p><a href="#" onclick="openLink()">Please give your consent via mobile number</a>. (popups must be allowed)</p>';
$html .= <<<HTML
<script type="text/javascript">
function openLink () {
window.open({$url}, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
window.setTimeout(function () {window.location = "controller.php?action=complete";}, 5000);
}
</script>
HTML;
include __DIR__ . '/dialog.php';
break;
}
$html = '<h1>Signing process started (via mobile id)</h1>' .
'<p>Waiting for authorisation via mobile number (page will reload automatically).</p>' .
'<script type="text/javascript">window.setTimeout(function () {window.location = "controller.php?action=complete";}, 5000);</script>';
include __DIR__ . '/dialog.php';
break;
// check if the signature was created and embed it into the PDF document
case 'complete':
if (!isset($_SESSION[__FILE__]['processData'])) {
$html = '<p>No process data found in session</p>';
include __DIR__ . '/dialog.php';
break;
}
/** @var ProcessData $processData */
$processData = $_SESSION[__FILE__]['processData'];
$guzzleOptions = [
'handler' => new CurlHandler(),
'http_errors' => false,
'verify' => $verify,
'cert' => $cert,
'ssl_key' => $sslKey,
];
$httpClient = new GuzzleClient($guzzleOptions);
// create a Swisscom AIS module instance
$swisscomModule = new AsyncModule($customerId, $httpClient, new RequestFactory(), new StreamFactory());
$swisscomModule->setProcessData($processData);
try {
$signResult = $swisscomModule->processPendingSignature();
} catch (SignException $e) {
$minorResult = $e->getResultMinor();
if ($minorResult === 'http://ais.swisscom.ch/1.1/resultminor/subsystem/StepUp/timeout') {
$html = '<h1>StepUp authentification timeout</h1>';
} elseif ($minorResult === 'http://ais.swisscom.ch/1.1/resultminor/subsystem/StepUp/cancel') {
$html = '<h1>StepUp authentification was canceled</h1>';
} else {
$html = '<h1>An error occurred</h1><p>' . htmlspecialchars($e->getMessage()) . '</p>' .
'<p>ResultMajor: ' . $e->getResultMajor() . '</p>' .
'<p>ResultMinor:' . $e->getResultMinor() . '</p>';
}
include __DIR__ . '/dialog.php';
// clean up temporary file
unlink($processData->getTmpDocument()->getWriter()->getPath());
unset($_SESSION[__FILE__]);
break;
} catch (Throwable $e) {
$html = '<h1>Error on signing</h1><p>Error: '. $e->getMessage() . '</p>';
include __DIR__ . '/dialog.php';
unlink($processData->getTmpDocument()->getWriter()->getPath());
unset($_SESSION[__FILE__]);
break;
}
if ($signResult === false) {
$html = '<h1>Still pending!</h1>' .
'<p>Waiting for authorisation via mobile number (page will reload automatically).</p>' .
'<script type="text/javascript">window.setTimeout(function () {window.location = "controller.php?action=complete";}, 5000);</script>';
include __DIR__ . '/dialog.php';
break;
}
// let's get the document
$document = \SetaPDF_Core_Document::loadByFilename($fileToSign);
// now let's create a signer instance
$signer = new \SetaPDF_Signer($document);
try {
$tmpWriter = new \SetaPDF_Core_Writer_TempFile();
$document->setWriter($tmpWriter);
// save the signature to the temporary document
$signer->saveSignature($processData->getTmpDocument(), $signResult);
// add DSS
$document = \SetaPDF_Core_Document::loadByFilename($tmpWriter->getPath());
// optional: validate the technical integrity of the signature
$result = \SetaPDF_Signer_ValidationRelatedInfo_IntegrityResult::create($document, $processData->getFieldName());
if (!$result->isValid()) {
throw new \Exception('Signature integrity is not valid!');
}
// use the filename stored in the process data metadata to create a writer instance
$writer = new \SetaPDF_Core_Writer_String();
$document->setWriter($writer);
$swisscomModule->updateDss($document, $processData->getFieldName());
$document->save()->finish();
// clean up temporary file
$_SESSION[__FILE__]['pdf'] = [
'name' => $processData->getMetadata()['filename'],
'data' => $writer->getBuffer()
];
$html = '<h1>The document was signed successfully</h1>' .
'<p><a href="controller.php?action=download" class="downloadBtn">Download</a></p>';
} catch (\Throwable $e) {
$html = '<h1>Error on saving the signature</h1><p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>';
} finally {
unlink($processData->getTmpDocument()->getWriter()->getPath());
unset($_SESSION[__FILE__]['processData']);
}
include __DIR__ . '/dialog.php';
break;
// a download action for the final signed document
case 'download':
if (!isset($_SESSION[__FILE__]['pdf'])) {
$html = '<h1>No pdf found in session</h1>';
include __DIR__ . '/dialog.php';
break;
}
$doc = $_SESSION[__FILE__]['pdf'];
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; ' . \SetaPDF_Core_Writer_Http::encodeFilenameForHttpHeader($doc['name']));
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . strlen($doc['data']));
echo $doc['data'];
flush();
break;
default:
$html = '<h1>Unknown action</h1>';
include __DIR__ . '/dialog.php';
break;
}