PDF Signing using hardware token (DSC) in Python

Hardware based tokens are widely used in India to generate signed PDF’s like invoices and agreement. We wrote small Python code to sign the invoices automatically where token was attached to a local server.

Windows drives are widely available but rare to find linux drivers are listed https://www.e-mudhra.com/Repository/index.html

You can uncomment to get the token name print(self.pkcs11.getSlotList(tokenPresent=True))
print(self.pkcs11.getTokenInfo(1)) to get token name, for PROXKey the name "WD PROXKey" was generated.

#!/usr/bin/env vpython3
# *-* coding: utf-8 *-*
import sys
import datetime
from endesive import pdf, hsm

import os
import sys

if sys.platform == 'win32':
    dllpath = r'c:\windows\system32\cryptoCertum3PKCS.dll'
else:
    dllpath = '/usr/lib/WatchData/ProxKey/lib/libwdpkcs_SignatureP11.so'

import PyKCS11 as PK11

class Signer(hsm.HSM):
    def certificate(self):
        #print(self.pkcs11.getSlotList(tokenPresent=True))
        #print(self.pkcs11.getTokenInfo(1))
#        print(self.pkcs11.getTokenInfo(2))
#        print(self.pkcs11.getTokenInfo(3))


#        print(self.pkcs11.getSlotInfo(1))
        self.login("WD PROXKey","12345678") # WF PROXKey is token name.
        keyid = [0x5e, 0x9a, 0x33, 0x44, 0x8b, 0xc3, 0xa1, 0x35, 0x33, 0xc7, 0xc2, 0x02, 0xf6, 0x9b, 0xde, 0x55, 0xfe, 0x83, 0x7b, 0xde]
        #keyid = [0x3f, 0xa6, 0x63, 0xdb, 0x75, 0x97, 0x5d, 0xa6, 0xb0, 0x32, 0xef, 0x2d, 0xdc, 0xc4, 0x8d, 0xe8]
        keyid = bytes(keyid)
        try:
            pk11objects = self.session.findObjects([(PK11.CKA_CLASS, PK11.CKO_CERTIFICATE)])
            all_attributes = [
                #PK11.CKA_SUBJECT,
                PK11.CKA_VALUE,
                #PK11.CKA_ISSUER,
                #PK11.CKA_CERTIFICATE_CATEGORY,
                #PK11.CKA_END_DATE,
                PK11.CKA_ID,
            ]

            for pk11object in pk11objects:
                try:
                    attributes = self.session.getAttributeValue(pk11object, all_attributes)
                except PK11.PyKCS11Error as e:
                    continue

                attrDict = dict(list(zip(all_attributes, attributes)))
                cert = bytes(attrDict[PK11.CKA_VALUE])
                #if keyid == bytes(attrDict[PK11.CKA_ID]):
                return bytes(attrDict[PK11.CKA_ID]), cert
        finally:
            self.logout()
        return None, None

    def sign(self, keyid, data, mech):
        self.login("WD PROXKey","12345678")
        try:
            privKey = self.session.findObjects([(PK11.CKA_CLASS, PK11.CKO_PRIVATE_KEY)])[0]
            mech = getattr(PK11, 'CKM_%s_RSA_PKCS' % mech.upper())
            sig = self.session.sign(privKey, data, PK11.Mechanism(mech, None))
            return bytes(sig)
        finally:
            self.logout()

def main():
    date = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
    date = date.strftime('%Y%m%d%H%M%S+00\'00\'')
    dct = {
        "sigflags": 3,
        "sigpage": 0,
        "sigbutton": True,
        "contact": "madhurendra@tikaj.com",
        "location": 'India',
        "signingdate": date.encode(),
        "reason": 'Sample sign',
        "signature": 'Madhurendra Sachan',
        "signaturebox": (0, 0, 100, 100),
    }
    clshsm = Signer(dllpath)
    fname = 'sample.pdf'
    datau = open(fname, 'rb').read()
    datas = pdf.cms.sign(datau, dct,
        None, None,
        [],
        'sha256',
        clshsm,
    )
    fname = fname.replace('.pdf', '-signed.pdf')
    with open(fname, 'wb') as fp:
        fp.write(datau)
        fp.write(datas)


main()

.dll/.so path for common tokens

  • For Windows the file will be in Windows\SysWOW64″ or “WINDOWS\system32” or “WINNT\system32”
  • For Linux machine the file will be in /usr/local/lib/ or /usr/lib/
