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.
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?
Well, its an ugly workaround based on external system commands just which seems to me even worse than accessing non-public attributes.
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)