MyPDFSigner provides extensions for PHP, Ruby and Python to sign PDF documents using tokens stored in PKCS#11 and PKCS#12 certificate stores. The extensions support time stamping (RFC 3161) and visible signatures, including the incorporation of "watermark" images, PAdES LTV, PAdES B-B and B-T, encryption and signature verification.
The extensions are only available for Linux, except the Python extension which is also available for Windows (available for Python 2.7 and Python 3.6). Please contact KryptoKoder if you need extensions for a different OS.
Install the PHP|Ruby|Python extension (Fedora|Ubuntu):
$ sudo dnf install mypdfsigner-[php|ruby|python]-2.7.5-1.x86_64.rpm
$ sudo gdebi mypdfsigner-[php|ruby|python]_2.7.1-1_amd64.deb
Test a script from the command line:
$ [php|ruby|python] /usr/local/mypdfsigner/tests/test.[php|rb|py]
There are further examples in the /usr/local/mypdfsigner/tests/ directory. The test-json.[php|rb|py] example shows how to use json notation to build a configuration file on the fly, and the test-external.[php|rb|py] examples shows how to sign a PDF by having a signature created as an external step, possibly by a remote third party application.
In Windows copy the file mypdfsigner.pyd from "C:\Program Files\MyPDFSigner" to C:\Python27\Lib\site-packages or mypdfsigner.cp36-win_amd64.pyd to C:\Python36\Lib\site-packages. Then from a directory where you have write permissions (like C:\Users\Username\Downloads):
C:\> python "C:\Program Files\MyPDFSigner\tests\test.py"
The signed file example-signed-python.pdf can then be found in the current directory.
Before using any of the extensions it is necessary to start with the graphical application to create a configuration file for the key store and alias one wants to use. The application creates a .mypdfsigner file in your home directory. This file can be copied to /usr/local/mypdfsigner and renamed mypdfsigner.conf (this step can be skipped but then the configuration file needs to be specified when calling the "sign" function).
The extensions support PKCS#12 and PKCS#11 key stores. Use of PKCS#11 key stores
is achieved through the OpenSSL engine cryptographic module. To install:
-- dnf install engine_pkcs11 (Fedora)
-- apt-get install engine_pkcs11 (Ubuntu)
PKCS#12: For a PKCS#12 key store the configuration file created by the graphical application is ready to be used by the extensions.
PKCS#11: For a PKCS#11 key store some extra preparation and editing is required. By default, and unlike the PKCS#12 case, the PIN that protects the private key is not saved to the configuration file by the graphical application. However this can be changed by adding the entry savepin=on to the configuration file previously created (with the graphical application) and by signing a PDF document. This will prompt for the PIN which will then be encrypted and saved to the configuration file. Besides that a few other entries will be added to the file:
# pkcs11 related parameters savepin=on signerpem=/path/to/cert.pem capem=/path/to/chain.pem engine=/usr/lib/engines/engine_pkcs11.so p11url=[needs to be set manually]
The signerpem and capem entries point to files in the user home directory. They can be moved elsewhere and the paths updated. Note that for the capem file to be correct the Certificates Store directory needs to be correct. This is the directory where the certificates that belong to the chain of trust of the signing certificate are placed (these certificates are usually not inside the PKCS#11 security device but instead are part of the software that comes with the device).
The path of the engine module defaults to a predefined location if the module exists there (in Linux the predefined locations are /usr/lib/engines/engine_pkcs11.so or /usr/lib64/openssl/engines/pkcs11.so, in Mac OS X the predefined location is /usr/local/opt/engine_pkcs11/lib/engines/engine_pkcs11.so). Otherwise it is left unfilled and needs to be filled manually. For Windows a pkcs11.dll engine file is provided in the installation directory.
The p11url needs to be set manually. To that end run the p11tool command (adapt to your PKCS#11 module):
$ p11tool --provider /usr/local/lib/libpteidpkcs11.so --list-privkeys --login
The output is similar to the one below. The URL is the value that needs to be set in the p11url entry. That would be pkcs11:model=Portugal%20eID;manufacturer=Zetes;serial=0000012736981650;token=CARTAO%20DE%20CIDADAO;id=%46;object=CITIZEN%20SIGNATURE%20KEY;type=private in the example below. If you do not have the p11tool available (as may happen in Windows) you can build a different URL using the pkcs11-tool (see below).
Object 0: URL: pkcs11:model=Portugal%20eID;manufacturer=Zetes;serial=0000012736981650;token=CARTAO%20DE%20CIDADAO;id=%45;object=CITIZEN%20AUTHENTICATION%20KEY;type=private Type: Private key Label: CITIZEN AUTHENTICATION KEY Flags: CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE; ID: 45 Object 1: URL: pkcs11:model=Portugal%20eID;manufacturer=Zetes;serial=0000012736981650;token=CARTAO%20DE%20CIDADAO;id=%46;object=CITIZEN%20SIGNATURE%20KEY;type=private Type: Private key Label: CITIZEN SIGNATURE KEY Flags: CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE; ID: 46
On a Mac OS X you can use brew to install opensc, engine_pkcs11 and gnutls (provides p11tool).
Once the configuration is ready you can test it using the command line. Make sure that signing with the command line works before trying out the extensions.
If you get an error like the one below then that means that your engine_pkcs11 version does not yet support the more recent PKCS#11 URI schema (RFC 7512).
kryptokoder:~ support$ /Applications/MyPDFSigner.app/Contents/MacOS/mypdfsigner -i /Applications/MyPDFSigner.app/Contents/Home/tests/example.pdf -o /tmp/example-signed-cli.pdf -z .mypdfsigner format not recognized! supported formats: <id>, <slot>:<id>, id_<id>, slot_<slot>-id_<id>, label_<label>, slot_<slot>-label_<label> where <slot> is the slot number as normal integer, and <id> is the id number as hex string. and <label> is the textual key label string. PKCS11_get_private_key returned NULL -1#Error: ENGINE_load_private_key() returned NULL key...
If that is the case, or if you do not have the p11tool available, then you need to use the old format, that uses the slot and ID values (slot_<slot>-id_<id>). For the example above, with ID 46 and slot 1 the p11url entry would be:
You can use the pkcs11-tool, as explained in PKCS#11 Security Device with OpenSC, to get the list of slots. To get the ID you can also use the pkcs11-tool as shown below:
C:\Program Files\OpenSC Project\OpenSC\tools>pkcs11-tool --slot 1 -O Certificate Object, type = X.509 cert label: SIGNATURE CERTIFICATE ID: 46 Public Key Object; RSA 1024 bits label: SIGNATURE CERTIFICATE ID: 46 Usage: encrypt, verify Certificate Object, type = X.509 cert label: SIGNATURE SUB CA ID: 51 Public Key Object; RSA 2048 bits label: SIGNATURE SUB CA ID: 51 Usage: encrypt, verify
Time Stamping: MyPDFSigner supports the HTTP(S) POST TSA protocol. Configuration is straightforward and is done with the entries tsaurl, tsauser and tsapasswd in the configuration file. If using HTTPS, a extra entry, tsacert may be needed.
# time stamping parameters tsaurl=http://adobe-timestamp.geotrust.com/tsa # tsauser and tsapasswd if required by you TSA provider tsauser=tsausername tsapasswd=tsauserpassword # if using HTTPS and certificate trust not established tsacert=/path/to/CAtrust.pem
To establish a connection to a HTTPS Time Stamping server, the CA that issued the certificate of the server needs to be trusted. If the CA certificate is already present in the ca-bundle.crt file (usually in the /etc/ssl/certs/ directory) then the trust is already established and no further steps are needed.
If trust has not been established then one needs to import the certificate of the CA that issued the time stamping server certificate. The certificate can be appended to the ca-bundle.crt file, or can be placed into its own file, whose path then needs to be used as the tsacert entry in the configuration file. In either case, the certificate needs to be converted to the PEM format:
$ openssl x509 -in CAcert -text -out CAcert.pem
Note: the above assumes the certificate in the CAcert file was already in PEM. If it is in DER format, then you need to add '-inform DER' to the above command.
You can test that the certificate is correct and that you can establish a connection to the server by using the command curl:
$ curl -v --cacert CAcert.pem https://tsa.example.com
Visible Signatures: MyPDFSigner supports visible signatures and allows for some customization (image, size, position and page). A signature is made visible by setting TRUE the "visible" argument of the "sign" function (or by passing -v when using the command line).
The visible signature is placed by default on the first page of the document. To place it in a different page add the entry sigpage=page to the configuration file. The "page" value is a positive or negative integer; if negative it means the pages are counted from the end. For instance, to place the signature on the last page the entry can just be sigpage=-1.
Before explaining how signature customization is done one needs to know about PDF size units, also known as points. Point units are based on a "72 units per inch" scale. Hence letter size (8.5 by 11 inches) corresponds to 612 x 792 points, and A4 (210 by 297 mm) corresponds to 595 x 842 points. A visible signature position is specified by an array of four values corresponding to the "lx" (left x), "by" (bottom y), "rx" (right x) and "ty" (top y) coordinates of the sides of the signature rectangle. MyPDFSigner can accept both positive and negative coordinates, with negative coordinates being mesured from the right and top edges of the page (positive units are measured from the lower left corner of the page). By default MyPDFSigner uses the rectangle [-170 -80 -40 -40]. A different signature rectangle can be specified in the configuration file by adding the entry sigrect=[lx by rx ty] with the new values. The text part of the visible signature consists of four lines: the cn (common name, obtained from the certificate), reason, location and date. The font size is adapted to the specified rectangle height. However the width of the rectangle needs to be manually tuned so that the text fits.
A visible signature can also incorporate an image if the sigimage entry is present in the configuration file. The image will be scaled to fit inside the signature rectangle. The suggested approach to select the right sized signature image is to start by choosing the right visible signature rectangle size (as explained above). Once that is known create a signature image with the same proportions (but high enough resolution so that it looks fine when printed). An example: if the rectangle is 130 (units) wide by 40 tall (like the default one) the size when printed will be 130/72 inches by 40/72 inches. Sign your name in a piece of paper inside a rectangle of such dimensions and scan it at, say, 300 dpi. This will create an image that is 130*300/72 (pixels) wide by 40*300/72 tall (or 542 by 167 pixels). An image with such resolution will scale nicely and the resulting graphics will have the right size. Images of different proportions can be used but since they will be scaled (and centered) to fit inside the rectangle there will be space around two of the sides of the image.
MyPDFSigner has some limitations regarding the type of images that can use. The image needs to be of RGB-Alpha PNG type. That is not a serious limitation since it is the default format used by Gimp when saving a color PNG file that includes a transparent layer. In case of doubt look at the image properties using Gimp.
An example of a signed and time stamped PDF document with a visible signature is available here. Note that it was signed with a self signed certificate so the warning one sees when opening in Adobe Reader is expected.
# visible signature parameters # sigpage defaults to 1; sigimage needs to point to a PNG image sigrect=[lx by rx ty] sigpage=1 sigimage=/path/to/image.png
Encryption: Encryption of signed PDFs can be enabled if a non empty password is provided (either in the command line, with the -p switch, or using the API - see below). The password provide is the User (or Open Document) password. An Owner (or Permissions) password can also be provided in the configuration file, together with the permissions, using the following entries:
# pdf encryption parameters # owner (permissions) password encpermpwd=ownerpassword # print permission: 0 = full, 1 = low, 2 = none encpermprint=2 # change permission: 0 = all, 1 = annotate, 2 = form, 3 = assembly, 4 = none encpermchange=4 # copy permission: 0 = no, 1 = yes encpermcopy=0 # accessbility permission: 0 = no, 1 = yes encpermaccess=0 # metadata encryption: 0 = no, 1 = yes encmetadata=1
Note: support for certified signatures is not implemented yet if encryption is used.
Hash Algorithm: By default MyPDFSigner uses SHA256 but that can be replaced by SHA1, SHA224, SHA384 or SHA512 if the entry hashalgo is present in the configuration file (example: hashalgo=sha1). Although SHA256 is more secure that SHA1 it may also be noticeably slower if the entropy gathering engine of your machine is slow (this is in general not observed but it may happen). You can check whether that is the case by experimenting signing with SHA1.
# hash algorithm parameters # possible values: sha1, sha224, sha256 (default), sha384 and sha512 hashalgo=sha256
Subfilter: By default MyPDFSigner uses the adbe.pkcs7.detached subfilter but that can be changed using the entry subfilter in the configuration file. The possible values are adbe.pkcs7.sha1, adbe.x509.rsa.sha1, adbe.pkcs7.detached (the default) and ETSI.CAdES.detached. Note that the subfilter value adbe.pkcs7.sha1 requires hashalgo=sha1, and the subfilter value adbe.x509.rsa.sha1, not withstanding the name, does allow for hash algorithms that are not SHA1 (so any of the SHA algorithms is allowed), but MyPDFSigner does not support the creation yet of signatures compatible with this subfilter (but the PDF can be prepared for signature using this subfilter, and the signature can then be created by a third party application). The subfilter value ETSI.CAdES.detached should be used to create signed documents compliant with PAdES compliance level B-B and B-T (requires that Time Stamping is enabled).
# subfilter parameters # possible values: adbe.pkcs7.sha1 (requires hashalgo=sha1), adbe.x509.rsa.sha1, adbe.pkcs7.detached (the default) and ETSI.CAdES.detached subfilter=adbe.pkcs7.detached
Application Name: If using a server license you can tag the signed PDFs with your own application name. This is the name that appears in the "Advanced Signature Properties" window of Adobe Reader (click visible signature box → Signature Properties.. → Advanced Properties...). To that end add the entry appname to your configuration file (example: appname=ACME Super Signer). More information is available in the license page.
# name of signing application (this requires a server license) appname=ACME Super Signer
MyPDFSigner provides three main functions, one to sign PDFs, one to verify signed PDFs, and a last one to add PAdES LTV (Long Term Validation) to already signed PDF documents. Other functions to sign a PDF by steps (to prepare a PDF for signing and generate the associated hash, to sign the hash and create the signature structure, and to embed the signature in the previously prepared PDF) are also provided and are shown in one of the examples.
The list of functions is shown below. Depending on the scripting language used they should be prepended either with mypdfsigner. or mypdfsigner_. The arguments, which are self explanatory, are always strings, except for visible, certify and timestamp, which are boolean. The return values are strings, that can be tokenized to retrieve values that may be fed into other functions.
sign(input, output, password, location, reason, visible, certify, timestamp, conffile)
add_metadata_sign(input, output, password, location, reason, visible, certify, timestamp, title, author, subject, keywords, conffile)
prepare_signature(input, output, password, location, reason, visible, certify, signername, certsn, conffile)
add_metadata_prepare_signature(input, output, password, location, reason, visible, certify, signername, certsn, title, author, subject, keywords, conffile)
create_signature(hash, timestamp, conffile)
add_ltv(input, output, conffile)
The test directory of the installation includes a couple of examples that show how to use these functions and what return values to expect.
<?php $inputPDF = "/usr/local/mypdfsigner/tests/example.pdf"; $outputPDF = "/tmp/example-signed-php.pdf"; $password = ""; # if non empty document will also be encrypted $location = "PHP Location"; $reason = "PHP Reason"; $visible = TRUE; $certify = TRUE; $timestamp = TRUE; $title = "PHP Title"; $author = "PHP Author"; $subject = "PHP Subject"; $keywords = "PHP Keywords"; $confFile = "/usr/local/mypdfsigner/tests/mypdfsigner.conf"; $signResult = mypdfsigner_add_metadata_sign($inputPDF, $outputPDF, $password, $location, $reason, $visible, $timestamp, $certify, $title, $author, $subject, $keywords, $confFile); echo $signResult . "\n"; $verifyResult = mypdfsigner_verify($outputPDF, $confFile); echo $verifyResult . "\n"; ?>
require 'mypdfsigner' include MyPDFSigner inputPath = "/usr/local/mypdfsigner/tests/example.pdf" outputPath = "/tmp/example-signed-ruby.pdf" password = "" # if non empty document will also be encrypted location = "Ruby Location" reason = "Ruby Reason" visible = true certify = true timestamp = true title = "Ruby Title" author = "Ruby Author" subject = "Ruby Subject" keywords = "Ruby Keywords" confFile = "/usr/local/mypdfsigner/tests/mypdfsigner.conf" signResult = mypdfsigner_add_metadata_sign(inputPath, outputPath, password, location, reason, visible, timestamp, certify, title, author, subject, keywords, confFile) puts signResult verifyResult = mypdfsigner_verify(outputPath, confFile) puts verifyResult
import mypdfsigner inputPath = "/usr/local/mypdfsigner/tests/example.pdf" outputPath = "/tmp/example-signed-python.pdf" password = "" # if non empty document will also be encrypted location = "Python Location" reason = "Python Reason" visible = True certify = True timestamp = True title = "Python Title" author = "Python Author" subject = "Python subject" keywords = "Python keywords" confFile = "/usr/local/mypdfsigner/tests/mypdfsigner.conf" signResult = mypdfsigner.add_metadata_sign(inputPath, outputPath, password, location, reason, visible, certify, timestamp, title, author, subject, keywords, confFile) print signResult verifyResult = mypdfsigner.verify(outputPath, confFile) print verifyResult
Note that although the path to the configuration file can be passed as an argument of the sign function, that approach is not recommended if using PKCS#11 key stores. Instead it is recommended that the configuration file is saved to the default location (/usr/local/mypdfsigner/mypdfsigner.conf) and an empty argument is passed to the sign function. This has the benefit that the registration of the PKCS#11 engine happens at startup time (i.e., when the web server starts) and the cleanup happens at shutdown time (when the web server shuts down). This issue is not relevant if using the command line or if using a PKCS#12 key store.
Byte Range: During the process of signing a PDF a default placeholder for the signature is created inside the document. If this placeholder is not large enough to hold the signature, and this generally happens if the certificate chain is long, MyPDFSigner will emit an error similar to:
-1#Byte range underestimated... add extrarange=5300 to config file (add to existing value).
This means that placeholder is too small and a larger one is needed. To help MyPDFSigner create a larger placeholder add the following entry to the configuration file and rerun the signing process.
# tell mypdfsigner to expand the default placeholder by this amount extrarange=5300