<div dir="auto">Nice. What's needed to move this forward?</div><div class="gmail_extra"><br><div class="gmail_quote">On 2 Feb. 2017 11:38 pm, "Cory Benfield" <<a href="mailto:cory@lukasa.co.uk">cory@lukasa.co.uk</a>> wrote:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">All,<br>
<br>
A recent change in the Fastly policy has led the distutils-sig to discover that there is a very real risk of many macOS installations of Python being entirely unable to access PyPI using pip, due to the fact that they are linked against the ancient system OpenSSL (see [1]). This problem ultimately stems from the fact that Python has never exposed the ability to use TLS backends other than OpenSSL in the standard library. As Christian and I discussed back at PyCon US 2015, the ssl module would more properly be called the openssl module, due to exposing many OpenSSL-specific concepts and behaviours in its API. This has meant that the Python ecosystem is overwhelmingly also an OpenSSL ecosystem, which is problematic on Windows and macOS (and Unices that for whatever reason aren’t interested in shipping an OpenSSL), as it has meant that Python needs to bring its own OpenSSL, and that it is troublesome to interact with the system trust database.<br>
<br>
The first step to resolving this would be to provide a new module that exposes TLS concepts in a much more generic manner. There are two possible approaches to this. The first is to have this module be a generic concrete implementation that can be compiled against multiple TLS backends (like curl). This would require that all the relevant bindings be built into the standard library from the get-go, which provides a substantial maintenance burden on a team of people that are already understaffed maintaining the ssl module alone. The second approach is to define a generic high-level TLS interface that provides a minimal usable interface that can be implemented by both first- and third-party modules. This would allow the standard library to continue to ship with only exactly what it needs (for example, OpenSSL, SecureTransport and SChannel), but would give those who want to use more esoteric TLS choices (NSS, GnuTLS, mbedTLS, and BoringSSL are some examples) an API that they can implement that will guarantee that complying modules can use the appropriate TLS backend.<br>
<br>
To that end, I’ve composed a draft PEP that would define this API. The current copy can be found on GitHub[2] and is also provided below. This draft PEP has been under discussion in the Python Security-SIG since the start of January, and has gone through a number of revisions. This is the next step in moving this forward: exposing the wider Python development community to the PEP for further revision before it is proposed to python-dev.<br>
<br>
Please note that this proposal is not intended to resolve the current problem pip has with TLSv1.2, and so is not subject to the tight time constraints associated with that problem. That problem will be solved by building a temporary shim into urllib3 that can use SecureTransport bindings on the Mac. This proposal is intended as a solution to the more-general problem of supporting different TLS backends in Python, and so is larger in scope and less urgent.<br>
<br>
I should also mention that there will be a tendency to want to make this API all things to all people from the get-go. I’m going to strongly resist attempts to extend this API too much, because each additional bit of API surface makes it harder for us to encourage module authors to conform to this API. I will encourage people to extend this API over time as needed, but to begin with I think it is most important that basic TLS clients and servers can be constructed with this API. More specialised features should be considered future enhancements, rather than being tacked on to this initial PEP.<br>
<br>
Please let me know what you think.<br>
<br>
Cory<br>
<br>
[1]: <a href="https://mail.python.org/pipermail/distutils-sig/2017-January/029970.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/distutils-sig/2017-<wbr>January/029970.html</a><br>
[2]: <a href="https://github.com/Lukasa/peps/pull/1" rel="noreferrer" target="_blank">https://github.com/Lukasa/<wbr>peps/pull/1</a><br>
<br>
—<br>
<br>
PEP: XXX<br>
Title: TLS Abstract Base Classes<br>
Version: $Revision$<br>
Last-Modified: $Date$<br>
Author: Cory Benfield <<a href="mailto:cory@lukasa.co.uk">cory@lukasa.co.uk</a>><br>
Status: Draft<br>
Type: Standards Track<br>
Content-Type: text/x-rst<br>
Created: 17-Oct-2016<br>
Python-Version: 3.7<br>
Post-History: 30-Aug-2002<br>
<br>
<br>
Abstract<br>
========<br>
<br>
This PEP would define a standard TLS interface in the form of a collection of<br>
abstract base classes. This interface would allow Python implementations and<br>
third-party libraries to provide bindings to TLS libraries other than OpenSSL<br>
that can be used by tools that expect the interface provided by the Python<br>
standard library, with the goal of reducing the dependence of the Python<br>
ecosystem on OpenSSL.<br>
<br>
<br>
Rationale<br>
=========<br>
<br>
In the 21st century it has become increasingly clear that robust and<br>
user-friendly TLS support is an extremely important part of the ecosystem of<br>
any popular programming language. For most of its lifetime, this role in the<br>
Python ecosystem has primarily been served by the `ssl module`_, which provides<br>
a Python API to the `OpenSSL library`_.<br>
<br>
Because the ``ssl`` module is distributed with the Python standard library, it<br>
has become the overwhelmingly most-popular method for handling TLS in Python.<br>
An extraordinary majority of Python libraries, both in the standard library and<br>
on the Python Package Index, rely on the ``ssl`` module for their TLS<br>
connectivity.<br>
<br>
Unfortunately, the preeminence of the ``ssl`` module has had a number of<br>
unforeseen side-effects that have had the effect of tying the entire Python<br>
ecosystem tightly to OpenSSL. This has forced Python users to use OpenSSL even<br>
in situations where it may provide a worse user experience than alternative TLS<br>
implementations, which imposes a cognitive burden and makes it hard to provide<br>
"platform-native" experiences.<br>
<br>
<br>
Problems<br>
--------<br>
<br>
The fact that the ``ssl`` module is build into the standard library has meant<br>
that all standard-library Python networking libraries are entirely reliant on<br>
the OpenSSL that the Python implementation has been linked against. This<br>
leads to the following issues:<br>
<br>
* It is difficult to take advantage of new, higher-security TLS without<br>
recompiling Python to get a new OpenSSL. While there are third-party bindings<br>
to OpenSSL (e.g. `pyOpenSSL`_), these need to be shimmed into a format that<br>
the standard library understands, forcing projects that want to use them to<br>
maintain substantial compatibility layers.<br>
<br>
* For Windows distributions of Python, they need to be shipped with a copy of<br>
OpenSSL. This puts the CPython development team in the position of being<br>
OpenSSL redistributors, potentially needing to ship security updates to the<br>
Windows Python distributions when OpenSSL vulnerabilities are released.<br>
<br>
* For macOS distributions of Python, they need either to be shipped with a copy<br>
of OpenSSL or linked against the system OpenSSL library. Apple has formally<br>
deprecated linking against the system OpenSSL library, and even if they had<br>
not, that library version has been unsupported by upstream for nearly one<br>
year as of the time of writing. The CPython development team has started<br>
shipping newer OpenSSLs with the Python available from <a href="http://python.org" rel="noreferrer" target="_blank">python.org</a>, but this<br>
has the same problem as with Windows.<br>
<br>
* Many systems, including but not limited to Windows and macOS, do not make<br>
their system certificate stores available to OpenSSL. This forces users to<br>
either obtain their trust roots from elsewhere (e.g. `certifi`_) or to<br>
attempt to export their system trust stores in some form.<br>
<br>
Relying on `certifi`_ is less than ideal, as most system administrators do<br>
not expect to receive security-critical software updates from PyPI.<br>
Additionally, it is not easy to extend the `certifi`_ trust bundle to include<br>
custom roots, or to centrally manage trust using the `certifi`_ model.<br>
<br>
Even in situations where the system certificate stores are made available to<br>
OpenSSL in some form, the experience is still sub-standard, as OpenSSL will<br>
perform different validation checks than the platform-native TLS<br>
implementation. This can lead to users experiencing different behaviour on<br>
their browsers or other platform-native tools than they experience in Python,<br>
with little or no recourse to resolve the problem.<br>
<br>
* Users may wish to integrate with TLS libraries other than OpenSSL for many<br>
other reasons, such as OpenSSL missing features (e.g. TLS 1.3 support), or<br>
because OpenSSL is simply too large and unweildy for the platform (e.g. for<br>
embedded Python). Those users are left with the requirement to use<br>
third-party networking libraries that can interact with their preferred TLS<br>
library or to shim their preferred library into the OpenSSL-specific ``ssl``<br>
module API.<br>
<br>
Additionally, the ``ssl`` module as implemented today limits the ability of<br>
CPython itself to add support for alternative TLS backends, or remove OpenSSL<br>
support entirely, should either of these become necessary or useful. The<br>
``ssl`` module exposes too many OpenSSL-specific function calls and features to<br>
easily map to an alternative TLS backend.<br>
<br>
<br>
Proposal<br>
========<br>
<br>
This PEP proposes to introduce a few new Abstract Base Classes in Python 3.7 to<br>
provide TLS functionality that is not so strongly tied to OpenSSL. It also<br>
proposes to update standard library modules to use only the interface exposed<br>
by these abstract base classes wherever possible. There are three goals here:<br>
<br>
1. To provide a common API surface for both core and third-party developers to<br>
target their TLS implementations to. This allows TLS developers to provide<br>
interfaces that can be used by most Python code, and allows network<br>
developers to have an interface that they can target that will work with a<br>
wide range of TLS implementations.<br>
2. To provide an API that has few or no OpenSSL-specific concepts leak through.<br>
The ``ssl`` module today has a number of warts caused by leaking OpenSSL<br>
concepts through to the API: the new ABCs would remove those specific<br>
concepts.<br>
3. To provide a path for the core development team to make OpenSSL one of many<br>
possible TLS backends, rather than requiring that it be present on a system<br>
in order for Python to have TLS support.<br>
<br>
The proposed interface is laid out below.<br>
<br>
<br>
Abstract Base Classes<br>
---------------------<br>
<br>
There are several interfaces that require standardisation. Those interfaces<br>
are:<br>
<br>
1. Configuring TLS, currently implemented by the `SSLContext`_ class in the<br>
``ssl`` module.<br>
2. Wrapping a socket object, currently implemented by the `SSLSocket`_ class<br>
in the ``ssl`` module.<br>
3. Providing an in-memory buffer for doing in-memory encryption or decryption<br>
with no actual I/O (necessary for asynchronous I/O models), currently<br>
implemented by the `SSLObject`_ class in the ``ssl`` module.<br>
4. Applying TLS configuration to the wrapping objects in (2) and (3). Currently<br>
this is also implemented by the `SSLContext`_ class in the ``ssl`` module.<br>
5. Specifying TLS cipher suites. There is currently no code for doing this in<br>
the standard library: instead, the standard library uses OpenSSL cipher<br>
suite strings.<br>
6. Specifying application-layer protocols that can be negotiated during the<br>
TLS handshake.<br>
7. Specifying TLS versions.<br>
8. Reporting errors to the caller, currently implemented by the `SSLError`_<br>
class in the ``ssl`` module.<br>
9. Specifying certificates to load, either as client or server certificates.<br>
10. Specifying which trust database should be used to validate certificates<br>
presented by a remote peer.<br>
11. Finding a way to get hold of these interfaces at run time.<br>
<br>
While it is technically possible to define (2) in terms of (3), for the sake of<br>
simplicity it is easier to define these as two separate ABCs. Implementations<br>
are of course free to implement the concrete subclasses however they see fit.<br>
<br>
Obviously, (5) doesn't require an abstract base class: instead, it requires a<br>
richer API for configuring supported cipher suites that can be easily updated<br>
with supported cipher suites for different implementations.<br>
<br>
(9) is a thorny problem, becuase in an ideal world the private keys associated<br>
with these certificates would never end up in-memory in the Python process<br>
(that is, the TLS library would collaborate with a Hardware Security Module<br>
(HSM) to provide the private key in such a way that it cannot be extracted from<br>
process memory). Thus, we need to provide an extensible model of providing<br>
certificates that allows concrete implementations the ability to provide this<br>
higher level of security, while also allowing a lower bar for those<br>
implementations that cannot. This lower bar would be the same as the status<br>
quo: that is, the certificate may be loaded from an in-memory buffer or from a<br>
file on disk.<br>
<br>
(10) also represents an issue because different TLS implementations vary wildly<br>
in how they allow users to select trust stores. Some implementations have<br>
specific trust store formats that only they can use (such as the OpenSSL CA<br>
directory format that is created by ``c_rehash``), and others may not allow you<br>
to specify a trust store that does not include their default trust store.<br>
<br>
For this reason, we need to provide a model that assumes very little about the<br>
form that trust stores take. The "Trust Store" section below goes into more<br>
detail about how this is achieved.<br>
<br>
Finally, this API will split the responsibilities currently assumed by the<br>
`SSLContext`_ object: specifically, the responsibility for holding and managing<br>
configuration and the responsibility for using that configuration to build<br>
wrapper objects.<br>
<br>
This is necessarily primarily for supporting functionality like Server Name<br>
Indication (SNI). In OpenSSL (and thus in the ``ssl`` module), the server has<br>
the ability to modify the TLS configuration in response to the client telling<br>
the server what hostname it is trying to reach. This is mostly used to change<br>
certificate chain so as to present the correct TLS certificate chain for the<br>
given hostname. The specific mechanism by which this is done is by returning<br>
a new `SSLContext`_ object with the appropriate configuration.<br>
<br>
This is not a model that maps well to other TLS implementations. Instead, we<br>
need to make it possible to provide a return value from the SNI callback that<br>
can be used to indicate what configuration changes should be made. This means<br>
providing an object that can hold TLS configuration. This object needs to be<br>
applied to specific TLSWrappedBuffer, and TLSWrappedSocket objects.<br>
<br>
For this reason, we split the responsibility of `SSLContext`_ into two separate<br>
objects. The ``TLSConfiguration`` object is an object that acts as container<br>
for TLS configuration: the ``ClientContext`` and ``ServerContext`` objects are<br>
objects that are instantiated with a ``TLSConfiguration`` object. Both objects<br>
would be immutable.<br>
<br>
Configuration<br>
~~~~~~~~~~~~~<br>
<br>
The ``TLSConfiguration`` concrete class defines an object that can hold and<br>
manage TLS configuration. The goals of this class are as follows:<br>
<br>
1. To provide a method of specifying TLS configuration that avoids the risk of<br>
errors in typing (this excludes the use of a simple dictionary).<br>
2. To provide an object that can be safely compared to other configuration<br>
objects to detect changes in TLS configuration, for use with the SNI<br>
callback.<br>
<br>
This class is not an ABC, primarily because it is not expected to have<br>
implementation-specific behaviour. The responsibility for transforming a<br>
``TLSConfiguration`` object into a useful set of configuration for a given TLS<br>
implementation belongs to the Context objects discussed below.<br>
<br>
This class has one other notable property: it is immutable. This is a desirable<br>
trait for a few reasons. The most important one is that it allows these objects<br>
to be used as dictionary keys, which is potentially extremely valuable for<br>
certain TLS backends and their SNI configuration. On top of this, it frees<br>
implementations from needing to worry about their configuration objects being<br>
changed under their feet, which allows them to avoid needing to carefully<br>
synchronize changes between their concrete data structures and the<br>
configuration object.<br>
<br>
This object is extendable: that is, future releases of Python may add<br>
configuration fields to this object as they become useful. For<br>
backwards-compatibility purposes, new fields are only appended to this object.<br>
Existing fields will never be removed, renamed, or reordered.<br>
<br>
The ``TLSConfiguration`` object would be defined by the following code::<br>
<br>
ServerNameCallback = Callable[[TLSBufferObject, Optional[str], TLSConfiguration], Any]<br>
<br>
<br>
_configuration_fields = [<br>
'validate_certificates',<br>
'certificate_chain',<br>
'ciphers',<br>
'inner_protocols',<br>
'lowest_supported_version',<br>
'highest_supported_version',<br>
'trust_store',<br>
'sni_callback',<br>
]<br>
<br>
<br>
_DEFAULT_VALUE = object()<br>
<br>
<br>
class TLSConfiguration(namedtuple('<wbr>TLSConfiguration', _configuration_fields)):<br>
"""<br>
An immutable TLS Configuration object. This object has the following<br>
properties:<br>
<br>
:param validate_certificates bool: Whether to validate the TLS<br>
certificates. This switch operates at a very broad scope: either<br>
validation is enabled, in which case all forms of validation are<br>
performed including hostname validation if possible, or validation<br>
is disabled, in which case no validation is performed.<br>
<br>
Not all backends support having their certificate validation<br>
disabled. If a backend does not support having their certificate<br>
validation disabled, attempting to set this property to ``False``<br>
will throw a ``TLSError`` when this object is passed into a<br>
context object.<br>
<br>
:param certificate_chain Tuple[Tuple[Certificate],<wbr>PrivateKey]: The<br>
certificate, intermediate certificate, and the corresponding<br>
private key for the leaf certificate. These certificates will be<br>
offered to the remote peer during the handshake if required.<br>
<br>
The first Certificate in the list must be the leaf certificate. All<br>
subsequent certificates will be offered as intermediate additional<br>
certificates.<br>
<br>
:param ciphers Tuple[CipherSuite]:<br>
The available ciphers for TLS connections created with this<br>
configuration, in priority order.<br>
<br>
:param inner_protocols Tuple[Union[NextProtocol, bytes]]:<br>
Protocols that connections created with this configuration should<br>
advertise as supported during the TLS handshake. These may be<br>
advertised using either or both of ALPN or NPN. This list of<br>
protocols should be ordered by preference.<br>
<br>
:param lowest_supported_version TLSVersion:<br>
The minimum version of TLS that should be allowed on TLS<br>
connections using this configuration.<br>
<br>
:param highest_supported_version TLSVersion:<br>
The maximum version of TLS that should be allowed on TLS<br>
connections using this configuration.<br>
<br>
:param trust_store TrustStore:<br>
The trust store that connections using this configuration will use<br>
to validate certificates.<br>
<br>
:param sni_callback Optional[ServerNameCallback]:<br>
A callback function that will be called after the TLS Client Hello<br>
handshake message has been received by the TLS server when the TLS<br>
client specifies a server name indication.<br>
<br>
Only one callback can be set per ``TLSConfiguration``. If the<br>
``sni_callback`` is ``None`` then the callback is disabled. If the<br>
``TLSConfiguration`` is used for a ``ClientContext`` then this<br>
setting will be ignored.<br>
<br>
The ``callback`` function will be called with three arguments: the<br>
first will be the ``TLSBufferObject`` for the connection; the<br>
second will be a string that represents the server name that the<br>
client is intending to communicate (or ``None`` if the TLS Client<br>
Hello does not contain a server name); and the third argument will<br>
be the original ``Context``. The server name argument will be the<br>
IDNA *decoded* server name.<br>
<br>
The ``callback`` must return a ``TLSConfiguration`` to allow<br>
negotiation to continue. Other return values signal errors.<br>
Attempting to control what error is signaled by the underlying TLS<br>
implementation is not specified in this API, but is up to the<br>
concrete implementation to handle.<br>
<br>
The Context will do its best to apply the ``TLSConfiguration``<br>
changes from its original configuration to the incoming connection.<br>
This will usually include changing the certificate chain, but may<br>
also include changes to allowable ciphers or any other<br>
configuration settings.<br>
"""<br>
__slots__ = ()<br>
<br>
def __new__(cls, validate_certificates=None: Optional[bool],<br>
certificate_chain=None: Optional[Tuple[Tuple[<wbr>Certificate], PrivateKey]],<br>
ciphers=None: Optional[Tuple[CipherSuite]],<br>
inner_protocols=None: Optional[Tuple[Union[<wbr>NextProtocol, bytes]]],<br>
lowest_supported_version=None: Optional[TLSVersion],<br>
highest_supported_version=<wbr>None: Optional[TLSVersion],<br>
trust_store=None: Optional[TrustStore],<br>
sni_callback=None: Optional[ServerNameCallback]):<br>
<br>
if validate_certificates is None:<br>
validate_certificates = True<br>
<br>
if ciphers is None:<br>
ciphers = DEFAULT_CIPHER_LIST<br>
<br>
if inner_protocols is None:<br>
inner_protocols = []<br>
<br>
if lowest_supported_version is None:<br>
lowest_supported_version = TLSVersion.TLSv1<br>
<br>
if highest_supported_version is None:<br>
highest_supported_version = TLSVersion.MAXIMUM_SUPPORTED<br>
<br>
return super().__new__(<br>
cls, validate_certificates, certificate_chain, ciphers,<br>
inner_protocols, lowest_supported_version,<br>
highest_supported_version, trust_store, sni_callback<br>
)<br>
<br>
def update(self, validate_certificates=_<wbr>DEFAULT_VALUE,<br>
certificate_chain=_DEFAULT_<wbr>VALUE,<br>
ciphers=_DEFAULT_VALUE,<br>
inner_protocols=_DEFAULT_<wbr>VALUE,<br>
lowest_supported_version=_<wbr>DEFAULT_VALUE,<br>
highest_supported_version=_<wbr>DEFAULT_VALUE,<br>
trust_store=_DEFAULT_VALUE,<br>
sni_callback=_DEFAULT_VALUE):<br>
"""<br>
Create a new ``TLSConfiguration``, overriding some of the settings<br>
on the original configuration with the new settings.<br>
"""<br>
if validate_certificates is _DEFAULT_VALUE:<br>
validate_certificates = self.validate_certificates<br>
<br>
if certificate_chain is _DEFAULT_VALUE:<br>
certificate_chain = self.certificate_chain<br>
<br>
if ciphers is _DEFAULT_VALUE:<br>
ciphers = self.ciphers<br>
<br>
if inner_protocols is _DEFAULT_VALUE:<br>
inner_protocols = self.inner_protocols<br>
<br>
if lowest_supported_version is _DEFAULT_VALUE:<br>
lowest_supported_version = self.lowest_supported_version<br>
<br>
if highest_supported_version is _DEFAULT_VALUE:<br>
highest_supported_version = self.highest_supported_version<br>
<br>
if trust_store is _DEFAULT_VALUE:<br>
trust_store = self.trust_store<br>
<br>
if sni_callback is _DEFAULT_VALUE:<br>
sni_callback = self.sni_callback<br>
<br>
return self.__class__(<br>
validate_certificates, certificate_chain, ciphers,<br>
inner_protocols, lowest_supported_version,<br>
highest_supported_version, trust_store, sni_callback<br>
)<br>
<br>
<br>
<br>
Context<br>
~~~~~~~<br>
<br>
We define two Context abstract base classes. These ABCs define objects that<br>
allow configuration of TLS to be applied to specific connections. They can be<br>
thought of as factories for ``TLSWrappedSocket`` and ``TLSWrappedBuffer``<br>
objects.<br>
<br>
Unlike the current ``ssl`` module, we provide two context classes instead of<br>
one. Specifically, we provide the ``ClientContext`` and ``ServerContext``<br>
classes. This simplifies the APIs (for example, there is no sense in the server<br>
providing the ``server_hostname`` parameter to ``ssl.SSLContext.wrap_socket``<wbr>,<br>
but because there is only one context class that parameter is still available),<br>
and ensures that implementations know as early as possible which side of a TLS<br>
connection they will serve. Additionally, it allows implementations to opt-out<br>
of one or either side of the connection. For example, SChannel on macOS is not<br>
really intended for server use and has an enormous amount of functionality<br>
missing for server-side use. This would allow SChannel implementations to<br>
simply not define a concrete subclass of ``ServerContext`` to signal their lack<br>
of support.<br>
<br>
As much as possible implementers should aim to make these classes immutable:<br>
that is, they should prefer not to allow users to mutate their internal state<br>
directly, instead preferring to create new contexts from new TLSConfiguration<br>
objects. Obviously, the ABCs cannot enforce this constraint, and so they do not<br>
attempt to.<br>
<br>
The ``Context`` abstract base class has the following class definition::<br>
<br>
TLSBufferObject = Union[TLSWrappedSocket, TLSWrappedBuffer]<br>
<br>
<br>
class _BaseContext(metaclass=<wbr>ABCMeta):<br>
@abstractmethod<br>
def __init__(self, configuration: TLSConfiguration):<br>
"""<br>
Create a new context object from a given TLS configuration.<br>
"""<br>
<br>
@property<br>
@abstractmethod<br>
def configuration(self) -> TLSConfiguration:<br>
"""<br>
Returns the TLS configuration that was used to create the context.<br>
"""<br>
<br>
<br>
class ClientContext(_BaseContext):<br>
@abstractmethod<br>
def wrap_socket(self,<br>
socket: socket.socket,<br>
server_hostname: Optional[str],<br>
auto_handshake=True: bool) -> TLSWrappedSocket:<br>
"""<br>
Wrap an existing Python socket object ``socket`` and return a<br>
``TLSWrappedSocket`` object. ``socket`` must be a ``SOCK_STREAM``<br>
socket: all other socket types are unsupported.<br>
<br>
The returned SSL socket is tied to the context, its settings and<br>
certificates.<br>
<br>
The parameter ``server_hostname`` specifies the hostname of the<br>
service which we are connecting to. This allows a single server to<br>
host multiple SSL-based services with distinct certificates, quite<br>
similarly to HTTP virtual hosts. This is also used to validate the<br>
TLS certificate for the given hostname. If hostname validation is<br>
not desired, then pass ``None`` for this parameter.<br>
<br>
The parameter ``auto_handshake`` specifies whether to do the SSL<br>
handshake automatically after doing a ``socket.connect()``, or<br>
whether the application program will call it explicitly, by<br>
invoking the ``TLSWrappedSocket.do_<wbr>handshake()`` method. Calling<br>
``TLSWrappedSocket.do_<wbr>handshake()`` explicitly gives the program<br>
control over the blocking behavior of the socket I/O involved in<br>
the handshake.<br>
"""<br>
<br>
@abstractmethod<br>
def wrap_buffers(self, incoming: Any, outgoing: Any,<br>
server_hostname: Optional[str]) -> TLSWrappedBuffer:<br>
"""<br>
Wrap a pair of buffer objects (``incoming`` and ``outgoing``) to<br>
create an in-memory stream for TLS. The SSL routines will read data<br>
from ``incoming`` and decrypt it, and write encrypted data to<br>
``outgoing``.<br>
<br>
The buffer objects must be either file objects or objects that<br>
implement the buffer protocol.<br>
<br>
The ``server_hostname`` parameter has the same meaning as in<br>
``wrap_socket``.<br>
"""<br>
<br>
<br>
class ServerContext(_BaseContext):<br>
@abstractmethod<br>
def wrap_socket(self, socket: socket.socket,<br>
auto_handshake=True: bool) -> TLSWrappedSocket:<br>
"""<br>
Wrap an existing Python socket object ``socket`` and return a<br>
``TLSWrappedSocket`` object. ``socket`` must be a ``SOCK_STREAM``<br>
socket: all other socket types are unsupported.<br>
<br>
The returned SSL socket is tied to the context, its settings and<br>
certificates.<br>
<br>
The parameter ``auto_handshake`` specifies whether to do the SSL<br>
handshake automatically after doing a ``socket.accept()``, or<br>
whether the application program will call it explicitly, by<br>
invoking the ``TLSWrappedSocket.do_<wbr>handshake()`` method. Calling<br>
``TLSWrappedSocket.do_<wbr>handshake()`` explicitly gives the program<br>
control over the blocking behavior of the socket I/O involved in<br>
the handshake.<br>
"""<br>
<br>
@abstractmethod<br>
def wrap_buffers(self, incoming: Any, outgoing: Any) -> TLSWrappedBuffer:<br>
"""<br>
Wrap a pair of buffer objects (``incoming`` and ``outgoing``) to<br>
create an in-memory stream for TLS. The SSL routines will read data<br>
from ``incoming`` and decrypt it, and write encrypted data to<br>
``outgoing``.<br>
<br>
The buffer objects must be either file objects or objects that<br>
implement the buffer protocol.<br>
"""<br>
<br>
<br>
Socket<br>
~~~~~~<br>
<br>
The socket-wrapper ABC will be defined by the ``TLSWrappedSocket`` ABC, which<br>
has the following definition::<br>
<br>
class TLSWrappedSocket(metaclass=<wbr>ABCMeta):<br>
# The various socket methods all must be implemented. Their definitions<br>
# have been elided from this class defintion in the PEP because they<br>
# aren't instructive.<br>
@abstractmethod<br>
def do_handshake(self) -> None:<br>
"""<br>
Performs the TLS handshake. Also performs certificate validation<br>
and hostname verification.<br>
"""<br>
<br>
@abstractmethod<br>
def cipher(self) -> Optional[CipherSuite]:<br>
"""<br>
Returns the CipherSuite entry for the cipher that has been<br>
negotiated on the connection. If no connection has been negotiated,<br>
returns ``None``.<br>
"""<br>
<br>
@abstractmethod<br>
def negotiated_protocol(self) -> Optional[Union[NextProtocol, bytes]]:<br>
"""<br>
Returns the protocol that was selected during the TLS handshake.<br>
This selection may have been made using ALPN, NPN, or some future<br>
negotiation mechanism.<br>
<br>
If the negotiated protocol is one of the protocols defined in the<br>
``NextProtocol`` enum, the value from that enum will be returned.<br>
Otherwise, the raw bytestring of the negotiated protocol will be<br>
returned.<br>
<br>
If ``Context.set_inner_protocols(<wbr>)`` was not called, if the other<br>
party does not support protocol negotiation, if this socket does<br>
not support any of the peer's proposed protocols, or if the<br>
handshake has not happened yet, ``None`` is returned.<br>
"""<br>
<br>
@property<br>
@abstractmethod<br>
def context(self) -> Context:<br>
"""<br>
The ``Context`` object this socket is tied to.<br>
"""<br>
<br>
@abstractproperty<br>
def negotiated_tls_version(self) -> Optional[TLSVersion]:<br>
"""<br>
The version of TLS that has been negotiated on this connection.<br>
"""<br>
<br>
@abstractmethod<br>
def unwrap(self) -> socket.socket:<br>
"""<br>
Cleanly terminate the TLS connection on this wrapped socket. Once<br>
called, this ``TLSWrappedSocket`` can no longer be used to transmit<br>
data. Returns the socket that was wrapped with TLS.<br>
"""<br>
<br>
<br>
Buffer<br>
~~~~~~<br>
<br>
The buffer-wrapper ABC will be defined by the ``TLSWrappedBuffer`` ABC, which<br>
has the following definition::<br>
<br>
class TLSWrappedBuffer(metaclass=<wbr>ABCMeta):<br>
@abstractmethod<br>
def read(self, amt=None: int) -> bytes:<br>
"""<br>
Read up to ``amt`` bytes of data from the input buffer and return<br>
the result as a ``bytes`` instance. If ``amt`` is ``None``, will<br>
attempt to read until either EOF is reached or until further<br>
attempts to read would raise either ``WantReadError`` or<br>
``WantWriteError``.<br>
<br>
Raise ``WantReadError`` or ``WantWriteError`` if there is<br>
insufficient data in either the input or output buffer and the<br>
operation would have caused data to be written or read.<br>
<br>
As at any time a re-negotiation is possible, a call to ``read()``<br>
can also cause write operations.<br>
"""<br>
<br>
@abstractmethod<br>
def readinto(self, buffer: Any, amt=None: int) -> int:<br>
"""<br>
Read up to ``amt`` bytes of data from the input buffer into<br>
``buffer``, which must be an object that implements the buffer<br>
protocol. Returns the number of bytes read. If ``amt`` is ``None``,<br>
will attempt to read until either EOF is reached or until further<br>
attempts to read would raise either ``WantReadError`` or<br>
``WantWriteError``, or until the buffer is full.<br>
<br>
Raises ``WantReadError`` or ``WantWriteError`` if there is<br>
insufficient data in either the input or output buffer and the<br>
operation would have caused data to be written or read.<br>
<br>
As at any time a re-negotiation is possible, a call to<br>
``readinto()`` can also cause write operations.<br>
"""<br>
<br>
@abstractmethod<br>
def write(self, buf: Any) -> int:<br>
"""<br>
Write ``buf`` in encrypted form to the output buffer and return the<br>
number of bytes written. The ``buf`` argument must be an object<br>
supporting the buffer interface.<br>
<br>
Raise ``WantReadError`` or ``WantWriteError`` if there is<br>
insufficient data in either the input or output buffer and the<br>
operation would have caused data to be written or read.<br>
<br>
As at any time a re-negotiation is possible, a call to ``write()``<br>
can also cause read operations.<br>
"""<br>
<br>
@abstractmethod<br>
def do_handshake(self) -> None:<br>
"""<br>
Performs the TLS handshake. Also performs certificate validation<br>
and hostname verification.<br>
"""<br>
<br>
@abstractmethod<br>
def cipher(self) -> Optional[CipherSuite]:<br>
"""<br>
Returns the CipherSuite entry for the cipher that has been<br>
negotiated on the connection. If no connection has been negotiated,<br>
returns ``None``.<br>
"""<br>
<br>
@abstractmethod<br>
def negotiated_protocol(self) -> Optional[Union[NextProtocol, bytes]]:<br>
"""<br>
Returns the protocol that was selected during the TLS handshake.<br>
This selection may have been made using ALPN, NPN, or some future<br>
negotiation mechanism.<br>
<br>
If the negotiated protocol is one of the protocols defined in the<br>
``NextProtocol`` enum, the value from that enum will be returned.<br>
Otherwise, the raw bytestring of the negotiated protocol will be<br>
returned.<br>
<br>
If ``Context.set_inner_protocols(<wbr>)`` was not called, if the other<br>
party does not support protocol negotiation, if this socket does<br>
not support any of the peer's proposed protocols, or if the<br>
handshake has not happened yet, ``None`` is returned.<br>
"""<br>
<br>
@property<br>
@abstractmethod<br>
def context(self) -> Context:<br>
"""<br>
The ``Context`` object this socket is tied to.<br>
"""<br>
<br>
@abstractproperty<br>
def negotiated_tls_version(self) -> Optional[TLSVersion]:<br>
"""<br>
The version of TLS that has been negotiated on this connection.<br>
"""<br>
<br>
@abstractmethod<br>
def shutdown(self) -> None:<br>
"""<br>
Performs a clean TLS shut down. This should generally be used<br>
whenever possible to signal to the remote peer that the content is<br>
finished.<br>
"""<br>
<br>
<br>
Cipher Suites<br>
~~~~~~~~~~~~~<br>
<br>
Supporting cipher suites in a truly library-agnostic fashion is a remarkably<br>
difficult undertaking. Different TLS implementations often have *radically*<br>
different APIs for specifying cipher suites, but more problematically these<br>
APIs frequently differ in capability as well as in style. Some examples are<br>
shown below:<br>
<br>
OpenSSL<br>
^^^^^^^<br>
<br>
OpenSSL uses a well-known cipher string format. This format has been adopted as<br>
a configuration language by most products that use OpenSSL, including Python.<br>
This format is relatively easy to read, but has a number of downsides: it is<br>
a string, which makes it remarkably easy to provide bad inputs; it lacks much<br>
detailed validation, meaning that it is possible to configure OpenSSL in a way<br>
that doesn't allow it to negotiate any cipher at all; and it allows specifying<br>
cipher suites in a number of different ways that make it tricky to parse. The<br>
biggest problem with this format is that there is no formal specification for<br>
it, meaning that the only way to parse a given string the way OpenSSL would is<br>
to get OpenSSL to parse it.<br>
<br>
OpenSSL's cipher strings can look like this::<br>
<br>
'ECDH+AESGCM:ECDH+CHACHA20:DH+<wbr>AESGCM:DH+CHACHA20:ECDH+<wbr>AES256:DH+AES256:ECDH+AES128:<wbr>DH+AES:RSA+AESGCM:RSA+AES:!<wbr>aNULL:!eNULL:!MD5'<br>
<br>
This string demonstrates some of the complexity of the OpenSSL format. For<br>
example, it is possible for one entry to specify multiple cipher suites: the<br>
entry ``ECDH+AESGCM`` means "all ciphers suites that include both<br>
elliptic-curve Diffie-Hellman key exchange and AES in Galois Counter Mode".<br>
More explicitly, that will expand to four cipher suites::<br>
<br>
"ECDHE-ECDSA-AES256-GCM-<wbr>SHA384:ECDHE-RSA-AES256-GCM-<wbr>SHA384:ECDHE-ECDSA-AES128-GCM-<wbr>SHA256:ECDHE-RSA-AES128-GCM-<wbr>SHA256"<br>
<br>
That makes parsing a complete OpenSSL cipher string extremely tricky. Add to<br>
the fact that there are other meta-characters, such as "!" (exclude all cipher<br>
suites that match this criterion, even if they would otherwise be included:<br>
"!MD5" means that no cipher suites using the MD5 hash algorithm should be<br>
included), "-" (exclude matching ciphers if they were already included, but<br>
allow them to be re-added later if they get included again), and "+" (include<br>
the matching ciphers, but place them at the end of the list), and you get an<br>
*extremely* complex format to parse. On top of this complexity it should be<br>
noted that the actual result depends on the OpenSSL version, as an OpenSSL<br>
cipher string is valid so long as it contains at least one cipher that OpenSSL<br>
recognises.<br>
<br>
OpenSSL also uses different names for its ciphers than the names used in the<br>
relevant specifications. See the manual page for ``ciphers(1)`` for more<br>
details.<br>
<br>
The actual API inside OpenSSL for the cipher string is simple::<br>
<br>
char *cipher_list = <some cipher list>;<br>
int rc = SSL_CTX_set_cipher_list(<wbr>context, cipher_list);<br>
<br>
This means that any format that is used by this module must be able to be<br>
converted to an OpenSSL cipher string for use with OpenSSL.<br>
<br>
SecureTransport<br>
^^^^^^^^^^^^^^^<br>
<br>
SecureTransport is the macOS system TLS library. This library is substantially<br>
more restricted than OpenSSL in many ways, as it has a much more restricted<br>
class of users. One of these substantial restrictions is in controlling<br>
supported cipher suites.<br>
<br>
Ciphers in SecureTransport are represented by a C ``enum``. This enum has one<br>
entry per cipher suite, with no aggregate entries, meaning that it is not<br>
possible to reproduce the meaning of an OpenSSL cipher string like<br>
"ECDH+AESGCM" without hand-coding which categories each enum member falls into.<br>
<br>
However, the names of most of the enum members are in line with the formal<br>
names of the cipher suites: that is, the cipher suite that OpenSSL calls<br>
"ECDHE-ECDSA-AES256-GCM-<wbr>SHA384" is called<br>
"TLS_ECDHE_ECDHSA_WITH_AES_<wbr>256_GCM_SHA384" in SecureTransport.<br>
<br>
The API for configuring cipher suites inside SecureTransport is simple::<br>
<br>
SSLCipherSuite ciphers[] = {TLS_ECDHE_ECDSA_WITH_AES_256_<wbr>GCM_SHA384, ...};<br>
OSStatus status = SSLSetEnabledCiphers(context, ciphers, sizeof(cphers));<br>
<br>
SChannel<br>
^^^^^^^^<br>
<br>
SChannel is the Windows system TLS library.<br>
<br>
SChannel has extremely restrictive support for controlling available TLS<br>
cipher suites, and additionally adopts a third method of expressing what TLS<br>
cipher suites are supported.<br>
<br>
Specifically, SChannel defines a set of ``ALG_ID`` constants (C unsigned ints).<br>
Each of these constants does not refer to an entire cipher suite, but instead<br>
an individual algorithm. Some examples are ``CALG_3DES`` and ``CALG_AES_256``,<br>
which refer to the bulk encryption algorithm used in a cipher suite,<br>
``CALG_DH_EPHEM`` and ``CALG_RSA_KEYX`` which refer to part of the key exchange<br>
algorithm used in a cipher suite, ``CALG_SHA1`` and ``CALG_MD5`` which refer to<br>
the message authentication code used in a cipher suite, and ``CALG_ECDSA`` and<br>
``CALG_RSA_SIGN`` which refer to the signing portions of the key exchange<br>
algorithm.<br>
<br>
This can be thought of as the half of OpenSSL's functionality that<br>
SecureTransport doesn't have: SecureTransport only allows specifying exact<br>
cipher suites, while SChannel only allows specifying *parts* of the cipher<br>
suite, while OpenSSL allows both.<br>
<br>
Determining which cipher suites are allowed on a given connection is done by<br>
providing a pointer to an array of these ``ALG_ID`` constants. This means that<br>
any suitable API must allow the Python code to determine which ``ALG_ID``<br>
constants must be provided.<br>
<br>
<br>
Proposed Interface<br>
^^^^^^^^^^^^^^^^^^<br>
<br>
The proposed interface for the new module is influenced by the combined set of<br>
limitations of the above implementations. Specifically, as every implementation<br>
*except* OpenSSL requires that each individual cipher be provided, there is no<br>
option but to provide that lowest-common denominator approach.<br>
<br>
The simplest approach is to provide an enumerated type that includes all of the<br>
cipher suites defined for TLS. The values of the enum members will be their<br>
two-octet cipher identifier as used in the TLS handshake, stored as a tuple of<br>
integers. The names of the enum members will be their IANA-registered cipher<br>
suite names.<br>
<br>
Rather than populate this enum by hand, it is likely that we'll define a<br>
script that can build it from Christian Heimes' `tlsdb JSON file`_ (warning:<br>
large file). This also opens up the possibility of extending the API with<br>
additional querying function, such as determining which TLS versions support<br>
which ciphers, if that functionality is found to be useful or necessary.<br>
<br>
If users find this approach to be onerous, a future extension to this API can<br>
provide helpers that can reintroduce OpenSSL's aggregation functionality.<br>
<br>
Because this enum would be enormous, the entire enum is not provided here.<br>
Instead, a small sample of entries is provided to give a flavor of how it will<br>
appear.<br>
<br>
::<br>
<br>
class CipherSuite(Enum):<br>
...<br>
TLS_ECDHE_RSA_WITH_3DES_EDE_<wbr>CBC_SHA = (0xC0, 0x12)<br>
...<br>
TLS_ECDHE_ECDSA_WITH_AES_128_<wbr>CCM = (0xC0, 0xAC)<br>
...<br>
TLS_ECDHE_ECDSA_WITH_AES_128_<wbr>GCM_SHA256 = (0xC0, 0x2B)<br>
...<br>
<br>
<br>
Protocol Negotiation<br>
~~~~~~~~~~~~~~~~~~~~<br>
<br>
Both NPN and ALPN allow for protocol negotiation as part of the HTTP/2<br>
handshake. While NPN and ALPN are, at their fundamental level, built on top of<br>
bytestrings, string-based APIs are frequently problematic as they allow for<br>
errors in typing that can be hard to detect.<br>
<br>
For this reason, this module would define a type that protocol negotiation<br>
implementations can pass and be passed. This type would wrap a bytestring to<br>
allow for aliases for well-known protocols. This allows us to avoid the<br>
problems inherent in typos for well-known protocols, while allowing the full<br>
extensibility of the protocol negotiation layer if needed by letting users pass<br>
byte strings directly.<br>
<br>
::<br>
<br>
class NextProtocol(Enum):<br>
H2 = b'h2'<br>
H2C = b'h2c'<br>
HTTP1 = b'http/1.1'<br>
WEBRTC = b'webrtc'<br>
C_WEBRTC = b'c-webrtc'<br>
FTP = b'ftp'<br>
STUN = b'stun.nat-discovery'<br>
TURN = b'stun.turn'<br>
<br>
TLS Versions<br>
~~~~~~~~~~~~<br>
<br>
It is often useful to be able to restrict the versions of TLS you're willing to<br>
support. There are many security advantages in refusing to use old versions of<br>
TLS, and some misbehaving servers will mishandle TLS clients advertising<br>
support for newer versions.<br>
<br>
The following enumerated type can be used to gate TLS versions. Forward-looking<br>
applications should almost never set a maximum TLS version unless they<br>
absolutely must, as a TLS backend that is newer than the Python that uses it<br>
may support TLS versions that are not in this enumerated type.<br>
<br>
Additionally, this enumerated type defines two additional flags that can always<br>
be used to request either the lowest or highest TLS version supported by an<br>
implementation.<br>
<br>
::<br>
<br>
class TLSVersion(Enum):<br>
MINIMUM_SUPPORTED<br>
SSLv2<br>
SSLv3<br>
TLSv1<br>
TLSv1_1<br>
TLSv1_2<br>
TLSv1_3<br>
MAXIMUM_SUPPORTED<br>
<br>
<br>
Errors<br>
~~~~~~<br>
<br>
This module would define three base classes for use with error handling. Unlike<br>
many of the the other classes defined here, these classes are not abstract, as<br>
they have no behaviour. They exist simply to signal certain common behaviours.<br>
Backends should subclass these exceptions in their own packages, but needn't<br>
define any behaviour for them.<br>
<br>
In general, concrete implementations should subclass these exceptions rather<br>
than throw them directly. This makes it moderately easier to determine which<br>
concrete TLS implementation is in use during debugging of unexpected errors.<br>
However, this is not mandatory.<br>
<br>
The definitions of the errors are below::<br>
<br>
class TLSError(Exception):<br>
"""<br>
The base exception for all TLS related errors from any backend.<br>
Catching this error should be sufficient to catch *all* TLS errors,<br>
regardless of what backend is used.<br>
"""<br>
<br>
class WantWriteError(TLSError):<br>
"""<br>
A special signaling exception used only when non-blocking or<br>
buffer-only I/O is used. This error signals that the requested<br>
operation cannot complete until more data is written to the network,<br>
or until the output buffer is drained.<br>
"""<br>
<br>
class WantReadError(TLSError):<br>
"""<br>
A special signaling exception used only when non-blocking or<br>
buffer-only I/O is used. This error signals that the requested<br>
operation cannot complete until more data is read from the network, or<br>
until more data is available in the input buffer.<br>
"""<br>
<br>
<br>
Certificates<br>
~~~~~~~~~~~~<br>
<br>
This module would define an abstract X509 certificate class. This class would<br>
have almost no behaviour, as the goal of this module is not to provide all<br>
possible relevant cryptographic functionality that could be provided by X509<br>
certificates. Instead, all we need is the ability to signal the source of a<br>
certificate to a concrete implementation.<br>
<br>
For that reason, this certificate implementation defines only constructors. In<br>
essence, the certificate object in this module could be as abstract as a handle<br>
that can be used to locate a specific certificate.<br>
<br>
Concrete implementations may choose to provide alternative constructors, e.g.<br>
to load certificates from HSMs. If a common interface emerges for doing this,<br>
this module may be updated to provide a standard constructor for this use-case<br>
as well.<br>
<br>
Concrete implementations should aim to have Certificate objects be hashable if<br>
at all possible. This will help ensure that TLSConfiguration objects used with<br>
an individual concrete implementation are also hashable.<br>
<br>
::<br>
<br>
class Certificate(metaclass=ABCMeta)<wbr>:<br>
@abstractclassmethod<br>
def from_buffer(cls, buffer: bytes) -> Certificate:<br>
"""<br>
Creates a Certificate object from a byte buffer. This byte buffer<br>
may be either PEM-encoded or DER-encoded. If the buffer is PEM<br>
encoded it *must* begin with the standard PEM preamble (a series of<br>
dashes followed by the ASCII bytes "BEGIN CERTIFICATE" and another<br>
series of dashes). In the absence of that preamble, the<br>
implementation may assume that the certificate is DER-encoded<br>
instead.<br>
"""<br>
<br>
@abstractclassmethod<br>
def from_file(cls, path: Union[pathlib.Path, AnyStr]) -> Certificate:<br>
"""<br>
Creates a Certificate object from a file on disk. This method may<br>
be a convenience method that wraps ``open`` and ``from_buffer``,<br>
but some TLS implementations may be able to provide more-secure or<br>
faster methods of loading certificates that do not involve Python<br>
code.<br>
"""<br>
<br>
<br>
Private Keys<br>
~~~~~~~~~~~~<br>
<br>
This module would define an abstract private key class. Much like the<br>
Certificate class, this class has almost no behaviour in order to give as much<br>
freedom as possible to the concrete implementations to treat keys carefully.<br>
<br>
This class has all the caveats of the ``Certificate`` class.<br>
<br>
::<br>
<br>
class PrivateKey(metaclass=ABCMeta):<br>
@abstractclassmethod<br>
def from_buffer(cls,<br>
buffer: bytes,<br>
password=None: Optional[Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]) -> PrivateKey:<br>
"""<br>
Creates a PrivateKey object from a byte buffer. This byte buffer<br>
may be either PEM-encoded or DER-encoded. If the buffer is PEM<br>
encoded it *must* begin with the standard PEM preamble (a series of<br>
dashes followed by the ASCII bytes "BEGIN", the key type, and<br>
another series of dashes). In the absence of that preamble, the<br>
implementation may assume that the certificate is DER-encoded<br>
instead.<br>
<br>
The key may additionally be encrypted. If it is, the ``password``<br>
argument can be used to decrypt the key. The ``password`` argument<br>
may be a function to call to get the password for decrypting the<br>
private key. It will only be called if the private key is encrypted<br>
and a password is necessary. It will be called with no arguments,<br>
and it should return either bytes or bytearray containing the<br>
password. Alternatively a bytes, or bytearray value may be supplied<br>
directly as the password argument. It will be ignored if the<br>
private key is not encrypted and no password is needed.<br>
"""<br>
<br>
@abstractclassmethod<br>
def from_file(cls,<br>
path: Union[pathlib.Path, bytes, str],<br>
password=None: Optional[Union[Callable[[], Union[bytes, bytearray]], bytes, bytearray]) -> PrivateKey:<br>
"""<br>
Creates a PrivateKey object from a file on disk. This method may<br>
be a convenience method that wraps ``open`` and ``from_buffer``,<br>
but some TLS implementations may be able to provide more-secure or<br>
faster methods of loading certificates that do not involve Python<br>
code.<br>
<br>
The ``password`` parameter behaves exactly as the equivalent<br>
parameter on ``from_buffer``.<br>
"""<br>
<br>
<br>
Trust Store<br>
~~~~~~~~~~~<br>
<br>
As discussed above, loading a trust store represents an issue because different<br>
TLS implementations vary wildly in how they allow users to select trust stores.<br>
For this reason, we need to provide a model that assumes very little about the<br>
form that trust stores take.<br>
<br>
This problem is the same as the one that the Certificate and PrivateKey types<br>
need to solve. For this reason, we use the exact same model, by creating an<br>
opaque type that can encapsulate the various means that TLS backends may open<br>
a trust store.<br>
<br>
A given TLS implementation is not required to implement all of the<br>
constructors. However, it is strongly recommended that a given TLS<br>
implementation provide the ``system`` constructor if at all possible, as this<br>
is the most common validation trust store that is used. Concrete<br>
implementations may also add their own constructors.<br>
<br>
Concrete implementations should aim to have TrustStore objects be hashable if<br>
at all possible. This will help ensure that TLSConfiguration objects used with<br>
an individual concrete implementation are also hashable.<br>
<br>
::<br>
<br>
class TrustStore(metaclass=ABCMeta):<br>
@abstractclassmethod<br>
def system(cls) -> TrustStore:<br>
"""<br>
Returns a TrustStore object that represents the system trust<br>
database.<br>
"""<br>
<br>
@abstractclassmethod<br>
def from_pem_file(cls, path: Union[pathlib.Path, bytes, str]) -> TrustStore:<br>
"""<br>
Initializes a trust store from a single file full of PEMs.<br>
"""<br>
<br>
<br>
Runtime Access<br>
~~~~~~~~~~~~~~<br>
<br>
A not-uncommon use case for library users is to want to allow the library to<br>
control the TLS configuration, but to want to select what backend is in use.<br>
For example, users of Requests may want to be able to select between OpenSSL or<br>
a platform-native solution on Windows and macOS, or between OpenSSL and NSS on<br>
some Linux platforms. These users, however, may not care about exactly how<br>
their TLS configuration is done.<br>
<br>
This poses a problem: given an arbitrary concrete implementation, how can a<br>
library work out how to load certificates into the trust store? There are two<br>
options: either all concrete implementations can be required to fit into a<br>
specific naming scheme, or we can provide an API that makes it possible to grab<br>
these objects.<br>
<br>
This PEP proposes that we use the second approach. This grants the greatest<br>
freedom to concrete implementations to structure their code as they see fit,<br>
requiring only that they provide a single object that has the appropriate<br>
properties in place. Users can then pass this "backend" object to libraries<br>
that support it, and those libraries can take care of configuring and using the<br>
concrete implementation.<br>
<br>
All concrete implementations must provide a method of obtaining a ``Backend``<br>
object. The ``Backend`` object can be a global singleton or can be created by a<br>
callable if there is an advantage in doing that.<br>
<br>
The ``Backend`` object has the following definition::<br>
<br>
Backend = namedtuple(<br>
'Backend',<br>
['client_context', 'server_context',<br>
'certificate', 'private_key', 'trust_store']<br>
)<br>
<br>
Each of the properties must provide the concrete implementation of the relevant<br>
ABC. This ensures that code like this will work for any backend::<br>
<br>
trust_store = backend.trust_store.system()<br>
<br>
<br>
Changes to the Standard Library<br>
==============================<wbr>=<br>
<br>
The portions of the standard library that interact with TLS should be revised<br>
to use these ABCs. This will allow them to function with other TLS backends.<br>
This includes the following modules:<br>
<br>
- asyncio<br>
- ftplib<br>
- http.client<br>
- imaplib<br>
- nntplib<br>
- poplib<br>
- smtplib<br>
<br>
<br>
Migration of the ssl module<br>
---------------------------<br>
<br>
Naturally, we will need to extend the ``ssl`` module itself to conform to these<br>
ABCs. This extension will take the form of new classes, potentially in an<br>
entirely new module. This will allow applications that take advantage of the<br>
current ``ssl`` module to continue to do so, while enabling the new APIs for<br>
applications and libraries that want to use them.<br>
<br>
In general, migrating from the ``ssl`` module to the new ABCs is not expected<br>
to be one-to-one. This is normally acceptable: most tools that use the ``ssl``<br>
module hide it from the user, and so refactoring to use the new module should<br>
be invisible.<br>
<br>
However, a specific problem comes from libraries or applications that leak<br>
exceptions from the ``ssl`` module, either as part of their defined API or by<br>
accident (which is easily done). Users of those tools may have written code<br>
that tolerates and handles exceptions from the ``ssl`` module being raised:<br>
migrating to the ABCs presented here would potentially cause the exceptions<br>
defined above to be thrown instead, and existing ``except`` blocks will not<br>
catch them.<br>
<br>
For this reason, part of the migration of the ``ssl`` module would require that<br>
the exceptions in the ``ssl`` module alias those defined above. That is, they<br>
would require the following statements to all succeed::<br>
<br>
assert ssl.SSLError is tls.TLSError<br>
assert ssl.SSLWantReadError is tls.WantReadError<br>
assert ssl.SSLWantWriteError is tls.WantWriteError<br>
<br>
The exact mechanics of how this will be done are beyond the scope of this PEP,<br>
as they are made more complex due to the fact that the current ``ssl``<br>
exceptions are defined in C code, but more details can be found in<br>
`an email sent to the Security-SIG by Christian Heimes`_.<br>
<br>
<br>
Future<br>
======<br>
<br>
Major future TLS features may require revisions of these ABCs. These revisions<br>
should be made cautiously: many backends may not be able to move forward<br>
swiftly, and will be invalidated by changes in these ABCs. This is acceptable,<br>
but wherever possible features that are specific to individual implementations<br>
should not be added to the ABCs. The ABCs should restrict themselves to<br>
high-level descriptions of IETF-specified features.<br>
<br>
However, well-justified extensions to this API absolutely should be made. The<br>
focus of this API is to provide a unifying lowest-common-denominator<br>
configuration option for the Python community. TLS is not a static target, and<br>
as TLS evolves so must this API.<br>
<br>
<br>
References<br>
==========<br>
<br>
.. _ssl module: <a href="https://docs.python.org/3/library/ssl.html" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/ssl.html</a><br>
.. _OpenSSL Library: <a href="https://www.openssl.org/" rel="noreferrer" target="_blank">https://www.openssl.org/</a><br>
.. _PyOpenSSL: <a href="https://pypi.org/project/pyOpenSSL/" rel="noreferrer" target="_blank">https://pypi.org/project/<wbr>pyOpenSSL/</a><br>
.. _certifi: <a href="https://pypi.org/project/certifi/" rel="noreferrer" target="_blank">https://pypi.org/project/<wbr>certifi/</a><br>
.. _SSLContext: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/ssl.html#ssl.<wbr>SSLContext</a><br>
.. _SSLSocket: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLSocket" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/ssl.html#ssl.SSLSocket</a><br>
.. _SSLObject: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLObject" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/ssl.html#ssl.SSLObject</a><br>
.. _SSLError: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLError" rel="noreferrer" target="_blank">https://docs.python.org/3/<wbr>library/ssl.html#ssl.SSLError</a><br>
.. _MSDN articles: <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/mt490158(v=vs.85).aspx" rel="noreferrer" target="_blank">https://msdn.microsoft.com/en-<wbr>us/library/windows/desktop/<wbr>mt490158(v=vs.85).aspx</a><br>
.. _tlsdb JSON file: <a href="https://github.com/tiran/tlsdb/blob/master/tlsdb.json" rel="noreferrer" target="_blank">https://github.com/tiran/<wbr>tlsdb/blob/master/tlsdb.json</a><br>
.. _an email sent to the Security-SIG by Christian Heimes: <a href="https://mail.python.org/pipermail/security-sig/2017-January/000213.html" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>pipermail/security-sig/2017-<wbr>January/000213.html</a><br>
<br>
<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
<br>
<br>
..<br>
Local Variables:<br>
mode: indented-text<br>
indent-tabs-mode: nil<br>
sentence-end-double-space: t<br>
fill-column: 70<br>
coding: utf-8<br>
End:<br>
<br>
______________________________<wbr>_________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/<wbr>codeofconduct/</a></blockquote></div></div>