2

I wrote a simple Python script which makes a digital signature using my smart card (Rutoken ECP SC), PKCS#11 library (implemented by my vendor) and PyKCS11 wrapper for Python. I already generated the private/public key pair using this card and created a signature using pkcs11 but I don't know how to verify this signed data. The problem is that my pkcs11 library (implemented in c++) has verification methods but PyKCS11 wrapper don't. And now I don't know how to solve this problem. I will be appreciated if anybody could tell me how to fix this problem.

That's my script:

import PyKCS11
import getopt
import sys
import platform

red = blue = magenta = normal = ""
if sys.stdout.isatty() and platform.system().lower() != 'windows':
    red = "\x1b[01;31m"
    blue = "\x1b[34m"
    magenta = "\x1b[35m"
    normal = "\x1b[0m"

format_long = magenta + "  %s:" + blue + " %s (%s)" + normal
format_binary = magenta + "  %s:" + blue + " %d bytes" + normal
format_normal = magenta + "  %s:" + blue + " %s" + normal

pkcs11 = PyKCS11.PyKCS11Lib()
lib_path = "/usr/lib/pkcs11-arm/rtpkcs11ecp/librtpkcs11ecp.so"
pkcs11.load(lib_path)
info = pkcs11.getInfo()
print "Library manufacturerID: " + info.manufacturerID

slots = pkcs11.getSlotList()
print "Available Slots:", len(slots)

# As I understand we need only first slot
if len(slots) > 0:
    slot = slots[0]
    slotInfo = pkcs11.getSlotInfo(slot)
    tokenInfo = pkcs11.getTokenInfo(slot)

    flags = PyKCS11.CKF_RW_SESSION
    session = pkcs11.openSession(slot, flags)
    print "Opened session 0x%08X" % session.session.value()
    pin = "12345678"
    session.login(pin)
    objects = session.findObjects()                
    all_attributes = PyKCS11.CKA.keys()             # all keys supported by SC

    print "Defining KEY_GENERATION mechanism"
    mech = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS_KEY_PAIR_GEN, None)


    print "Generating key"
    public_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_VERIFY, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_WRAP, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_RSA),
        (PyKCS11.CKA_VERIFY_RECOVER, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_MODULUS_BITS, 2048),
    ]

    private_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_SIGN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_UNWRAP, PyKCS11.CK_TRUE)
        ]

    (pub, priv) = session.generateKeyPair(public_template, private_template, mech)

    # ==================================================
    # Signing data
    sourceText = "Hello World"
    binaryData = ' '.join(format(ord(x), 'b') for x in sourceText)

    signMechanism = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS, None)
    signedData = session.sign(priv, binaryData, signMechanism)
    print signedData


    #====================================================
    # now we have to verify signedData using the private key



    session.logout()
    session.closeSession()
    print "Close session 0x%08X" % session.session.value()

This is the output of the sign method:

[83L, 29L, 52L, 93L, 228L, 220L, 13L, 187L, 224L, 212L, 112L, 204L, 198L, 91L, 207L, 6L, 215L, 38L, 233L, 194L, 252L, 140L, 106L, 62L, 69L, 94L, 252L, 89L, 194L, 18L, 58L, 240L, 174L, 2L, 26L, 212L, 152L, 134L, 40L, 67L, 163L, 53L, 226L, 74L, 15L, 47L, 200L, 131L, 58L, 199L, 22L, 103L, 145L, 235L, 196L, 117L, 196L, 78L, 160L, 223L, 118L, 0L, 147L, 91L, 9L, 146L, 218L, 142L, 1L, 47L, 192L, 20L, 96L, 230L, 77L, 242L, 124L, 232L, 77L, 130L, 207L, 226L, 165L, 108L, 241L, 198L, 33L, 9L, 79L, 238L, 35L, 53L, 127L, 31L, 118L, 167L, 4L, 84L, 158L, 98L, 171L, 37L, 221L, 208L, 80L, 17L, 142L, 61L, 207L, 204L, 17L, 94L, 38L, 136L, 44L, 161L, 191L, 131L, 237L, 213L, 108L, 175L, 14L, 31L, 61L, 2L, 85L, 6L, 104L, 226L, 201L, 71L, 141L, 243L, 72L, 2L, 142L, 83L, 87L, 140L, 1L, 83L, 26L, 93L, 96L, 96L, 207L, 217L, 222L, 168L, 78L, 221L, 158L, 199L, 213L, 82L, 212L, 45L, 62L, 14L, 22L, 128L, 68L, 76L, 205L, 247L, 124L, 23L, 69L, 123L, 68L, 116L, 239L, 49L, 130L, 207L, 43L, 194L, 9L, 4L, 55L, 35L, 51L, 21L, 233L, 198L, 121L, 212L, 61L, 244L, 117L, 98L, 174L, 173L, 209L, 252L, 218L, 51L, 63L, 217L, 160L, 18L, 45L, 167L, 161L, 79L, 10L, 130L, 80L, 63L, 234L, 48L, 155L, 66L, 84L, 116L, 186L, 42L, 119L, 250L, 177L, 206L, 90L, 117L, 159L, 98L, 165L, 70L, 141L, 39L, 108L, 212L, 33L, 20L, 163L, 181L, 113L, 177L, 201L, 129L, 108L, 182L, 94L, 14L, 200L, 213L, 22L, 29L, 182L, 45L, 16L, 242L, 227L, 242L, 192L, 42L]
Tequila
  • 726
  • 7
  • 23
  • 3
    (Theoretically) as public key is not sensitive, you can verify the signature without using the token (see e.g. [here](http://stackoverflow.com/questions/544433/how-do-you-verify-an-rsa-sha1-signature-in-python)). You would need to extract the public key first (via `C_GetAttributeValue` for `CKA_MODULUS` and `CKA_PUBLIC_EXPONENT`). – vlp Nov 21 '15 at 14:39
  • 1
    And additional note: consider using some RSA signature variant with a digest (e.g. `CKM_SHA256_RSA_PKCS` or any other `CKM_*_RSA_PKCS` one). And make sure your sign/verify algorithms variants match. – vlp Nov 21 '15 at 14:44
  • Thank you for your reply, @vlp. I will try to test this idea – Tequila Nov 23 '15 at 06:24
  • @Tequila Let vlp post an answer if this works out (there is no reason why it shouldn't). You can always add code to that later if you want to. You may also want to read [this answer of mine](http://crypto.stackexchange.com/a/10103/1172) for more information about signing algorithms in PKCS#11. – Maarten Bodewes Nov 24 '15 at 20:10
  • Thanks, @MaartenBodewes.Yes I was planning to post my source code after the verification will be done. At this moment I extracted the CKA_MODULUS and CKA_PUBLIC_EXPONENT attributes. And they looks like this: CKA_MODULUS: (196L, ... 133L) CKA_PUBLIC_EXPONENT: (1L, 0L, 1L) but it is not the base64string. To use it in vlp solution we have to convert it in base64string format. But I don't know will this conversation affect to the verification results or not? or maybe I did something wrong? – Tequila Nov 25 '15 at 07:56
  • So I had extracted CKA_MODULUS and CKA_PUBLIC_EXPONENT and wanted to create a rsaObject using RSA.new_pub_key((e,n)) function to verify the signature (as described in tle @vlp post) but it requares public exponent and modulus be in the OpenSSL's MPINT format. And I don't know how to convert this values from the hex format to MPINT programatically. Could you tell me please is this possible to convert to this format using M2Crypto library ? My modulus attribute has 2048 bit length – Tequila Nov 26 '15 at 11:24
  • These objects has next values: modulus = c461b0b839e1c09987f4372bc53d54c2f39950da0a3947ea66128a9fe70afe552498ba4dc2cfcd9723fcf7e15b53803ef7a3b2bbd8a3ca9a4387a301714ff6a3103a3c0da39fa2ddd171d358066557ba55063fa94d26fc7ee5ca99c3b749cbe7a09febdfa032ff405ea55e1a72a2deb2355729dd3f45c22e3e4a1528f76c3fd46b18358628d6aa798492e7b57b634928dc1da50d897bfecac7da1c57328e9ac5cff2dc848b7fc900cf762161215eb9cbfcc600334e6bb2b30b44d83c8d7b996b4e2d0c91b0978c52140a31db9798057e154d02050adc878907ee20376985861d12c46b8e252ef5b1174f3aea40ba6f7228bddba6f414b5bb3acfe606d8dcfa85 exponent = 010001 – Tequila Nov 26 '15 at 11:26
  • 1
    To get (some) pubkey: `RSA.new_pub_key(('\x00\x00\x01\x00\xc4\x61\xb0\xb8\x39\xe1\xc0\x99..skipped...\xdc\xfa\x85', '\x00\x00\x00\x03\x01\x00\x01'))` which works for me (see [here](http://stackoverflow.com/a/10368618/5128464) and [here](http://stackoverflow.com/a/1902766/5128464)). I am not that into python for the rest for the code...sorry PS: Please consider using `CKM_SHA1_RSA_PKCS` instead of `CKM_RSA_PKCS`, as M2Crypto's `RSA.verify()` seems to require a digest (SHA1 being the default one)...Good luck! PS2: The public key parameters you got look ok. – vlp Nov 26 '15 at 22:03
  • sorry for so long anwer. Thank you very much @vlp! With your help I could create rsa object. But I have another question. You told that I need to use CKM_SHA1_RSA_PKCS sign mechanism instead of CKM_RSA_PKCS. But what if I can't do it because my smart card does not support this mechanism? In this case I can't use this statement pubkey.reset_context(md='sha1') . So how could I verify my signature in this case? – Tequila Nov 30 '15 at 04:16
  • Seems that your token really [does not support signatures with a digest](https://github.com/roman-sopov/RutokenJS/blob/master/index.html#L58). I tried to get the signature with hash working in python (by prepending the digestinfo) but had to stop now without a working result. Do you insist on python? [Java pkcs11](https://docs.oracle.com/javase/7/docs/technotes/guides/security/p11guide.html) might work better for you...It all depends on what exactly you want to achieve... – vlp Dec 01 '15 at 13:49
  • In my situation Python is the best idea because my application has been developed using Python. I would like to avoid to use different programming languages because it makes the application more difficulty to support in future. However, if we can't use Python for this purpose I have to choose another way( who knows maybe c++ or Java). But I really don't like this idea =( Or maybe I could find some another library which could verify the CKM_RSA_PKCS signature (not M2Crypto) – Tequila Dec 01 '15 at 17:57

1 Answers1

3

This code works for me (beware, the public exponent is assumed to be 3 bytes long):

import PyKCS11
import getopt
import sys
import platform
import hashlib
from M2Crypto import RSA

pkcs11 = PyKCS11.PyKCS11Lib()
lib_path = "/opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so"
pkcs11.load(lib_path)
info = pkcs11.getInfo()
slots = pkcs11.getSlotList()
if len(slots) > 0:
    session = pkcs11.openSession(slots[0], PyKCS11.CKF_RW_SESSION)
    session.login("12345678")
    mech = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS_KEY_PAIR_GEN, None)
    public_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_VERIFY, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_WRAP, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_RSA),
        (PyKCS11.CKA_VERIFY_RECOVER, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_MODULUS_BITS, 2048),
    ]
    private_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_SIGN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_UNWRAP, PyKCS11.CK_TRUE)
        ]
    (pub, priv) = session.generateKeyPair(public_template, private_template, mech)
    (pubExp,pubModulus) = session.getAttributeValue(pub,[PyKCS11.CKA_PUBLIC_EXPONENT,PyKCS11.CKA_MODULUS], True)
    # ==================================================
    # Signing data
    binaryData = "Hello world"
    # Generate SHA1
    sha1 = hashlib.sha1()
    sha1.update(str(bytearray(binaryData)))
    digest=sha1.digest()
    # Indicate SHA1 is used
    binaryData2='\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'+digest
    signMechanism = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS, None)
    signedData = session.sign(priv, binaryData2, signMechanism)
    session.logout()
    session.closeSession()
    # ==================================================
    # Verify
    pubkey = RSA.new_pub_key(('\x00\x00\x00\x03' + str(bytearray(pubExp)), '\x00\x00\x01\x01\x00'+str(bytearray(pubModulus))))
    result=pubkey.verify(str(bytearray(digest)), str(bytearray(signedData)), 'sha1')
    print "VERIFY:" + str(result)

I am not into python, so please take it as a proof of concept and not as a solution. For the interesting parts:

  • as your PKCS#11 driver does not support RSA signature with a hash it is needed to calculate the hash and build the DigestInfo ASN.1 part manually (the result is in the binaryData2 variable)

  • as RSA.new_pub_key() accepts a tuple in openssl's format for BN_mpi2bn (which it uses internally), it was needed to prefix the modulus with one additional \x00 to ensure it is interpreted as a positive number (the '\x00\x00\x01\x01\x00' part)

  • given the function verify() uses openssl's RSA_verify which takes as an argument a digest of signed data (and not the data itself) it was needed to obey and give it the digest (which is re-used from the signature generation part and you would have to generate a fresh one if you plan to have a separate verify function)


Note: For e.g. SHA256, you would need to use the appropriate digestInfo magic ASN.1 string prefix (see here for usable values) + appropriate digest object from the hashlib + correct 3rd verify call argument.

Good luck!

Community
  • 1
  • 1
vlp
  • 7,811
  • 2
  • 23
  • 51
  • wow, it works! How did you do this ?) It is a good news for this day! Thank you very much! I almost wrote this signature mechanism on the c++, but with your help it is not necessary for now)) – Tequila Dec 02 '15 at 06:36
  • Could you recommend some books or resources about such things? It will be great if these literature has Russian translation (If I'm not mistakes you speak in Russian). Thanks a lot! – Tequila Dec 02 '15 at 06:49
  • @Tequila I am very glad it works! Regarding resources...google? Good luck with your project! – vlp Dec 03 '15 at 00:55