SetaPDF Demos PHP libraries to handle, modify or create PDF files

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