Add signature-policy-identifier attribute
Special PAdES profiles requires to add signature policy identifiers as a signed attribute to the CMS container.
This PHP demo shows you how to implement exactly that: It allows you to specify a signature policy id, its sha256 hash value and its URL in an individual signature module that extends the default PAdES signature module.
For demonstration purpose we use a signature policy from the ICP-Brasil (PKI Infrastructure from Brasil).
PHP
<?php use setasign\SetaPDF2\Core\Document; use setasign\SetaPDF2\Core\Writer\HttpWriter; use setasign\SetaPDF2\Demos\Signer\Module\Signature\PadesWithSignaturePolicyModule; use setasign\SetaPDF2\Signer\Signer; // load and register the autoload function require_once __DIR__ . '/../../../../../bootstrap.php'; // load the module class require_once __DIR__ . '/../../../../../classes/Signer/Module/Signature/PadesWithSignaturePolicyModule.php'; $writer = new HttpWriter('signed.pdf'); $document = Document::loadByFilename( $assetsDirectory . '/pdfs/camtown/Laboratory-Report.pdf', $writer ); // create a signer instance $signer = new Signer($document); // add a signature field $field = $signer->addSignatureField(); // and define that you want to use this field $signer->setSignatureFieldName($field->getQualifiedName()); $certificatePath = $assetsDirectory . '/certificates/setapdf-no-pw.pem'; // now create a signature module $module = new PadesWithSignaturePolicyModule(); // pass the path to the certificate $module->setCertificate('file://' . $certificatePath); // or its content //$module->setCertificate(file_get_contents($certificatePath)); // or a certificate instance //$certificate = \setasign\SetaPDF2\Signer\X509\Certificate::fromFileOrString($certificatePath); //$module->setCertificate($certificate); // set the path to the private key (in this demo the key is also saved in the certificate file) $module->setPrivateKey('file://' . $certificatePath, ''); // All available policies for ICP Brazil can be found e.g. here: // https://www.gov.br/iti/pt-br/assuntos/repositorio/artefatos-de-assinatura-digital // The hash value is from the signature policy DER structure (signPolicyHash field) and it is not the hash of the file! $module->setSignaturePolicy( '2.16.76.1.7.1.11.1.3', '23E4BE4B9B362172E4EBB0E72B86A133ECE5AAD843D8651C6E38A0BA3F08FC60', 'http://politicas.icpbrasil.gov.br/PA_PAdES_AD_RB_v1_3.der' ); // sign the document with the module $signer->sign($module);
PHP
<?php namespace setasign\SetaPDF2\Demos\Signer\Module\Signature; use setasign\SetaPDF2\Core\Type\PdfDictionary; use setasign\SetaPDF2\Core\Type\PdfName; use setasign\SetaPDF2\Signer\Asn1\Element as Asn1Element; use setasign\SetaPDF2\Signer\Asn1\Oid; use setasign\SetaPDF2\Signer\Digest; use setasign\SetaPDF2\Signer\Signature\Module\Pades; class PadesWithSignaturePolicyModule extends Pades { protected $_policyOid; protected $_policySha256Hash; protected $_policyUrl; public function setSignaturePolicy(string $oid, string $sha256Hash, string $url): void { $this->_policyOid = $oid; $this->_policySha256Hash = $sha256Hash; $this->_policyUrl = $url; } /** * @return array|Asn1Element[]|null * @throws \setasign\SetaPDF2\Signer\Exception */ protected function _getSignedAttributes() { $signedAttributes = parent::_getSignedAttributes(); if (!isset($signedAttributes['1.2.840.113549.1.9.16.2.15']) && $this->_policyOid !== null) { $signedAttributes['1.2.840.113549.1.9.16.2.15'] = $this->_getSignaturePolicyIdentifierAttribute(); } return $signedAttributes; } /** * @return Asn1Element */ protected function _getSignaturePolicyIdentifierAttribute(): Asn1Element { /** * signature-policy-identifier attribute: * https://www.rfc-editor.org/rfc/rfc5126.html#section-5.8.1 */ $sigPolicyQualifiers = new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::OBJECT_IDENTIFIER, Oid::encode('1.2.840.113549.1.9.16.5.1') // sigPolicyQualifier-spuri ), new Asn1Element( Asn1Element::IA5_STRING, $this->_policyUrl ) ] ) ] ); $signaturePolicyId = new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::OBJECT_IDENTIFIER, Oid::encode($this->_policyOid) ), new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::OBJECT_IDENTIFIER, Oid::encode(Digest::getOid(Digest::SHA_256)) ), new Asn1Element(Asn1Element::NULL) ] ), new Asn1Element( Asn1Element::OCTET_STRING, hex2bin($this->_policySha256Hash) ), ] ), $sigPolicyQualifiers ] ); return new Asn1Element( Asn1Element::SEQUENCE | Asn1Element::IS_CONSTRUCTED, '', [ new Asn1Element( Asn1Element::OBJECT_IDENTIFIER, Oid::encode('1.2.840.113549.1.9.16.2.15') // sigPolicyId ), new Asn1Element( Asn1Element::SET | Asn1Element::IS_CONSTRUCTED, '', [ $signaturePolicyId ] ) ] ); } // /** // * You may overwrite this method to e.g. update the Filter and SubFilter according to ICP Brasil. // * // * @param PdfDictionary $dictionary // * @return void // * @throws \setasign\SetaPDF2\Signer\Exception // */ // public function updateSignatureDictionary(PdfDictionary $dictionary) // { // parent::updateSignatureDictionary($dictionary); // // $dictionary['SubFilter'] = new PdfName('PBAD.PAdES', true); // $dictionary['Filter'] = new PdfName('PBAD_PAdES', true); // } }
