-
-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PKCS#11 support #237
Comments
Is there any progress on this? |
There is no progress yet, and I expect this to be a complex change. PRs and support donations are welcome. |
maybe not the most elegant solution but this worked for me using the PyKCS11 library import PyKCS11
from lxml import etree
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from signxml.xades import XAdESSigner, XAdESVerifier, XAdESVerifyResult, XAdESSignaturePolicy, XAdESDataObjectFormat
import binascii
class PublicNumbers:
def __init__(self, n, e):
self.n = n
self.e = e
class PublicKey:
def __init__(self, n, e):
self.pubnumbers = PublicNumbers(n, e)
def public_numbers(self):
return self.pubnumbers
class Key:
def __init__(self, session, keyid):
self.session = session
self.keyid = keyid
pubkey = self.session.findObjects([(PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY), (PyKCS11.CKA_ID, self.keyid)])[0]
modulus = session.getAttributeValue(pubkey, [PyKCS11.CKA_MODULUS])[0]
m = int(binascii.hexlify(bytearray(modulus)), 16)
exp = session.getAttributeValue(pubkey, [PyKCS11.CKA_PUBLIC_EXPONENT])[0]
e = int(binascii.hexlify(bytearray(exp)), 16)
self.pubkey = PublicKey(m, e)
def sign(self, data, padding, algorithm):
privkey = self.session.findObjects([(PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY), (PyKCS11.CKA_ID, self.keyid)])[0]
sig = self.session.sign(privkey, data, PyKCS11.Mechanism(PyKCS11.CKM_SHA256_RSA_PKCS))
return bytes(sig)
def public_key(self):
return self.pubkey
class Signer:
def __init__(self):
self.pkcs11 = PyKCS11.PyKCS11Lib()
self.pkcs11.load(pkcs11lib)
try:
self.slot = self.get_slot()
self.session = None
fp = open("file.xml", "rb")
r = fp.read()
fp.close()
root = etree.fromstring(r)
keyid, cert = self.get_cert()
key = Key(self.session, keyid)
data_object_format = XAdESDataObjectFormat(
Description="My XAdES signature",
MimeType="text/xml",
)
signed_root = XAdESSigner(signature_policy=None,
claimed_roles=["signer"],
data_object_format=data_object_format,
c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315").sign(root, key=key, cert=cert)
with open("signed.xml", "wb") as fp:
fp.write(etree.tostring(signed_root))
verifier = XAdESVerifier()
verify_results = verifier.verify(
signed_root, x509_cert=cert, expect_references=3, expect_signature_policy=None
)
finally:
self.logout()
def get_slot(self):
slots = self.pkcs11.getSlotList(tokenPresent=True)
return slots[0]
def get_cert(self):
self.session = self.pkcs11.openSession(
self.slot, PyKCS11.CKF_SERIAL_SESSION | PyKCS11.CKF_RW_SESSION
)
self.session.login(pin)
pk11objects = self.session.findObjects(
[(PyKCS11.CKA_CLASS, PyKCS11.CKO_CERTIFICATE)]
)
all_attributes = [
PyKCS11.CKA_VALUE,
PyKCS11.CKA_ID,
]
for pk11object in pk11objects:
try:
attributes = self.session.getAttributeValue(pk11object, all_attributes)
except PyKCS11.PyKCS11Error as e:
continue
attr_dict = dict(list(zip(all_attributes, attributes)))
cert = bytes(attr_dict[PyKCS11.CKA_VALUE])
keyid = bytes(attr_dict[PyKCS11.CKA_ID])
cert = x509.load_der_x509_certificate(cert, backend=default_backend())
cert = cert.public_bytes(encoding=serialization.Encoding.PEM)
return keyid, cert
def logout(self):
if self.session is not None:
self.session.logout()
self.session.closeSession()
self.session = None
if __name__ == "__main__":
Signer() |
Why does Key class need a |
signxml expects key to be bytes or a RSAPrivateKey instance, in case of bytes it will transform it to a RSAPrivateKey. then in the _serialize_key_value function it calls the public_key() and public_numbers() methods to get the modulus and exponent, so I created a Key class that kinda emulates RSAPrivateKey and implements the sign() and public_key() methods which are needed by signxml. and yes my code is RSA specific because if you look again at the _serialize_key_value method there are other methods and values that I have not implemented like parameter_numbers() or x and y |
Thank you for the pointer. I see in the _serialize_key_value there are only methods to add info for RSA and ECDSA keys. In PKCS11 IMHO there would need to be serialisation for X509Data (https://www.w3.org/TR/xmldsig-core/#sec-X509Data) instead of KeyValue. You provided the X509 cert, it would be nice to serialze it as X509. |
I made it sign, but: |
Digest is a little bigger problem as it is in XMLSignatureProcessor and just uses a Hash. So a little hidden or on the other hand all signers need to be overriden. |
Maybe a signer can get a parameter Hasher that would expose digest method. By default it would be implemented with Hash, but if overriden it would calculate digest in our case on the card. |
This is how X509Data could be made (it is written with python-pkcs11 and OpenSSL, but...):
It has a special feature to not just add a cert to verify, but also a chain if it is present on the card. Also there is an assumption to have a cert with the same label as private key. |
This signs and verifies an EC key (a lot of patchwork, sorry):
|
As mentioned before it would be nice to have just X509Data for certificate information. My case at the end needed |
Here is a prototype:
I took @Brandhor example, but I hope I did not break something. save it to file named
In EC key case it needs |
If signxml can write just signing certificate without public key (KeyValue) it also cleans up the interface and removes the need to get public key as it is a subset of certificate. I found out that this happens when you add
There is still room for improvement, but it is a start. Thank you @Brandhor for the kick in the right direction. Using EC key woken some bugs that are getting fixed and maybe there will be a solution in the future. |
RSA names from the card are special, so I made a translation to produce currect mechanism. Translation equals what is in the @Brandhor example.
|
References:
The text was updated successfully, but these errors were encountered: