30

In Python, ssl.wrap_socket can read certificates from files, ssl.wrap_socket require the certificate as a file path.

How can I start an SSL connection using a certificate read from string variables?

My host environment does not allow write to files, and tempfile module is not functional
I'm using Python 2.7.
I store the certificate inside MySQL and read as a string.

Edit: I gave up, this is basically require implement ssl by pure python code, this is beyond my current knowledge.

kaala
  • 536
  • 1
  • 5
  • 15
  • 1
    Surely your host environment allows you to store SSL certificates, somehow? even if they want to supply them themselves. – user207421 Sep 09 '12 at 05:58
  • 1
    I saved the certificate data in mysql. my code need a ssl socket to another host to retrieve data. I read the certificate from database, but don't know how to create the ssl wrap. – kaala Sep 09 '12 at 12:31
  • Looking at the source, ssl.wrap_socket calls directly into the native code (openssl) function SSL_CTX_use_cert_chain_file which requires a path to a file, so what you are trying to do is not possible. You need to write the cert to a file for this to work. – cnelson Dec 16 '12 at 21:52
  • You'd need to write a **temporal file**, **pass it to OpenSSL** and **remove** it in the **smallest possible time**, to minimize the security risk. `os.tmpnam()` will return a random filepath. – Alba Mendez Dec 21 '12 at 17:20
  • 1
    Perhaps it is practical to enter all possible CA certificates to a static file, direct SSL to use that file and check what CA was verified against after the connection is established? – Dima Tisnek Mar 13 '14 at 12:49
  • 1
    I have similar issue when I was developing backend for ios application. I didn't try yet - but maybe you can switch to pyOpenSSL instead of native ssl. For my case I would try this library https://pypi.python.org/pypi/apns-client/0.2.1 instead of this https://pypi.python.org/pypi/apns/2.0.1 – alexche8 Nov 11 '15 at 11:24

5 Answers5

30

Looking at the source, ssl.wrap_socket calls directly into the native code (openssl) function SSL_CTX_use_cert_chain_file which requires a path to a file, so what you are trying to do is not possible.

For reference:

In ssl/init.py we see:

def wrap_socket(sock, keyfile=None, certfile=None,
                server_side=False, cert_reqs=CERT_NONE,
                ssl_version=PROTOCOL_SSLv23, ca_certs=None,
                do_handshake_on_connect=True):

    return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
                   server_side=server_side, cert_reqs=cert_reqs,
                   ssl_version=ssl_version, ca_certs=ca_certs,
                   do_handshake_on_connect=do_handshake_on_connect)

Points us to the SSLSocket constructor (which is in the same file) and we see the following happen:

self._sslobj = _ssl2.sslwrap(self._sock, server_side,
                                     keyfile, certfile,
                                     cert_reqs, ssl_version, ca_certs)

_ssl2 is implemented in C (_ssl2.c)

Looking at the sslwrap function, we see it's creating a new object:

    return (PyObject *) newPySSLObject(Sock, key_file, cert_file,
                                       server_side, verification_mode,
                                       protocol, cacerts_file);

Looking at the constructor for that object, we eventually see:

            ret = SSL_CTX_use_certificate_chain_file(self->ctx,
                                                     cert_file);

That function is defined in openssl, so now we need to switch to that codebase.

In ssl/ssl_rsa.c we eventually find in the function:

BIO_read_filename(in,file) 

If you dig far enough into the BIO code (part of openssl) you'll eventually come to a normal fopen():

fp=fopen(ptr,p);

So it looks like as it's currently written. It must be in a file openable by C's fopen().

Also, since python's ssl library so quickly jumps into C, I don't see a immediately obvious place to monkeypatch in a workaround either.

cnelson
  • 1,355
  • 11
  • 14
  • I have java client and python server. I prepared java keystore, extracted keys and certificate from it as pem files and specified them for `wrap_socket()` in python server. Now I am able to read key and certificate strings directly from keystore using `pyjks` as a string. Cant I use those key and certificate strings for preparing ssl socket in some way? – Mahesha999 Jun 20 '18 at 13:18
  • @domenukk Can you please confirm if it is not really possible to do what I have explained in above comment? – Mahesha999 Jun 26 '18 at 07:17
  • 1
    Yes I can confirm it's not possible. You will need to store them to disk or some sort of tmpfs or pseudo filesystem. – domenukk Jun 27 '18 at 01:32
  • dima's suggestion below about using a pure-python crypto library like M2 is probably the best solution to this issue. – cnelson Jul 13 '18 at 18:07
  • That's a little unfortunate since so many libraries depends on SSLContext, not socket as their input. If SSLContext can't take a string, if means fixes must be made in many codebases. Sad trombone. – user48956 Aug 12 '22 at 18:30
