Lab - 5 - PT Citizen Card and Smartcards

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()
Previous
Next