Hardware Token TypeLibrary file (Windows)Library file (Linux)
SafeSignaetpkss1.dllaetpkss1.so
eMudhraeMudhra\eMudhra CSPV1.0\wdpkcs.dll1. WatchData/eMudhra_3.4.3/lib/libpkcs11wrapper.so
2. WatchData/eMudhra_3.4.3/lib/libwdpkcs_eMudhra_343.so
Trust Key1. TRUST KEY\TRUST KEY CSP V1.0\wdpkcs.dll
2. C:\Windows\System32\TRUSTKEYP11_ND_v34.dll
1. WatchData/TRUSTKEY/lib/libpkcs11wrapper.so
2. WatchData/TRUSTKEY/lib/libwdpkcs_TRUSTKEY.so
Belgium eID MiddleWarebeidpkcs11.dllbeidpkcs11.so
Gemalto Cryptocard Tokenlibgtop11dotnet.dlllibgtop11dotnet.so
EPasseps2003csp11.dll
Aladdin eTokeneTPKCS11.dll
Safenet iKeydkck201.dll
Starkeyaetpkss1.dll
Watchdata PROXkeySignatureP11.dllWatchData/ProxKey/lib/libwdpkcs_SignatureP11.so
.DLL or .so path’s for linux.

9 comments

Skip to comment form

    • Ignacio on January 16, 2021 at 2:12 am
    • Reply

    thank you very much!! great job!!

    • Andreas on February 21, 2021 at 2:05 am
    • Reply

    Madhurendra, thank you very much for this blog post!
    I have got a eToken 5110 with a globalsign AATL certificate to sign pdf. How can I find out the hex values of my private Key?

    Thanks a lot!
    Andreas

    1. Hi Andreas,

      Thank you for going through the blog, I am glad it helped you.

      You can use bytes(attrDict[PK11.CKA_ID]) for value, if you want you can match it.

    • Andreas on February 21, 2021 at 2:12 am
    • Reply

    Madhurendra, thank you very much for this blog post!
    I have got a eToken 5110 with a globalsign AATL certificate to sign pdf. How can I find out the hex values of my private KeyID?

    It’s the value of line 27

    Thanks a lot!
    Andreas

    • Sanish Samuel on February 22, 2021 at 12:37 pm
    • Reply

    Hello Madhurendra,

    I need some similar work to be done for my company. Could you mail me, to discuss more.

    • Fernando Cabral on June 11, 2021 at 9:18 pm
    • Reply

    Hi, Madhurendra

    I am trying to use your application but I am stuck with an error message I have not been able to interpret.
    Here is the output:

    Traceback (most recent call last):
    File “assinapdf.py”, line 103, in
    main()
    File “assinapdf.py”, line 91, in main
    datas = pdf.cms.sign(datau, dct,
    File “/home/fernando/.local/lib/python3.8/site-packages/endesive/pdf/cms.py”, line 955, in sign
    return cls.sign( File “/home/fernando/.local/lib/python3.8/site-packages/endesive/pdf/cms.py”, line 653, in sign
    contents = signer.sign( File “/home/fernando/.local/lib/python3.8/site-packages/endesive/signer.py”, line 86, in sign
    keyid, cert = hsm.certificate()
    File “assinapdf.py”, line 39, in certificate pk11objects = self.session.findObjects([(PK11.CKA_CLASS, PK11.CKO_CERTIFICATE)])
    AttributeError: ‘NoneType’ object has no attribute ‘findObjects’

    Any hints on where I should look for the problem?

    1. I think your token is not connected, possibly driver or DLL is incorrect.

    • Nikolay Penev on November 17, 2021 at 12:12 am
    • Reply

    Hi, Madhurendra

    I have some problem when using your great py script.

    If i open signed doc in adobe reader i see error in sign:

    Signature validity is UNKNOWN.
    The signer’s identity is unknown because it has expired or is not yet valid.

    Can you help me to fix this?

    • Durgesh on February 23, 2022 at 5:19 pm
    • Reply

    Hi, Madhurendra
    It is a very nice post. However I am not able to resolve this error.

    Traceback (most recent call last):
    File “C:/Users/durge/OneDrive/D_Folders/AutoDX/digsign1.py”, line 81, in
    signpdf()
    File “C:/Users/durge/OneDrive/D_Folders/AutoDX/digsign1.py”, line 72, in signpdf
    datas = pdf.cms.sign(datau, dct, None,None ,[],’sha256′,clshsm,)
    File “C:\Users\durge\anaconda3\lib\site-packages\endesive\pdf\cms.py”, line 965, in sign
    return cls.sign(
    File “C:\Users\durge\anaconda3\lib\site-packages\endesive\pdf\cms.py”, line 655, in sign
    contents = signer.sign(
    File “C:\Users\durge\anaconda3\lib\site-packages\endesive\signer.py”, line 202, in sign
    signed_value_signature = hsm.sign(keyid, tosign, hashalgo)
    File “C:/Users/durge/OneDrive/D_Folders/AutoDX/digsign1.py”, line 35, in sign
    privKey = self.session.findObjects([(PK11.CKA_CLASS, PK11.CKO_PRIVATE_KEY)])[0]
    AttributeError: ‘NoneType’ object has no attribute ‘findObjects’

Leave a Reply

Your email address will not be published.

%d bloggers like this: