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
$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);
// }
}
