Skip to content
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

How to sign using a hardware token? #235

Open
fabioz opened this issue Dec 20, 2023 · 7 comments
Open

How to sign using a hardware token? #235

fabioz opened this issue Dec 20, 2023 · 7 comments

Comments

@fabioz
Copy link

fabioz commented Dec 20, 2023

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).

@kislyuk
Copy link
Member

kislyuk commented Dec 21, 2023

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?

@fabioz
Copy link
Author

fabioz commented Dec 21, 2023

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

@kislyuk
Copy link
Member

kislyuk commented Dec 24, 2023

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.

@kislyuk kislyuk closed this as completed Dec 24, 2023
@kislyuk
Copy link
Member

kislyuk commented Jan 11, 2024

Opened #237 to track PKCS11 support.

@ii00
Copy link

ii00 commented Jan 18, 2024

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/.

@kislyuk
Copy link
Member

kislyuk commented Jan 18, 2024

@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 kislyuk reopened this Jan 18, 2024
@ii00
Copy link

ii00 commented Jan 21, 2024

@kislyuk I have to authorize our medical app to our governmental National Health Information System:

  1. 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>
  1. 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#

  2. POST request to same auth endpoint the signed XML

  3. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants