Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access a page that require Safenet USB Token from urllib2 ot httplib

Tags:

python

When I have a software certificate I do like this.

import httplib

CLIENT_CERT_FILE = '/path/to/certificate.pem'  
connection = httplib.HTTPSConnection('url-to-open', cert_file=CLIENT_CERT_FILE)
connection.request('GET', '/')
response = connection.getresponse()
print response.status
data = response.read()
print data

How can I do the same with a Safenet USB Token ?

like image 993
gambuzzi Avatar asked Mar 26 '16 09:03

gambuzzi


1 Answers

TL;DR there are significant caveats and security issues with doing this in Python. A working "solution" involves using a PKCS#11 library to read the certificate from the key, then somehow persisting the certificate on the disk, and finally passing the resulting file path to the request object.

There will also be differences with each security stick's particularities. Some sticks do not offer to store a certificate along with its private key (aka a .pfx or .p12 file) which will essentially make this solution unworkable. I didn't have access to a Safenet stick, so used my own, please bear this in mind.

A solution for this requires quite a bit of work. Your use of a security dongle means that your client certificates are located onto the dongle itself. So, in order to achieve the same level of functionality, you need to write code to extract the certificate from there and feed it to your request object.

1. HTTPS-capable libraries in Python

Your requirement of using httplib (http.client for python 3.x) or urllib introduces a big caveat that the certificate used in the request has to be a file on the disk (and the same can be said of all libraries building in top of them, e.g. requests). See cnelson's answer to How to open ssl socket using certificate stored in string variables in python for the reason (in short: it's because python's ssl library makes use of a native C library which does not offer passing in-memory objects as the certificate). Also see the next answer from Dima Tisnek detailing possible workarounds with varying degrees of hackmanship.

If writing your certificate (even temporarily) on the disk is a non-starter for you, as it may very well be since you use a security stick, then it's not starting off looking good.

2. Getting the certificate from the security stick

Your biggest challenge is to get your hand on the certificate, which is currently nestled inside the security stick. Safenet sticks, like many others, are at the core a PKCS#11 capable SmartCard. I suggest you familiarise yourself with the concepts, but in essence, SmartCard is a standardised chip design, and PKCS#11 is a standardised protocol to interface with it. "Standardised" comes with caveats of course since many vendors come up with their own implementations, but it could probably be standardised enough for your purpose. The trick here will be to use available PKCS#11 interfaces on the stick to extract the certificate's attributes. This is what web browsers essentially do when using the stick to authenticate on websites using the stored certificate, so you need to have your python program do a similar thing.

2.1 Selecting a PKCS#11 library

Unfortunately, there are only a few libraries that come up when searching for "python pkcs11". I have no vested interest in either of them, and there may exist other less prominent ones.

python-pkcs11 (pypi, github, reference) offers a "high level, pythonic implementation of PKCS#11". It may be easier to use overall, but may lack compatibility and/or features depending on what you want to do, however I suspect simply retrieving certificates may be alright.

PyKCS11 (pypi, github, reference) on the other hand is a wrapper around a native PKCS#11 library, to which it will defer the calls. This one is lower-level, but looks more complete, plus may have the advantage to offer using your particular vendor's implementation if relevant.

2.2 Example code

For the example, I'll be using the user-friendlier API of python-pkcs11. Please bear in mind that this code is not thoroughly tested (and has been simplified in parts) and serves as illustrating the general idea.

import pkcs11
import asn1crypto.pem
import urllib.request
import tempfile
import ssl
import os

# this is OpenSC's implementation of PKCS#11
# other security sticks may come with another implementation.
# choose the most appropriate one
lib = pkcs11.lib('/usr/lib/pkcs11/opensc-pkcs11.so')
# tokens may be identified with various names, ids...
# it's probably rare that more than one at a time would be plugged in
token = lib.get_token(token_serial='<token_serial_value>')

pem = None
with token.open() as sess:
    pkcs11_certificates = sess.get_objects(
        {
            pkcs11.Attribute.CLASS: pkcs11.ObjectClass.CERTIFICATE,
            pkcs11.Attribute.LABEL: "Cardholder certificate"
        })

    # hopefully the selector above is sufficient
    assert len(pkcs11_certificates) == 1

    pkcs11_cert = pkcs11_certificates[0]
    der_encoded_certificate = pkcs11_cert.__getitem__(pkcs11.Attribute.VALUE)
    # the ssl library expects to be given PEM armored certificates
    pem_armored_certificate = asn1crypto.pem.armor("CERTIFICATE",
        der_encoded_certificate)

# this is the ugly part: persisting the certificate on disk
# i deliberately did not go with a sophisticated solution here since it's
#   such a big caveat to have to do this...
certfile = tempfile.mkstemp()
with open(certfile[1], 'w') as certfile_handle:
    certfile_handle.write(pem_armored_certificate.decode("utf-8"))

# this will instruct the ssl library to provide the certificate
# if asked by the server.
sslctx = ssl.create_default_context()
sslctx.load_cert_chain(certfile=certfile[1])
# if your certificate does not contain the private key, find it elsewhere
# sslctx.load_cert_chain(certfile=certfile[1],
#     keyfile="/path/to/privatekey.pem",
#     password="<private_key_password_if_applicable>")

response = urllib.request.urlopen("https://ssl_website", context=sslctx)

# Cleanup and delete the "temporary" certificate from disk
os.remove(certfile[1])

3. Conclusion

I'd say that Python is not going to be the best bet for doing ssl client authentication using security sticks. The fact that most ssl libraries require the certificate to be present on the disk works directly against the benefits (and sometimes, requirements) of the use of a security stick in the first place. I'm well aware that this answer does not provide a full solution to this problem, but hopefully exposes the challenges in enough detail to make an educated decision on whether to pursue this further or to find another way.

In any case, good luck.

like image 115
korrigan Avatar answered Nov 16 '22 16:11

korrigan