SSL for Python (M2Crypto) on Windows

M2Crypto is the most versatile and popular SSL library for Python. Naturally, it takes a predictable amount of burden getting it to work under Windows.

If you’re lucky, you can find a precompiled binary online, and circumvent the heartache. Though many pages have come and gone, here is one that works, courtesy of the grr project: M2Crypto.

Not only do they provide a [non-trivial] set of instructions on how to build the binaries yourself, but they present binaries, as well. Though the binaries are hosted on Google Code (and unlikely to go away), I’ve hosted them, too, for brevity:

M2CryptoWindows

Note that these binaries, as given, are not installable Python packages. I have produced and published two such packages to PyPI, for your convenience:

M2CryptoWin32
M2CryptoWin64

Creating Those Neat OpenSSH RSA Public Keys in OpenSSL (along with DSA and ECDSA Keys)

Not too long ago, I wanted to know how to generate the public-keys that OpenSSH generates, as a transitional step to understanding how to decode them. In this fashion, I might apply the knowledge, some day, to a dynamically configurable SSH platform to allow a client to connect via SSH and to authenticate against a database, such as with Github and Bitbucket.

What I did not learn while painfully trolling and reverse-engineering all of the key-generation code for ssh-keygen was that this representation is described by PKCS8. Yes, more than a little time was unnecessarily wasted. In fact, you can generate PKCS8-formatted public keys with OpenSSL.

Needless to say, I never finished reverse-engineering the code. This wasn’t so much because it became a monster, but rather the code was unfathomably ugly. It would’ve taken an enormous amount of time to hack something together, and the same amount of time to refactor it into being something elegant. It required too much of a commitment for something without an immediate need. It would’ve been quicker (and funnier) to write a FUSE wrapper to read the database, and just point OpenSSH to the mountpoint.

Generating an RSA public-key and converting it to PKCS8:

$ openssl genrsa -out mykey.pem 2048
Generating RSA private key, 2048 bit long modulus
.................+++
.........................................................................+++
e is 65537 (0x10001)

$ openssl rsa -in mykey.pem -pubout | ssh-keygen -f /dev/stdin -i -m PKCS8
writing RSA key
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCb7G51EdPBy8Np3x84DoWB5moC/ijLqDKr7ipvajNs6rICIEgX70D5S5jPAiC7HCcubHbxAEV3rRlQJpsKyiy85vKHL787IRhmHu9ILFRm+7OxYY783QCBNP+NcZJVFWsHASxm05hOB8Z4pC0d2ql/eajqUOvk83TdiJnJA3lg+3/LCTd6mfQAKybycllBZxE8W6FLXlpsva2J1hW3bxs5FcvsoqzVjboPmbXNuC0sKk5gWPq/Rrpc7E9N8bEPt/jk/CAUtUPxFtFMe48odHvXnb4YAkt2sTKHzH9BWgn4jSjPsjwYBiG2xWk0n5RcAi2GoPHCwveD2rArY+fQW8+5

The parameters to ssh-keygen simply indicate that the data is coming from STDIN, we’re doing an import, and the key-format is PKCS8.

Also, with DSA:

$ openssl dsaparam -out dsaparam.pem 2048
Generating DSA parameters, 2048 bit long prime
This could take some time
..............+.........+.................+...........+++++++++++++++++++++++++++++++++++++++++++++++++++*
.......+....+...........+......+...+................+.....+.....................+...+...................................+..........+....+..........+.........+..+.+...........+.....+..+......+.......+.....+........+.............+.....+.....+...+..+..............+++++++++++++++++++++++++++++++++++++++++++++++++++*

$ openssl gendsa dsaparam.pem -out dsa_priv.pem
Generating DSA key, 2048 bits

