Skip to content

Commit cd5dbb0

Browse files
committed
Added example with visible signature appearance
1 parent f37afe3 commit cd5dbb0

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
<?php
2+
/* This demo shows you how to create a visible signature with an on-demand certificate and Step-Up authentication
3+
* through the Swisscom All-in Signing Service including a timestamp signature.
4+
*
5+
* For the signature appearance we use a static PDF document for demonstration purpose.
6+
*
7+
* It uses the signature standard "PAdES-baseline" and the revocation information of both signature and timestamp
8+
* are added to the Document Security Store (DSS) afterwards to have LTV enabled (PAdES Signature Level: B-LT).
9+
*/
10+
11+
use GuzzleHttp\Client as GuzzleClient;
12+
use GuzzleHttp\Handler\CurlHandler;
13+
use Http\Factory\Guzzle\RequestFactory;
14+
use Http\Factory\Guzzle\StreamFactory;
15+
use Mjelamanov\GuzzlePsr18\Client as Psr18Wrapper;
16+
use setasign\SetaPDF\Signer\Module\SwisscomAIS\AsyncModule;
17+
use setasign\SetaPDF\Signer\Module\SwisscomAIS\ProcessData;
18+
use setasign\SetaPDF\Signer\Module\SwisscomAIS\SignException;
19+
20+
date_default_timezone_set('Europe/Berlin');
21+
error_reporting(E_ALL | E_STRICT);
22+
ini_set('display_errors', 1);
23+
24+
// require the autoload class from Composer
25+
require_once('../vendor/autoload.php');
26+
27+
session_start();
28+
if (isset($_GET['restart']) && $_GET['restart'] === '1') {
29+
unset($_SESSION[__FILE__]);
30+
}
31+
32+
if (!file_exists(__DIR__ . '/settings/settings-on-demand.php')) {
33+
throw new RuntimeException('Missing settings/settings-on-demand.php!');
34+
}
35+
36+
$settings = require(__DIR__ . '/settings/settings-on-demand.php');
37+
38+
$guzzleOptions = [
39+
'handler' => new CurlHandler(),
40+
'http_errors' => false,
41+
'verify' => __DIR__ . '/ais-ca-ssl.crt',
42+
'cert' => $settings['cert'],
43+
'ssl_key' => $settings['privateKey']
44+
];
45+
46+
$httpClient = new GuzzleClient($guzzleOptions);
47+
// only required if you are using guzzle < 7
48+
$httpClient = new Psr18Wrapper($httpClient);
49+
50+
// let's get the document
51+
$document = SetaPDF_Core_Document::loadByFilename('files/tektown/Laboratory-Report.pdf');
52+
53+
// now let's create a signer instance
54+
$signer = new SetaPDF_Signer($document);
55+
// create a Swisscom AIS module instance
56+
$swisscomModule = new AsyncModule($settings['customerId'], $httpClient, new RequestFactory(), new StreamFactory());
57+
if (!array_key_exists(__FILE__, $_SESSION)) {
58+
$signer->setAllowSignatureContentLengthChange(false);
59+
$signer->setSignatureContentLength(34000);
60+
61+
// set some signature properties
62+
$signer->setLocation($_SERVER['SERVER_NAME']);
63+
$signer->setContactInfo('+01 2345 67890123');
64+
$signer->setReason('testing...');
65+
66+
// create a visible signature field
67+
$fieldName = $signer->addSignatureField(
68+
SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
69+
1,
70+
\SetaPDF_Signer_SignatureField::POSITION_LEFT_TOP,
71+
['x' => 260, 'y' => -100],
72+
180,
73+
70
74+
)->getQualifiedName();
75+
$signer->setSignatureFieldName($fieldName);
76+
77+
// additionally, the signature should include a qualified timestamp
78+
$swisscomModule->setAddTimestamp(true);
79+
// set on-demand options
80+
$swisscomModule->setOnDemandCertificate($settings['distinguishedName']);
81+
if (isset($settings['stepUpAuthorisation'])) {
82+
$swisscomModule->setStepUpAuthorisation(
83+
$settings['stepUpAuthorisation']['msisdn'],
84+
'Please confirm to sign Laboratory-Report.pdf',
85+
$settings['stepUpAuthorisation']['language'],
86+
$settings['stepUpAuthorisation']['serialNumber'] ?? null
87+
);
88+
}
89+
90+
// you may use an own temporary file handler
91+
$tempPath = SetaPDF_Core_Writer_TempFile::createTempPath();
92+
93+
// load the PDF document for the appearance and ...
94+
$graphicDocument = \SetaPDF_Core_Document::loadByFilename('files/Handwritten-Signature.pdf');
95+
// convert the first page to a XObject
96+
$xObject = $graphicDocument
97+
->getCatalog()
98+
->getPages()
99+
->getPage(1)
100+
->toXObject($document, \SetaPDF_Core_PageBoundaries::ART_BOX);
101+
102+
// which can be used to initiate a XObject appearance instance
103+
$appearance = new \SetaPDF_Signer_Signature_Appearance_XObject($xObject);
104+
105+
// pass the appearance module to the signer instance
106+
$signer->setAppearance($appearance);
107+
108+
// prepare the PDF
109+
$tmpDocument = $signer->preSign(
110+
new SetaPDF_Core_Writer_File($tempPath),
111+
$swisscomModule
112+
);
113+
114+
$processData = $swisscomModule->initSignature($tmpDocument, $fieldName);
115+
// inject individual metadata into the process data
116+
$processData->setMetadata(['filename' => 'Swisscom.pdf']);
117+
118+
// For the purpose of this demo we just serialize the processData into the session.
119+
// You could use e.g. a database or a dedicated directory on your server.
120+
$_SESSION[__FILE__]['processData'] = $processData;
121+
122+
$response = $swisscomModule->getLastResponseData();
123+
if (isset($response['SignResponse']['OptionalOutputs']['sc.StepUpAuthorisationInfo']['sc.Result']['sc.ConsentURL'])) {
124+
// The content of the website pointed by the consent URL can change over time and therefore the page
125+
// must be displayed as it is. The recommended methods for showing the content hosted under the consent
126+
// URL are:
127+
// • To embed an iFrame in the application (see [IFR - https://github.com/SwisscomTrustServices/AIS/wiki/SAS-iFrame-Embedding-Guide] for guidelines)
128+
// • To send an SMS to the user with the consent URL, so the user can open it directly on his phone browser
129+
130+
$url = json_encode($response['SignResponse']['OptionalOutputs']['sc.StepUpAuthorisationInfo']['sc.Result']['sc.ConsentURL']);
131+
echo 'Started async signing process... <a href="#" onclick="openLink()">Please give your consent via mobile number ';
132+
echo $settings['stepUpAuthorisation']['msisdn'] . '.</a> (popups must be allowed)';
133+
echo '<br/><hr/>If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
134+
echo <<<HTML
135+
<script type="text/javascript">
136+
function openLink () {
137+
window.open({$url}, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
138+
window.setTimeout(function () {window.location = window.location.pathname;}, 5000);
139+
}
140+
</script>
141+
HTML;
142+
143+
return;
144+
}
145+
146+
echo 'Started async signing process (via mobile number ' . $settings['stepUpAuthorisation']['msisdn'] . '). Waiting for authorisation... ';
147+
echo 'The page should reload every 5 seconds.';
148+
echo '<br/><hr/>If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
149+
echo '<script type="text/javascript">window.setTimeout(function () {window.location = window.location.pathname;}, 5000);</script>';
150+
return;
151+
}
152+
153+
/** @var ProcessData $processData */
154+
$processData = $_SESSION[__FILE__]['processData'];
155+
$swisscomModule->setProcessData($processData);
156+
157+
try {
158+
$signResult = $swisscomModule->processPendingSignature();
159+
} catch (SignException $e) {
160+
$minorResult = $e->getResultMinor();
161+
if ($minorResult === 'http://ais.swisscom.ch/1.1/resultminor/subsystem/StepUp/timeout') {
162+
echo 'StepUp authentification timeout.';
163+
} elseif ($minorResult === 'http://ais.swisscom.ch/1.1/resultminor/subsystem/StepUp/cancel') {
164+
echo 'StepUp authentification was canceled';
165+
} else {
166+
echo 'An error occurred: ' . htmlspecialchars($e->getMessage()) . '<br/>';
167+
var_dump($e->getResultMajor(), $e->getResultMinor());
168+
}
169+
170+
echo '<hr>Restart signing process here: <a href="?">Restart</a>';
171+
// clean up temporary file
172+
unlink($processData->getTmpDocument()->getWriter()->getPath());
173+
unset($_SESSION[__FILE__]);
174+
return;
175+
} catch (Throwable $e) {
176+
echo 'Error on signing. If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
177+
var_dump($e);
178+
return;
179+
}
180+
181+
if ($signResult === false) {
182+
echo 'Still pending! ';
183+
echo 'Waiting for authorisation via mobile number ' . $settings['stepUpAuthorisation']['msisdn'] . '. ';
184+
echo 'The page should reload every 5 seconds.';
185+
echo '<br/><hr/>If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
186+
echo '<script type="text/javascript">window.setTimeout(function () {window.location = window.location.pathname;}, 5000);</script>';
187+
return;
188+
}
189+
190+
try {
191+
$tmpWriter = new SetaPDF_Core_Writer_TempFile();
192+
$document->setWriter($tmpWriter);
193+
194+
// save the signature to the temporary document
195+
$signer->saveSignature($processData->getTmpDocument(), $signResult);
196+
197+
// add DSS
198+
$document = SetaPDF_Core_Document::loadByFilename($tmpWriter->getPath());
199+
// use the filename stored in the process data metadata to create a writer instance
200+
$writer = new SetaPDF_Core_Writer_Http($processData->getMetadata()['filename']);
201+
$document->setWriter($writer);
202+
$swisscomModule->updateDss($document, $processData->getFieldName());
203+
$document->save()->finish();
204+
205+
// clean up temporary file
206+
unlink($processData->getTmpDocument()->getWriter()->getPath());
207+
unset($_SESSION[__FILE__]);
208+
} catch (Throwable $e) {
209+
echo 'Error on saving the signature. If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
210+
var_dump($e);
211+
return;
212+
}
213+
214+
echo '<br/><hr/>If you want to restart the signature process click here: <a href="?restart=1">Restart</a>';
80.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)