External Implementation
Table of Contents
Introduction
The CMS module creates the whole signature structure in PHP. It also allows you to access the data that should be signed through the getDataToSign()
method.
The signature value can be created in any foreign process then and passed back to the setSignatureValue()
method.
This allows you to delegate the signature creation to any external program or service without writing a complete new module class. Following some examples of possible implementations which can be used with both the CMS or PAdES module.
Notice that currently the CMS and PAdES modules only support the RSA schema RSASSA-PKCS1-v1_5 in the signature value.
OpenSSL CLI commands
OpenSSL offer various command line tools, which can be used to create the signature value:
dgst
The dgst command generate the digital signature using message digests:
pkeyutl
Following demo uses the pkeyutil command to generate the digital signature:
rsautl
Following demo uses the rsautl command to generate the digital signature. It requires some more than only hashing but it also requires us to encode the hash in an ASN.1 structure:
Engines
All of the above commands can be used with an engine which e.g. allows you to access a smart card or usb token.
CAPI
You can use for example the CAPI engine on a windows environment with the pkeyutl this way:
// the subject $privateKey = 'SetaPDF-Demo'; // the password $privateKeyPass = ''; // build the command $cmd = $opensslPath . " pkeyutl -sign " . '-keyform ENGINE -engine capi -inkey ' . escapeshellarg($privateKey) . ' ' . "-pkeyopt digest:" . $module->getDigest() . ' ' // this will allow us to sign the hash only . '-passin pass:' . escapeshellarg($privateKeyPass) . ' ' . '-in ' . escapeshellarg($tmpFileIn) . ' ' . '-out ' . escapeshellarg($tmpFileOut) . ' 2>&1';
PKCS11
A more reliable solution to access PKCS11 compatible devices is the PKCS11 module.
If you have an USB token with PKCS11 support installed on your operating system and the engine is configured properly in OpenSSL you can access it with the pkeyutl this way:
$pkcs11Uri = 'pkcs11:model=ePass2003;manufacturer=EnterSafe%00;serial=XXXXXXX;token=XXXX;id=XXXX;object=XXX;type=private;pin-value=11111111'; // build the command $cmd = $opensslPath . " pkeyutl -sign " . '-keyform ENGINE -engine pkcs11 -inkey ' . escapeshellarg($pkcs11Uri) . ' ' . "-pkeyopt digest:" . $module->getDigest() . ' ' // this will allow us to sign the hash only . '-in ' . escapeshellarg($tmpFileIn) . ' ' . '-out ' . escapeshellarg($tmpFileOut) . ' 2>&1';
The $pkcs11Uri format is defined in RFC 7512.
To list all available objects on your token, you can e.g. use the p11tool (replace the so file with the one of your token):
p11tool --provider=/usr/lib/libcastle.so.1.0.0 --list-all --login
To export a certificate from your USB token (which is required as the certificate parameter) you also can use the p11tool:
p11tool –provider=/usr/lib/libcastle.so.1.0.0 –login –export-chain "PKCS-11 URI OF THE CERTIFICATE"
OpenSC pkcs11-tool
A more reliable solution to access PKCS11 compatible devices is the PKCS11 module.
Another way to access an attached USB token or smart card which offers a PKCS11 interface is by using the command line tool pkcs11-tool from the OpenSC project:
<?php // require SetaPDF require_once('library/SetaPDF/Autoload.php') $pkcs11ToolPathBin = '/usr/local/bin/pkcs11-tool'; $pkcs11Path = '/usr/local/lib/libetpkcs11.so'; // path to your PKCS11 module $slot = 0; // List objects: pkcs11-tool --module {$pkcs11Path} --slot {$slot} -O $id = '<the-id-of-your-key>'; $pin = '<your-pin>'; $writer = new \SetaPDF_Core_Writer_File('signed.pdf'); $document = \SetaPDF_Core_Document::loadByFilename('Laboratory-Report.pdf', $writer); $signer = new \SetaPDF_Signer($document); $signer->setSignatureContentLength(18000); $module = new \SetaPDF_Signer_Signature_Module_Pades(); $module->setCertificate('setapdf.crt'); $tempFile = \SetaPDF_Core_Writer_TempFile::createTempPath(); // create a temporary version which represents the data which should get signed $tmpDocument = $signer->preSign(new \SetaPDF_Core_Writer_File($tempFile), $module); // get the hash data from the module $hashData = $module->getDataToSign($tmpDocument->getHashFile()); // encode the hash in an ASN.1 structure $digestInfo = new \SetaPDF_Signer_Asn1_Element( \SetaPDF_Signer_Asn1_Element::SEQUENCE | \SetaPDF_Signer_Asn1_Element::IS_CONSTRUCTED, '', [ new \SetaPDF_Signer_Asn1_Element( \SetaPDF_Signer_Asn1_Element::SEQUENCE | \SetaPDF_Signer_Asn1_Element::IS_CONSTRUCTED, '', [ new \SetaPDF_Signer_Asn1_Element( \SetaPDF_Signer_Asn1_Element::OBJECT_IDENTIFIER, \SetaPDF_Signer_Asn1_Oid::encode( \SetaPDF_Signer_Digest::getOid($module->getDigest()) ) ), new \SetaPDF_Signer_Asn1_Element(\SetaPDF_Signer_Asn1_Element::NULL) ] ), new \SetaPDF_Signer_Asn1_Element( \SetaPDF_Signer_Asn1_Element::OCTET_STRING, hash($module->getDigest(), $hashData, true) ) ] ); // and write it to a temporary file $tmpFileIn = \SetaPDF_Core_Writer_TempFile::createTempFile($digestInfo); // prepare a temporary file for the final signature $tmpFileOut = \SetaPDF_Core_Writer_TempFile::createTempPath(); $cmd = $pkcs11ToolPathBin . ' ' . '--module ' . escapeshellarg($pkcs11Path) . ' ' . '--pin ' . escapeshellarg($pin) . ' ' . '--slot ' . escapeshellarg($slot) . ' ' . '--id ' . escapeshellarg($id) . ' ' . '-m RSA-PKCS ' . '--sign ' . '--input-file ' . escapeshellarg($tmpFileIn) . ' ' . '--output-file ' . escapeshellarg($tmpFileOut) . ' ' . '2>&1'; exec($cmd, $out, $retValue); if ($retValue !== 0) { throw new SetaPDF_Signer_Exception( sprintf('An error occurs while calling pkcs11-tool through CLI (exit code %s).', $retValue) ); } // get the signature data $signatureValue = file_get_contents($tmpFileOut); // pass it to the module $module->setSignatureValue($signatureValue); // get the final cms container $cms = $module->getCms(); // and pass it to the main signer instance $signer->saveSignature($tmpDocument, $cms); unlink($tmpFileIn); unlink($tmpFileOut);