Extract Public Key using pyOpenSSL from certificate or other connection information

sumpfomat picture sumpfomat · Dec 12, 2012 · Viewed 12.8k times · Source

I'm currently trying to write a python server script which should authenticate the current client based on its public key. Since I'm using twisted, the example in the twisted documenteation got me started.

While I can generate keys, connect and communicate using the example code, I have not yet found a way to get the public key of the client in a usable format. In this stackexchange question somebody extracts the public key from an OpenSSL.crypto.PKey object but cannot transform it to a readable format. Since in I have access to the PKey object of the x509 certificate in the verifyCallback method or via self.transport.getPeerCertificate() from any method of my Protocol, this would be a good way to go. The (not accepted) answer suggests to try crypto.dump_privatekey(PKey). Unfortunately, this does not really yield the expected result: While the BEGIN PRIVATE KEY and BEGIN PRIVATE KEY in the answer could be fixed by an easy text replacement function, the base64 string seems not match the public key. I've extracted the public key with openssl rsa -in client.key -pubout > client.pub as mentioned here. It does not match the result of the dump_privatekey function.

While there still is an open bug towards OpenSSL on launchpad, it is not yet fixed. It was reported 19 Month ago, and there is some recent (October 2012) activity on it, I do not have any hope of a fast fix in the repos.

Do you have any other ideas how I could get the public key in a format comparable to the client.pub file I have mentioned above? Perhaps there is a twisted or OpenSSL connection specific object which holds this information. Please note that I have to store the public key in the protocol object such that I can access it later.

Why is no Answer accepted?

M2Crypto by J.F. Sebastian

Sorry, that I had not thought of a possibility where I cannot correlate the certificate to the connection. I've added the requirement that I have to store the public key inside the protocol instance. Thus, using peerX509.as_pem() inside the postConnectionCheck function as suggested by J.F. Sebastian does not work. Furthermore, at least in version 0.21.1-2ubuntu3 of python-m2crypto I have to call peerX509.get_rsa().as_pem() to get the right public key. Using peerX509.as_pem(None) (since peerX509.as_pem() still wants a passphrase) yields excactly the same output as crypto.dump_privatekey(PKey) in PyOpenSSL. Maybe there is a bug.

Besides this, the answer showed me a possible way to write another workaround by using the following Echo protocol class:

class Echo(Protocol):
    def dataReceived(self, data):
        """As soon as any data is received, write it back."""
        if self.transport.checked and not self.pubkeyStored:
            self.pubkeyStored = True
            x509 = m2.ssl_get_peer_cert(self.transport.ssl._ptr())
            if x509 is not None:
                x509 = X509.X509(x509, 1)
                pk = x509.get_pubkey()
                self.pubkey = pk.get_rsa().as_pem()
                print pk.as_pem(None)
            print self.pubkey
        self.transport.write(data)

As you can see this uses some internal classes which I'd like to prevent. I'm hesitating submitting a small patch which would add a getCert method to the TLSProtocolWrapper class in M2Crypto.SSL.TwistedProtocolWrapper. Even if it was accepted upstream, it would break compatibility of my script with any but the most cut-of-the-edge versions of m2crypto. What would you do?

External OpenSSL call by me

Well, its an ugly workaround based on external system commands just which seems to me even worse than accessing non-public attributes.

Answer

Jessica Gadling picture Jessica Gadling · Jun 19, 2015

Some of previous answers produce (apparently?) working PEM public key files, but so far as I've tried, none of them produce the same output that the 'openssl rsa -pubout -in priv.key' does. This is pretty important to my test suite, and after poking around in the (0.15.1) PyOpenSSL code, this works well for both standard PKey objects and the public-key-only PKey objects created by the x509.get_pubkey() method:

from OpenSSL import crypto
from OpenSSL._util import lib as cryptolib


def pem_publickey(pkey):
    """ Format a public key as a PEM """
    bio = crypto._new_mem_buf()
    cryptolib.PEM_write_bio_PUBKEY(bio, pkey._pkey)
    return crypto._bio_to_string(bio)


key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
print pem_publickey(key)