Skip to content

Commit 7c3ddb5

Browse files
committed
bpo-16487: allow certificates to be specified from memory
1 parent e613e6a commit 7c3ddb5

File tree

5 files changed

+521
-77
lines changed

5 files changed

+521
-77
lines changed

Doc/library/ssl.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,13 +1270,13 @@ to speed up repeated connections from the same clients.
12701270
.. method:: SSLContext.load_cert_chain(certfile, keyfile=None, password=None)
12711271

12721272
Load a private key and the corresponding certificate. The *certfile*
1273-
string must be the path to a single file in PEM format containing the
1273+
string must be the path to a single file or a file object containing the
12741274
certificate as well as any number of CA certificates needed to establish
1275-
the certificate's authenticity. The *keyfile* string, if present, must
1276-
point to a file containing the private key in. Otherwise the private
1277-
key will be taken from *certfile* as well. See the discussion of
1278-
:ref:`ssl-certificates` for more information on how the certificate
1279-
is stored in the *certfile*.
1275+
the certificate's authenticity, in the PEM format. The *keyfile* string, if
1276+
present, must be the path to a file or a file object containing the private
1277+
key in PEM format. Otherwise the private key will be taken from *certfile*
1278+
as well. See the discussion of :ref:`ssl-certificates` for more information
1279+
on how the certificate is stored in *certfile*.
12801280

12811281
The *password* argument may be a function to call to get the password for
12821282
decrypting the private key. It will only be called if the private key is
@@ -1287,6 +1287,10 @@ to speed up repeated connections from the same clients.
12871287
as the *password* argument. It will be ignored if the private key is not
12881288
encrypted and no password is needed.
12891289

1290+
If *certfile* is provided as a file path, *keyfile* (if given) must be
1291+
provided as a file path as well (mixing file path and file object for these
1292+
arguments is not allowed).
1293+
12901294
If the *password* argument is not specified and a password is required,
12911295
OpenSSL's built-in password prompting mechanism will be used to
12921296
interactively prompt the user for a password.

Lib/ssl.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
import re
9696
import sys
9797
import os
98+
import io
9899
from collections import namedtuple
99100
from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag
100101

@@ -450,6 +451,51 @@ def load_default_certs(self, purpose=Purpose.SERVER_AUTH):
450451
self._load_windows_store_certs(storename, purpose)
451452
self.set_default_verify_paths()
452453

454+
def load_cert_chain(self, certfile, keyfile=None, password=None):
455+
# If `certfile` is bytes or string, treat it as file path.
456+
if isinstance(certfile, str) or isinstance(certfile, bytes):
457+
certfile_path = certfile
458+
459+
# If no `keyfile` is given, read private key from `certfile`.
460+
if keyfile is None:
461+
keyfile_path = certfile_path
462+
else:
463+
# If `certfile` is bytes or string, expect `keyfile` to be
464+
# a bytes or string file path, too.
465+
keyfile_path = keyfile
466+
467+
# Pre CPython 3.7 behavior: let OpenSSL consume the files via
468+
# SSL_CTX_use_certificate_chain_file().
469+
return self._load_cert_chain_pem_from_file_paths(
470+
certfile_path, keyfile_path, password)
471+
472+
# Expect `certfile` to be a file object, expect `keyfile` to be `None`
473+
# or a file object. Read file(s) and prepare OpenSSL memory BIO
474+
# objects. If file objects return text, encode it to bytes.
475+
476+
certdata = certfile.read()
477+
if isinstance(certdata, str):
478+
certdata = certdata.encode('utf-8')
479+
480+
if keyfile is not None:
481+
keydata = keyfile.read()
482+
if isinstance(keydata, str):
483+
keydata = keydata.encode('utf-8')
484+
else:
485+
# Expect that `certdata` contains the private key, too.
486+
keydata = certdata
487+
488+
certbio = MemoryBIO()
489+
certbio.write(certdata)
490+
certbio.write_eof()
491+
492+
keybio = MemoryBIO()
493+
keybio.write(keydata)
494+
keybio.write_eof()
495+
496+
return self._load_cert_chain_pem_from_bio(certbio, keybio, password)
497+
498+
453499
@property
454500
def options(self):
455501
return Options(super().options)

0 commit comments

Comments
 (0)