How to sign using a hardware token?
Hello, is there some guide on how to use signxml using a hardware token?
-- I'm not sure if this is a feature request or just something I didn't find in the documentation -- I did spend a good time looking for it, but couldn't find it.
As a note, I actually can get to sign with the PyKCS11 library, but it doesn't really provide the other parts expected from XAdES (so, not sure if some integration would be welcome if it's something not supported).
Hello - there is no guide, because I've never heard of such a need before.
Can you provide some more details? What specifically are you trying to accomplish? What is the API for your token?
Well, my use case is signing a XML but using a certificate stored in hardware token (SafeNet eToken 5110 in my case).
I searched a bit more and ended up using a java library already (xades4j), so, it's not a huge priority for me.
Having it In Python would be nicer though (as all my other code to manage things is in Python).
I have some code which works for me to sign it (pasted below using the PyKCS11, which I know works when using my token here), but it lacks the support to save in the xades format.
# This is an experiment. It can sign, but doesn't make the correct
# format afterwards...
from PyKCS11 import *
from lxml import etree
import keyring
def sign(xml_file_path:str):
# Define the XML file path
# Load the XML file
try:
with open(xml_file_path, 'rb') as xml_file:
xml_data = xml_file.read()
xml_root = etree.fromstring(xml_data)
except IOError:
print("Error: Could not read the XML file.")
exit(1)
except etree.XMLSyntaxError:
print("Error: Invalid XML format.")
exit(1)
# Define PKCS#11 library path and the slot where the token is present
# pkcs11_lib_path = '/path/to/your/pkcs11/library.so'
if sys.platform == 'win32':
pkcs11_lib_path = 'c:\\windows\\system32\\eTpkcs11.dll'
else:
pkcs11_lib_path = '/usr/local/lib/libeTPKcs11.dylib'
token_slot = 0 # Change this to the appropriate slot number
# Initialize the PKCS#11 library
pkcs11 = PyKCS11Lib()
pkcs11.load(pkcs11_lib_path)
# Find available slots and print their information
slots = pkcs11.getSlotList()
if len(slots) == 0:
print("No available slots found.")
exit(1)
# Ensure the specified slot is valid
if token_slot >= len(slots):
print("Error: Invalid slot number.")
exit(1)
# Open session with the token
session = pkcs11.openSession(slots[token_slot])
# Could specify pin, but let's let it ask for it.
pin = ''
# Login to the token using the provided PIN
try:
session.login(keyring.get_password('etoken', 'username'))
except PyKCS11Error as e:
print(f"Error: Failed to login to the token. {e}")
exit(1)
# Get the private key for signing
# Replace 'your_private_key_label' with the label of your private key
private_key_label = 'your_private_key_label'
private_key = session.findObjects([
(CKA_CLASS, CKO_PRIVATE_KEY),
# (CKA_LABEL, private_key_label)
])
if len(private_key) == 0:
print("Error: Private key not found.")
exit(1)
# Get the signing mechanism
mechanism = Mechanism(CKM_SHA256_RSA_PKCS, None)
# Canonicalize and sign the XML data
try:
canonicalized_xml = etree.tostring(xml_root, method="c14n", exclusive=True, with_comments=False)
signature = session.sign(private_key[0], canonicalized_xml, mechanism)
except PyKCS11Error as e:
print(f"Error: Failed to sign the XML. {e}")
session.logout()
exit(1)
# Convert the signature to base64 for inclusion in the XML
import base64
signature_base64 = base64.b64encode(bytes(signature)).decode('utf-8')
# Embed the signature into the XML
signature_element = etree.Element('Signature')
signature_value_element = etree.SubElement(signature_element, 'SignatureValue')
signature_value_element.text = signature_base64
# Ok, we have signed it, now, we'd need to store it properly as needed by xades
# Logout and close the session
session.logout()
session.closeSession()
Thanks, yeah I think support for PKCS11 is outside the scope of this library's design. If you need specific changes to the SignXML API to make it easier to subclass for your needs and isolate the signing functionality, let me know.
Opened #237 to track PKCS11 support.
Is there currently any way to sign XML using smart card HSM and store it for example in specification like https://www.w3.org/TR/xmldsig-core/.
@ii00 as I mentioned above, I've opened a separate issue to track PKCS11 support, so as long as your smart card HSM supports PKCS11, that work will help once it is completed.
The XML Signature specification you linked does not explicitly mention smart cards or HSMs. Can you elaborate on the specific part of the spec you were referring to, and the specific application that you need this for, with an example?
@kislyuk I have to authorize our medical app to our governmental National Health Information System:
- GET request to auth endpoint. The response will contain XML as the example bellow.
<nhis:message xmlns:nhis="https://www.his.bg" schemaLocation="https://www.his.bg/api/v1/NHIS-S002.xsd">
<nhis:contents>
<nhis:challenge value="6iZcCqHy1zs1BtfQebrdQ8D_V1ZctaEKmD59QKwVOv8"/>
</nhis:contents>
</nhis:message>
-
Sign the XML response with smart card HSM. The signature follows the specification at https://www.w3.org/TR/xmldsig-core/ where there are also examples. The schematic itself is available http://www.w3.org/2000/09/xmldsig#
-
POST request to same auth endpoint the signed XML
-
Get the XML response that contains the access token which our medical app will use to make further CRUD operations to our National Health Information System.
Obviously I have to use library like PyKCS11 or python-pkcs11 to get the private key and certificate as @fabioz showed in his example.
Then use XAdESSigner() which takes data, key and cert. I can extract the cert and pass it in base64. But key parameter have to be string or RSAPrivateKey