8

From Python 3.4, you can use SSLContext#load_verify_locations:

context = ssl.SSLContext()
context.load_verify_locations(cadata=cert)  # Where cert is str.

From https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations

judepereira
  • 1,239
  • 2
  • 17
  • 24
  • 1
    This also appears to work in Python 2.7: https://docs.python.org/2/library/ssl.html#ssl.create_default_context – Marc Liyanage Oct 24 '18 at 04:54
  • 1
    Shouldn't this be the answer to this question? – R. Navega Dec 10 '22 at 03:01
  • @R.Navega No. This is for the CA trust store, not for a server or client cert and private key as the questions asks for. – Gary van der Merwe Jun 05 '23 at 14:00
  • @GaryvanderMerwe I've reread the question and I still think it's asking about "opening an SSL socket using a (single) certificate object stored in a string variable". I could not find a mention for a cert and private key, and if I'm not mistaken can't they both be in the same certificate file? As the docs say, `an ASCII string of one or more PEM-encoded certificates` – R. Navega Jun 06 '23 at 20:09
5

Quick look though the ssl module source confirms what you want is not supported by the API: http://code.google.com/codesearch#2T6lfGELm_A/trunk/Modules/_ssl.c&q=sslwrap&type=cs

Which is not to say it is impossible, you could create a named pipe, feed one end from Python and give the filename to the ssl module.

For simpler, less secure use, dump cert from memory to mkstemp()'d file.

You could even mount FUSE volume and intercept file callback.

Finally, use ctypes to hack at ssl context at runtime and load cert/ket from a buffer following the C recipe Read certificate files from memory instead of a file using OpenSSL Things like these have been done before, but it's not for the faintest of heart.

It looks like you are trying to get out of e.g. app engine "jail," perhaps it is just not possible?

If you are not picky on ssl implementation, you can use M2Crypto, TLS Lite, pyOpenSSL or something else. The earlier is pure python, you can definitely hack it to use in-memory certificates/keys.

Community
  • 1
  • 1
Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120
  • The named pipe is basically a temp file, isn't it? Very good ideas though. I think using M2Crypto or something similar will be the best approach then. – domenukk Dec 20 '12 at 01:37
  • yes creating named pipe requires write permission on filesystem – Dima Tisnek Dec 20 '12 at 09:52
  • Even after having hooked into `load_verify_locations` of `ssl._ssl._SSLContext` using ctypes hackery, all that function gets is a `ssl.SSLContext` object and it's a mystery to me how to use that to access the right memory location that was allocated for the underlying `PySSLContext` parent class, so that I can retrieve the pointer to its `ctx` member of type `SL_CTX *`. Does anybody have any (literal) pointers? – josch Oct 16 '17 at 00:44
  • In case anybody else is interested in how to pull this off, I made this problem into a separate question: https://stackoverflow.com/questions/46762019 – josch Oct 16 '17 at 06:59
1

Python 3.4 added support for a cdata parameter to load certificates from a string. From https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations:

"The cadata object, if present, is either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates. Like with capath extra lines around PEM-encoded certificates are ignored but at least one certificate must be present".

zalmane
  • 336
  • 3
  • 5
-3

You can treat strings like files with StringIO.

Sean W.
  • 4,944
  • 8
  • 40
  • 66
  • 1
    StringIO is not working under ssl.wrap_socket, that require a string as file path. Exception: TypeError('must be string or None, not instance',) Traceback: Traceback (most recent call last): File "/data1/www/htdocs/705/pty/1/pty.py", line 23, in connect self.push.connect(self.gateway) File "/usr/local/sae/python/lib/python2.7/ssl.py", line 331, in connect self._real_connect(addr, False) File "/usr/local/sae/python/lib/python2.7/ssl.py", line 314, in _real_connect self.ca_certs, self.ciphers) TypeError: must be string or None, not instance – kaala Sep 10 '12 at 14:16
  • 1
    So it takes a file path and not a file object – Sean W. Sep 10 '12 at 16:11