Introduction
In this guide you will develop Python programs using the Python PKCS11 module[^1] that interacts with PKCS#11 tokens to produce and validate digital signatures.
For that, you need to have installed the following software:
-
pcscd:
sudo apt install pcscd
-
Python3 PIP:
sudo apt install python3-pip
-
Swig:
sudo apt instlal swig
-
PyKCS11:
pip3 install --user pykcs11
-
PTEID Software: available at https://www.autenticacao.gov.pt/cc-aplicacao and pre-installed in the Virtual Machine Image.
-
(Optional) Opensc:
sudo apt install opensc-pkcs11
You can verify the correct operation of the software by executing
eidguiV2
and pkcs11-tool -L
.
PKCS#11 Standard
The PKCS#11 standard (Cryptographic Token Interface Standard) is produced by RSA Security and defines native programming interfaces to cryptographic tokens, such as hardware cryptographic accelerators and smartcards, as is the case of Portuguese Citizen Card.
Python PyKCS11 includes a provider that, in contrast to most other providers, does not implement cryptographic algorithms itself. Instead, it exposes functions that are executed in the hardware tokens, and allows to obtain the objects stored in a smart card. When using the Python Cryptography module, this is the role of the Backends (providing access to hardware tokens). Unfortunately, no public (and stable) backend supports PKCS#11, making it required to use a standalone module (PyKCS#11).
Loading the card interface module
Interating with the Portuguese Citizen Card requires the use of a module
that translated PKCS#11 calls into some internal format adequate to the
smart card hardware. This is independent of the programming language
used, and it is why we need to install the Portuguese Citizen Card
software: besides a visualization tool, it also contains libraries that
allow accessing the Portuguese Citizen Card (libpteidpkcs11.so
)
Using the PyKCS11 module, the following snippet will try to list all slots, and present information about the tokens contained:
import PyKCS11
import binascii
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend as db
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.hashes import SHA1, Hash
lib = '/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so'
pkcs11 = PyKCS11.PyKCS11Lib()
pkcs11.load(lib)
slots = pkcs11.getSlotList(tokenPresent=True)
Task: List all tokens and print the detais of each token (revision, manufacturer, model, serial, etc...).
Contents of the Portuguese Citizen Card
After the token is available, it is possible to list the objects it contains. Some objects have public visibility and are accessed through a public session, while others are private, and the user must explicitelly create a private session. The different between sessions lies in the existence of omission of a pin code when opening the session.
You can list the contents of the Portuguese Citizen Card (i.e., its PKCS#11 objects), with code based on the following snippet:
...
all_attr = list(PyKCS11.CKA.keys())
#Filter attributes
all_attributes = [e for e in all_attr if isinstance(e, int)]
session = pkcs11.openSession(slot)
# session.login('1111') # DANGER!!! USE YOUR PINCODE!!
#### Search for objects and extract reference to private key and certificate
for obj in session.findObjects():
attr = session.getAttributeValue(obj, all_attributes)
attrDict = dict(list(zip(all_attributes, attr)))
print("Type:", PyKCS11.CKO[attrDict[PyKCS11.CKA_CLASS]], "\tLabel:", attrDict[PyKCS11.CKA_LABEL])
The information you get in the CKA_LABEL
attribute is the
identification of the object included in the Portuguese Citizen Card.
Through these identifiers we can select which certificate, or private
key, we intend to use for each cryptographic operation. The attribute
CKA_CLASS
identifies the class of the object and the attribute
CKA_CERTIFICATE_TYPE
can identify the type of a certificate.
You can individually inspect all attributes of the objects. If you
require access to the certificate content, convert the tuple contained
in the CKA_VALUE
to bytes (bytes(attributes['CKA_VALUE'])
) and load
it as a DER
certificate (x509.load_der_x509_certificate
).
cert_obj = session.findObjects([
(PyKCS11.CKA_CLASS, PyKCS11.CKO_CERTIFICATE),
(PyKCS11.CKA_LABEL, 'CITIZEN AUTHENTICATION CERTIFICATE')
])[0]
cert_der_data = bytes(cert_obj.to_dict()['CKA_VALUE'])
Task: Print the label of all objects, as well as the issuer and
subject of all certificates. Check with and without session.login()
.
Hint: Once loaded as a X509 certificate, you can reuse the code from the last class to print the issuer and subject, or even extract the public key.
Digital signature
Task: Create a program capable of generating a digital signature of a document, using the Portuguese Citizen Card, and storing it on a given file. Furthermore, complement it for writing on a file the public key certificate corresponding to the private key used for signing the document.
For this purpose consider the CITIZEN AUTHENTICATION CERTIFICATE
and
the corresponding CITIZEN AUTHENTICATION KEY
(the private key), as
many citizens to not have the CITIZEN SIGNATURE CERTIFICATE
activated.
Because the private key is not extractable, you cannot load this key as
a RSA key. Instead, use the session
object to sign the text:
mechanism = PyKCS11.Mechanism(PyKCS11.CKM_SHA1_RSA_PKCS, None)
text = b'text to sign'
signature = bytes(session.sign(private_key, text, mechanism))
print("signature: ", binascii.hexlify(signature))
Signature validation
To validate the signature we will not use the card. In reality this would be done by the message/document recipient, which doesn’t have access to the signers’ card. The process involves extracting the public key from the certificate, compute the text digest, and then validate the signature.
If the signatures do not match, the verify
method will throw an exception.
cert = x509.load_der_x509_certificate(cert_data, backend=db())
md = Hash(SHA1(), backend=db())
md.update(text)
digest = md.finalize()