LudovicRousseau / PyKCS11

PKCS#11 Wrapper for Python
GNU General Public License v2.0
96 stars 35 forks source link

openSession() takes up to 4 seconds using Yubikey/libykcs11 #102

Closed dmuensterer closed 1 year ago

dmuensterer commented 1 year ago

Your system information

Please describe your issue in as much detail as possible:

I'm using PyKCS11 to read the certificate from a Yubikey using a NFC reader to authenticate users. This works well butopenSession() takes up to 4 seconds. I figured there has to be a faster way, or is this expected behaviour?

 #!/usr/bin/env python3

from __future__ import print_function
import OpenSSL
from PyKCS11 import *
import binascii
import base64
import time
import stdiomask

pkcs11 = PyKCS11Lib()
pkcs11.load()  # define environment variable PYKCS11LIB=YourPKCS11Lib

allowedCNs = ['authorized@myorganisation.com','authorized2@myorganisation.com']

while 1:
    print('Please enter PIN')
    #pin = input()
    pin = stdiomask.getpass()

    # Allow for exit
    if pin == 'exit':
        exit()

    # get 1st slot
    print('Please present PIV device')
    while 1:
        try:
            slot = pkcs11.getSlotList(tokenPresent=True)[0]
            print('Authenticating. Do not remove...')
            break
        except:
            continue
    try:
        #### Measure time
        beginTime = time.time()
        ####

        session = pkcs11.openSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION)
        ### Measure time
        print("Time to open session")
        print(time.time()-beginTime)

        # Session login with pin needed for signing
        session.login(pin)

        ### Measure time
        print("Time to login via pin")
        print(time.time()-beginTime)

        object = session.findObjects([(CKA_CLASS, CKO_CERTIFICATE)])[0]
        all_attributes = [CKA_SUBJECT, CKA_VALUE, CKA_ISSUER, CKA_CERTIFICATE_CATEGORY, CKA_END_DATE]

        attributes = session.getAttributeValue(object, all_attributes)

        attrDict = dict(list(zip(all_attributes, attributes)))
        #print(attrDict)
        #print(binascii.hexlify(bytearray(attrDict[CKA_SUBJECT])))

        if attrDict[CKA_CERTIFICATE_CATEGORY] == (0x2, 0x0, 0x0, 0x0):
            exit()
        cert_header = "-----BEGIN CERTIFICATE-----\n"
        cert_footer = "\n-----END CERTIFICATE-----"
        cert = cert_header + str(base64.b64encode(bytearray(attrDict[CKA_VALUE])).decode('ascii')) + cert_footer

        client_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)

        ### Measure time
        print("Time to get certificate from key")
        print(time.time()-beginTime)

        # Extract CN from certificate
        cn=client_cert.get_subject().CN

        obj = session.findObjects([(CKA_CLASS, CKO_PRIVATE_KEY)])

        root_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open('ca-root.cer', 'rt').read())

        store = OpenSSL.crypto.X509Store()
        store.add_cert(root_cert)
        ctx = OpenSSL.crypto.X509StoreContext(store, client_cert)

    except Exception as e:
        print('Unknown error')
        print(e)
        continue
    try:
       ctx.verify_certificate()
       verified = True
    except:
        verified = False
    try:
        signedTime = time.time()
        toSign = str(signedTime)

        mechanism = Mechanism(CKM_ECDSA_SHA1, None)

        # find first private key and compute signature
        privKey = session.findObjects([(CKA_CLASS, CKO_PRIVATE_KEY)])[0]
        signature = session.sign(privKey, toSign, mechanism)
        #print("Signature: {}".format(binascii.hexlify(bytearray(signature))))

        # find first public key and verify signature
        pubKey = session.findObjects([(CKA_CLASS, CKO_PUBLIC_KEY)])[0]
        result = session.verify(pubKey, toSign, signature, mechanism)
        curTime = time.time()
        if verified:
            print('Verified certificate chain')
            delay = curTime-signedTime
            if delay<0.3:
                print('Signed&verified in ' + str(delay) + ' seconds')
                if result and cn in allowedCNs:
                    print('User ' + cn + ' is authorized')
                    print('ACCESS GRANTED')
                    print('######DOOR OPEN#####')
                    time.sleep(5)
                    print('######DOOR CLOSED#####')
                else:
                    print("Unauthorized")
                    print('######ACCESS DENIED#####')
                    time.sleep(3)
                    continue
            else:
                print("Signing process took too long. Blocked to prevent relay attack")
                print('######ACCESS DENIED#####')
                time.sleep(3)
                continue
        else:
            print("Untrusted certificate.")
            print('######ACCESS DENIED#####')
            time.sleep(3)
            continue
    except Exception as e:
        print('Unknown error')
        print(e)
        continue

    # logout
    session.logout()
    session.closeSession()

This prints:

Please enter PIN
Password: ********
Please present PIV device
Authenticating. Do not remove...
Time to open session
3.4371397495269775
Time to login via pin
3.4590892791748047
Time to get certificate from key
3.4607536792755127
Verified certificate chain
Signed&verified in 0.21068930625915527 seconds
User dominik.muensterer@deltasecure.de is authorized
ACCESS GRANTED
######DOOR OPEN#####
######DOOR CLOSED#####

Steps for reproducing this issue:

Using a Yubikey to authenticate using an NFC reader.

LudovicRousseau commented 1 year ago

Your source code is not complete.

dmuensterer commented 1 year ago

Thanks for the quick reply. I didn't realize the other context would be relevant. Please find the full code and output above.

LudovicRousseau commented 1 year ago

I am not surprised by the 4 seconds duration. I guess you have the same value if you use the C API directly instead of PyKCS11.

What you can do also is use the same Python program on Windows to see if you have the same results.

LudovicRousseau commented 1 year ago

PyKCS11 does not slow down any native PKCS#11 call. If C_OpenSession() then pkcs11.openSession() will be slow as well.