Skip to content

gh-60691: allow certificates to be specified from memory #2449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1309,13 +1309,13 @@ to speed up repeated connections from the same clients.
.. method:: SSLContext.load_cert_chain(certfile, keyfile=None, password=None)

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

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

If *certfile* is provided as a file path, *keyfile* (if given) must be
provided as a file path as well (mixing file path and file object for these
arguments is not allowed).

If the *password* argument is not specified and a password is required,
OpenSSL's built-in password prompting mechanism will be used to
interactively prompt the user for a password.
Expand Down
46 changes: 46 additions & 0 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
import re
import sys
import os
import io
from collections import namedtuple
from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag

Expand Down Expand Up @@ -461,6 +462,51 @@ def load_default_certs(self, purpose=Purpose.SERVER_AUTH):
self._load_windows_store_certs(storename, purpose)
self.set_default_verify_paths()

def load_cert_chain(self, certfile, keyfile=None, password=None):
# If `certfile` is bytes or string, treat it as file path.
if isinstance(certfile, str) or isinstance(certfile, bytes):
certfile_path = certfile

# If no `keyfile` is given, read private key from `certfile`.
if keyfile is None:
keyfile_path = certfile_path
else:
# If `certfile` is bytes or string, expect `keyfile` to be
# a bytes or string file path, too.
keyfile_path = keyfile

# Pre CPython 3.7 behavior: let OpenSSL consume the files via
# SSL_CTX_use_certificate_chain_file().
return self._load_cert_chain_pem_from_file_paths(
certfile_path, keyfile_path, password)

# Expect `certfile` to be a file object, expect `keyfile` to be `None`
# or a file object. Read file(s) and prepare OpenSSL memory BIO
# objects. If file objects return text, encode it to bytes.

certdata = certfile.read()
if isinstance(certdata, str):
certdata = certdata.encode('utf-8')

if keyfile is not None:
keydata = keyfile.read()
if isinstance(keydata, str):
keydata = keydata.encode('utf-8')
else:
# Expect that `certdata` contains the private key, too.
keydata = certdata

certbio = MemoryBIO()
certbio.write(certdata)
certbio.write_eof()

keybio = MemoryBIO()
keybio.write(keydata)
keybio.write_eof()

return self._load_cert_chain_pem_from_bio(certbio, keybio, password)


@property
def options(self):
return Options(super().options)
Expand Down
Loading