$ openssl dsa -in dsa_priv.pem -pubout | ssh-keygen -f /dev/stdin -i -m PKCS8
read DSA key
writing DSA key
ssh-dss AAAAB3NzaC1kc3MAAAEBAMq+cozwNedgZotyF85hu2yTtdm2EPQhZkSR3iC1qsidkO99HTt4Gpx5vrkdPs13D/RSJj7EJYwvsWSOI/OgY+OsqymR7Erqpf0NGdwzHpTz8nYe2oQNZgpaxh9iTMsfCQwdfchGT4QwewVf4fAGXRZRtmTSbNZIXOJMBiI9h6qW29DvI1TH/x4iWL3j1XA6NDGbVECYU4CE3bQUt/F3wuYTcy6JZU9JzbCLwO6lIkFQmVk4HyMODwM5YKectFh38a7r18oTosnewu9bKoiXCVmOK6RoEgiz/+Sqjxgi8GZ/LeJtLJGxPZxuUmRe8HGMprNW4VmaxoRkNgVL5LoyBNkAAAAVANmGyUL5j1iOp5ABkqKJuiq1sqEXAAABADzHG6NVAzVvZ8xFHc4G2gs56/HvkmvsyKyyQE5o1sV5Yp9yZq9LDW6egrCSRi1Ny6PeBCuSBPU1piZwObX7ZDJCvMvQZf55EONuZYVYBhYvV0Cxw0aet94Qz/u9RA9h1CKl7321d8SDWw4DDNAmQ7nS+NuS6bkAV9djpzK8WFh6PHjARF4DdefbjaYIVig8iWxxu3qju1iQQta2/XZ23OJxZb8eUE+Dgeq5Qdosb508VIRPByovRL3o9RbQ2y50Zp2OXmbib94nW1CpFzo/84RQfnP3RG2LPpmeK+KBTqbctj43B+ATvuIw5vHYj67o3P1MgOHvFpGnDt/McdUo1BoAAAEACqjSYFC9Zj5w5P2uMRhj+o9Gc7z3bYC/OphTcWhjZSs2OAF8+PGjpEjQYSpHPmbLhdbPsZpf2NAnAtz1WxCRVvOFeuSl9zdp4/JeRj3SKrxk0L/r5ZPGCkx+FdWV65sSwK8usaFHyKXeges1N8nmzTegphW3xVb4qI9OazanQ5sxqr+TzDa2yRAMK/PwW8mxyaYkofyct7uHOR0CD6/WjkYHHOEDzubPo1/nh2oKGC2eQnluNP8xBAg7Z5sqm6ZBSIWmOhpaOgIDNcwYla9gJJHrMecgbHkb6HuPcpaJvd2dCZpujFfB0+2jFUgPUsgygIMfV5mjUI3Zajmgmwtqiw==

I included DSA because generating a key-pair with DSA for general PKE is a bit more obscure. I don’t know why. It is still possible, however.

Lastly, ECDSA (elliptic-curve DSA), for completeness. This is really the one that you want to be using, if given a choice. See how cute/short the public-keys are for an algorithm with considerably-more strength? A 256-bit curve is equivalent to a traditional 3072-bit RSA key.

$ openssl ecparam -genkey -name prime256v1 -noout -out prime256v1.key.pem
$ openssl ec -in prime256v1.key.pem -pubout | ssh-keygen -f /dev/stdin -i -m PKCS8
read EC key
writing EC key
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC3FrhznL2pQ/titzgnWrbznR3ve2eNEgevog/aS7SszS9Vkq0uFefavBF4M2Txc34sIQ5TPiZxYdm9uO1siXSw=

Adding Custom Data to X.509 SSL Certificates

Signed SSL certificates have a feature known as “extensions”. In order for them to be there, they must be in the CSR. Therefore, CSR’s support them too. Although X.509 certificates are not meant for a lot of data and were never meant to act as databases (rather, an identity with associated information), they act as a great solution when you need to store secured information alongside your application at a client site. Though the data is viewable, you have the following guarantees:

  • The data (including the extensions) can not be interfered with, or it’ll fault its signatures.
  • The certificate will expire at a set time (and can be renewed if need be).
  • A certificate-revocation list (CRL) can be implemented (using a CRL distribution point, or “CDP”) so that you can invalidate a certificate remotely.

As long as you don’t care about keep the data secret, this makes extensions an ideal solution to a problem like on-site client licenses, where your software needs to regularly check whether the client still has permission to operate. You can also use a CRL to disable them if they stop paying their bill.

These extensions accommodate data that goes beyond the distinguished-name (DN) fields (locality, country, organization, common-name, etc..), chain of trust, key fingerprints, the signatures that guarantee the trustworthiness of the certificate (using the signature of the CA), and the integrity of the certificate (the signature of the certificate contents). Extensions seem relatively easy to add to certificates, whether you’re creating CSRs from code or from command-line. They’re just manageably-sized strings (though it technically seems like there is no official length limit) of human-readable text.

If you own the CA, then you might also create your own extensions. In this case, you’ll refer to your extension with a unique dotted identifier called an “OID” (we’ll go into this in the ASN.1 explanation below). Libraries might have trouble if you just refer to your own extension without properly registering it with your library prior. For example, OpenSSL has the ability to register and use custom extensions, but the M2Crypto SSL library doesn’t expose the registration call, and, therefore, can’t use custom extensions.

Unsupported extensions might be skipped or omitted from the signed certificate by a CA that doesn’t recognize/support them, so beware that you’ll need to stick to the popular extensions if you can’t use your own CA. Extensions that are mandatory for you requirements can be marked as “critical”, so that signing won’t precede if any of your extensions aren’t recognized.

The extension that we’re interested in, here, is “subjectAltName”, and it is recognized/supported by all CAs. This extension can describe the “alternative subjects” (using DNS-type entries) that you might need to specify if your X.509 needs to be used with more than one common-name (more than one hostname). It can also describe email-addresses and other kinds of identity information. However, it can also store custom text.

This is an example of two “subjectAltName” extensions (multiple instances of the same extensions can be present in a certificate):

DNS:server1.yourdomain.tld, DNS:server2.yourdomain.tld
otherName:1.3.6.1.4.1.99;UTF8:This is arbitrary data.

However, due to details soon to follow, it’s very difficult to pull the extension text back out, again. In order to go further, we have to take a quick diversion into certificate structure. This isn’t necessarily required, but it is information that is obscure-enough to find that you won’t have any coping skills if you encounter issues, otherwise.

Certificate Encoding

All of the standard, human-readable, SSL documents, such as the private-key, public-key, CSR, and X.509, are encoded in a format called PEM. This is base64-encoded data with anchors (e.g. “—–BEGIN DSA PRIVATE KEY—–“) on the top and bottom.

In order to have any use, a PEM-encoded document must be converted to a DER-encoded document. This just means that it’s stripped of the anchors and newlines, and then base64-decoded. DER is a tighter subset of “BER” encoding.

ASN.1 Encoding

The DER-encoding describes an ASN.1 data structure node. ASN.1 combines a tree of data with a tree of grammar specifications, and reduces down to hierarchical sets of DER-encoded data. All nodes (called “tags”) are represented by dot-separated identifiers called OIDs (mentioned above). Usually these are officially-assigned OIDs, but you might have some custom ones if you don’t have to pass your certificates to higher authority that might have a problem with them.

In order to decode the structure, you must walk it, applying the correct specs as required. There is nothing self-descriptive within the data. This makes it fast, but useless until you have enough pre-existing knowledge to descend to the information you require.

The specification for the common grammars (like RFC 2459 for X.509) in ASN.1 is so massive that you should expect to avoid getting involved in the mechanics at all costs, and to learn how to survive with the limited number of libraries already available. In all likelihood, a need for anything outside the realm of popular usage will require a non-trivial degree of debugging.

ASN.1 has been around… for a while (about thirty years, as of this year). It’s obtuse, impossible, and not understood in great deal by very few individuals. However, it’s going to be here for a while.

Extension Decoding

The reason that extensions are tough to decode is because the encoding depends on the text that you put in the extension. Specifically, the “otherName” and “UT8” parts. OpenSSL can’t present these values when it dumps the certificate, because it just doesn’t have enough information to decode them. M2Crypto, since it uses OpenSSL, has the same problem.

Now that we’ve introduced a little of the conceptual ASN.1 structure, let’s go back to the previous subjectAltName “otherName” example:

otherName:1.3.6.1.4.1.99;UTF8:This is arbitrary data.

The following is the breakdown:

  1. “otherName”: A classification of the subjectAltName extension that indicates custom-data. This has an OID of its own in the RFC 2459 grammar.
  2. 1.3.6.1.4.1.99: The OID of your company. The first eight parts comprise the common-prefix, combined with a “private enterprise number” (PEN). You can register for your own.
  3. Custom data, prefixed with a type. The “UTF8” prefix determines the encoding of the data, but is not itself included.

I used the following calls to M2Crypto to add these extensions to the X.509:

ext = X509.new_extension(
        'subjectAltName',
        'otherName:1.3.6.1.4.1.99;UTF8:This is arbitrary data.'
    )

ext.set_critical(1)
cert.add_ext(ext)

Aside from the extension information itself, I also indicate that it’s to be considered “critical”. Signing will fail if the CA doesn’t recognize the extension, and not simply omit it. When this gets encoded, it’ll be encoded as three separate “components”:

  1. The OID for the “otherName” type.
  2. The “critical” flag.
  3. A DER-encoded sequence of the PEN and the UTF8-encoded string.

It turns out that it’s quicker to use a library that specializes in ASN.1, rather than trying to get the information from OpenSSL. After all, it’s out-of-scope as it’s colocated with cryptographical data while not being cryptographical itself.

I used pyasn1.

Decoding Our Extension

To decode the string from the previous extension:

  1. Enumerate the extensions.
  2. Decode the third component (mentioned above) using the RFC 2459 “subjectAltName” grammar.
  3. Descend to the first component of the “SubjectAltName” node: the “GeneralName” node.
  4. Descend to the first component of the “General Name” node: the “AnotherName” nerve.
  5. Match the OID against the OID we’re looking for.
  6. Decode the string using the RFC 2459 UTF8 specification.

This is a dump of the structure using pyasn1:

SubjectAltName().
   setComponentByPosition(
       0, 
       GeneralName().
           setComponentByPosition(
               0, 
               AnotherName().
                   setComponentByPosition(
                       0, 
                       ObjectIdentifier(1.3.6.1.5.5.7.1.99)
                   ).
                   setComponentByPosition(
                       1, 
                       Any(hexValue='0309006465616462656566')
                   )
           )
   )

The process might seem easy, but this took some work (and collaboration) to get right, with the primary difficulty coming from obscurity meeting unfamiliarity. However, the process should be somewhat set in stone, every time.

This is the corresponding code. “cert” is an M2Crypto X.509 certificate:

cert, rest = decode(cert.as_der(), asn1Spec=rfc2459.Certificate())

extensions = cert['tbsCertificate']['extensions']
for extension in extensions:
    extension_oid = extension.getComponentByPosition(0)
    print("0 [%s]" % (repr(extension_oid)))

    critical_flag = extension.getComponentByPosition(1)
    print("1 [%s]" % (repr(critical_flag)))

    sal_raw = extension.getComponentByPosition(2)
    print("2 [%s]" % (repr(sal_raw)))

    (sal, r) = decode(sal_raw, rfc2459.SubjectAltName())
    
    gn = sal.getComponentByPosition(0)
    an = gn.getComponentByPosition(0)

    oid = an.getComponentByPosition(0)
    string = an.getComponentByPosition(1)

    print("[%s]" % (oid))

    # Decode the text.

    s, r = decode(string, rfc2459.UTF8String())

    print("Decoded: [%s]" % (s))
    print('')

Wrap Up

I wanted to provide an end-to-end tutorial in adding and retrieving “otherName”-type “subjectAltName” extensions because none currently exists. It’s a good solution for keeping data safe on someone else’s assets (as long as you don’t overburden the certificate with extensions, as it’ll decrease the efficiency to verify).

Don’t forget to implement the CRL/CDP, or you won’t have the possibility of faulting the certificate (and its extensions) without having to wait for them to expire.

Parsing P12 Certificates from Python

When it comes to working with certificates in Python, no one package has all of the answers. Without considering more advanced schemes (ECC), most of the key and certificate functionality will be in one of the following packages:

In general, ssl can handle SSL sockets and HTTPS connections, M2Crypto can handle RSA/DSA keys and certificates, and pyopenssl can handle P12 certificates. There is some role overlap:

  • pyopenssl and M2Crypto both do X509 certificate deconstruction
  • ssl does PEM/DER conversions

Since the reason that I’m doing this post is because of the obscureness of reading P12 certificates in Python, here’s an example of doing so:

from OpenSSL.crypto import load_pkcs12, FILETYPE_PEM, FILETYPE_ASN1

with open('cert.p12', 'rb') as f:
  c = f.read()

p = load_pkcs12(c, 'passphrase')

certificate = p.get_certificate()
private_key = p.get_privatekey()

# Where type is FILETYPE_PEM or FILETYPE_ASN1 (for DER).
type_ = FILETYPE_PEM

OpenSSL.crypto.dump_privatekey(type_, private_key)
OpenSSL.crypto.dump_certificate(type_, certificate)

# Get CSR fields (as a list of 2-tuples).
fields = certificate.get_subject().get_components()
print(fields)

Listing SSL Certificates for a Hostname

If you’ve configured new SSL certificates on a server, you might want to watch for them to take effect and/or verify that they’re configured correctly. You can use an OpenSSL tool to do this (there is supposed to be an underscore: “s_client”):

openssl s_client -connect <hostname>:443 -servername <hostname>

This will print a barrage of information. For the purpose indicated above, you might only be interested in the very top portion:

$ openssl s_client -connect www.google.com:443 -servername www.google.com
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---

Under “Certificate chain”, there will be a list of all the certificates you’ve configured for the hostname. For example, if you configure one certificate (for a well-known authority) there will be one item, or if you configure a regular and an IA certificate (for lesser known authorities), there will be two.

The item (0) applies to your hostname, and each subsequently higher certificate represents a higher certificate authority. The last line of each item matches the first of the next item.