Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 543 – A Unified TLS API for Python

Author:
Cory Benfield <cory at lukasa.co.uk>,Christian Heimes <christian at python.org>
Status:
Withdrawn
Type:
Standards Track
Created:
17-Oct-2016
Python-Version:
3.7
Post-History:
11-Jan-2017, 19-Jan-2017, 02-Feb-2017, 09-Feb-2017
Superseded-By:
748

Table of Contents

Abstract

This PEP would define a standard TLS interface in the form of a collection ofabstract base classes. This interface would allow Python implementations andthird-party libraries to provide bindings to TLS libraries other than OpenSSLthat can be used by tools that expect the interface provided by the Pythonstandard library, with the goal of reducing the dependence of the Pythonecosystem on OpenSSL.

Resolution

2020-06-25: With contemporary agreement with one author, and pastagreement with another, this PEP is withdrawn due to changes in theAPIs of the underlying operating systems.

Rationale

In the 21st century it has become increasingly clear that robust anduser-friendly TLS support is an extremely important part of the ecosystem ofany popular programming language. For most of its lifetime, this role in thePython ecosystem has primarily been served by thessl module, which providesa Python API to theOpenSSL library.

Because thessl module is distributed with the Python standard library, ithas become the overwhelmingly most-popular method for handling TLS in Python.An extraordinary majority of Python libraries, both in the standard library andon the Python Package Index, rely on thessl module for their TLSconnectivity.

Unfortunately, the preeminence of thessl module has had a number ofunforeseen side-effects that have had the effect of tying the entire Pythonecosystem tightly to OpenSSL. This has forced Python users to use OpenSSL evenin situations where it may provide a worse user experience than alternative TLSimplementations, which imposes a cognitive burden and makes it hard to provide“platform-native” experiences.

Problems

The fact that thessl module is built into the standard library has meantthat all standard-library Python networking libraries are entirely reliant onthe OpenSSL that the Python implementation has been linked against. Thisleads to the following issues:

  • It is difficult to take advantage of new, higher-security TLS withoutrecompiling Python to get a new OpenSSL. While there are third-party bindingsto OpenSSL (e.g.pyOpenSSL), these need to be shimmed into a format thatthe standard library understands, forcing projects that want to use them tomaintain substantial compatibility layers.
  • For Windows distributions of Python, they need to be shipped with a copy ofOpenSSL. This puts the CPython development team in the position of beingOpenSSL redistributors, potentially needing to ship security updates to theWindows Python distributions when OpenSSL vulnerabilities are released.
  • For macOS distributions of Python, they need either to be shipped with a copyof OpenSSL or linked against the system OpenSSL library. Apple has formallydeprecated linking against the system OpenSSL library, and even if they hadnot, that library version has been unsupported by upstream for nearly oneyear as of the time of writing. The CPython development team has startedshipping newer OpenSSLs with the Python available from python.org, but thishas the same problem as with Windows.
  • Many systems, including but not limited to Windows and macOS, do not maketheir system certificate stores available to OpenSSL. This forces users toeither obtain their trust roots from elsewhere (e.g.certifi) or toattempt to export their system trust stores in some form.

    Relying oncertifi is less than ideal, as most system administrators donot expect to receive security-critical software updates from PyPI.Additionally, it is not easy to extend thecertifi trust bundle to includecustom roots, or to centrally manage trust using thecertifi model.

    Even in situations where the system certificate stores are made available toOpenSSL in some form, the experience is still sub-standard, as OpenSSL willperform different validation checks than the platform-native TLSimplementation. This can lead to users experiencing different behaviour ontheir browsers or other platform-native tools than they experience in Python,with little or no recourse to resolve the problem.

  • Users may wish to integrate with TLS libraries other than OpenSSL for manyother reasons, such as OpenSSL missing features (e.g. TLS 1.3 support), orbecause OpenSSL is simply too large and unwieldy for the platform (e.g. forembedded Python). Those users are left with the requirement to usethird-party networking libraries that can interact with their preferred TLSlibrary or to shim their preferred library into the OpenSSL-specificsslmodule API.

Additionally, thessl module as implemented today limits the ability ofCPython itself to add support for alternative TLS backends, or remove OpenSSLsupport entirely, should either of these become necessary or useful. Thessl module exposes too many OpenSSL-specific function calls and features toeasily map to an alternative TLS backend.

Proposal

This PEP proposes to introduce a few new Abstract Base Classes in Python 3.7 toprovide TLS functionality that is not so strongly tied to OpenSSL. It alsoproposes to update standard library modules to use only the interface exposedby these abstract base classes wherever possible. There are three goals here:

  1. To provide a common API surface for both core and third-party developers totarget their TLS implementations to. This allows TLS developers to provideinterfaces that can be used by most Python code, and allows networkdevelopers to have an interface that they can target that will work with awide range of TLS implementations.
  2. To provide an API that has few or no OpenSSL-specific concepts leak through.Thessl module today has a number of warts caused by leaking OpenSSLconcepts through to the API: the new ABCs would remove those specificconcepts.
  3. To provide a path for the core development team to make OpenSSL one of manypossible TLS backends, rather than requiring that it be present on a systemin order for Python to have TLS support.

The proposed interface is laid out below.

Interfaces

There are several interfaces that require standardisation. Those interfacesare:

  1. Configuring TLS, currently implemented by theSSLContext class in thessl module.
  2. Providing an in-memory buffer for doing in-memory encryption or decryptionwith no actual I/O (necessary for asynchronous I/O models), currentlyimplemented by theSSLObject class in thessl module.
  3. Wrapping a socket object, currently implemented by theSSLSocket classin thessl module.
  4. Applying TLS configuration to the wrapping objects in (2) and (3). Currentlythis is also implemented by theSSLContext class in thessl module.
  5. Specifying TLS cipher suites. There is currently no code for doing this inthe standard library: instead, the standard library uses OpenSSL ciphersuite strings.
  6. Specifying application-layer protocols that can be negotiated during theTLS handshake.
  7. Specifying TLS versions.
  8. Reporting errors to the caller, currently implemented by theSSLErrorclass in thessl module.
  9. Specifying certificates to load, either as client or server certificates.
  10. Specifying which trust database should be used to validate certificatespresented by a remote peer.
  11. Finding a way to get hold of these interfaces at run time.

For the sake of simplicity, this PEP proposes to take a unified approach to(2) and (3) (that is, buffers and sockets). The Python socket API is asizeable one, and implementing a wrapped socket that has the same behaviour asa regular Python socket is a subtle and tricky thing to do. However, it isentirely possible to implement ageneric wrapped socket in terms of wrappedbuffers: that is, it is possible to write a wrapped socket (3) that will workfor any implementation that provides (2). For this reason, this PEP proposes toprovide an ABC for wrapped buffers (2) but a concrete class for wrapped sockets(3).

This decision has the effect of making it impossible to bind a small number ofTLS libraries to this ABC, because those TLS librariescannot provide awrapped buffer implementation. The most notable of these at this time appearsto be Amazon’ss2n, which currently does not provide an I/O abstractionlayer. However, even this library consider this a missing feature and areworking to add it. For this reason, it is safe to assume that a concreteimplementation of (3) in terms of (2) will be a substantial effort-savingdevice and a great tool for correctness. Therefore, this PEP proposes doingjust that.

Obviously, (5) doesn’t require an abstract base class: instead, it requires aricher API for configuring supported cipher suites that can be easily updatedwith supported cipher suites for different implementations.

(9) is a thorny problem, because in an ideal world the private keys associatedwith these certificates would never end up in-memory in the Python process(that is, the TLS library would collaborate with a Hardware Security Module(HSM) to provide the private key in such a way that it cannot be extracted fromprocess memory). Thus, we need to provide an extensible model of providingcertificates that allows concrete implementations the ability to provide thishigher level of security, while also allowing a lower bar for thoseimplementations that cannot. This lower bar would be the same as the statusquo: that is, the certificate may be loaded from an in-memory buffer or from afile on disk.

(10) also represents an issue because different TLS implementations vary wildlyin how they allow users to select trust stores. Some implementations havespecific trust store formats that only they can use (such as the OpenSSL CAdirectory format that is created byc_rehash), and others may not allow youto specify a trust store that does not include their default trust store.

For this reason, we need to provide a model that assumes very little about theform that trust stores take. The “Trust Store” section below goes into moredetail about how this is achieved.

Finally, this API will split the responsibilities currently assumed by theSSLContext object: specifically, the responsibility for holding and managingconfiguration and the responsibility for using that configuration to buildwrapper objects.

This is necessarily primarily for supporting functionality like Server NameIndication (SNI). In OpenSSL (and thus in thessl module), the server hasthe ability to modify the TLS configuration in response to the client tellingthe server what hostname it is trying to reach. This is mostly used to changecertificate chain so as to present the correct TLS certificate chain for thegiven hostname. The specific mechanism by which this is done is by returninga newSSLContext object with the appropriate configuration.

This is not a model that maps well to other TLS implementations. Instead, weneed to make it possible to provide a return value from the SNI callback thatcan be used to indicate what configuration changes should be made. This meansproviding an object that can hold TLS configuration. This object needs to beapplied to specific TLSWrappedBuffer, and TLSWrappedSocket objects.

For this reason, we split the responsibility ofSSLContext into two separateobjects. TheTLSConfiguration object is an object that acts as containerfor TLS configuration: theClientContext andServerContext objects areobjects that are instantiated with aTLSConfiguration object. All threeobjects would be immutable.

Note

The following API declarations uniformly use type hints to aidreading. Some of these type hints cannot actually be used in practicebecause they are circularly referential. Consider them more aguideline than a reflection of the final code in the module.

Configuration

TheTLSConfiguration concrete class defines an object that can hold andmanage TLS configuration. The goals of this class are as follows:

  1. To provide a method of specifying TLS configuration that avoids the risk oferrors in typing (this excludes the use of a simple dictionary).
  2. To provide an object that can be safely compared to other configurationobjects to detect changes in TLS configuration, for use with the SNIcallback.

This class is not an ABC, primarily because it is not expected to haveimplementation-specific behaviour. The responsibility for transforming aTLSConfiguration object into a useful set of configuration for a given TLSimplementation belongs to the Context objects discussed below.

This class has one other notable property: it is immutable. This is a desirabletrait for a few reasons. The most important one is that it allows these objectsto be used as dictionary keys, which is potentially extremely valuable forcertain TLS backends and their SNI configuration. On top of this, it freesimplementations from needing to worry about their configuration objects beingchanged under their feet, which allows them to avoid needing to carefullysynchronize changes between their concrete data structures and theconfiguration object.

This object is extendable: that is, future releases of Python may addconfiguration fields to this object as they become useful. Forbackwards-compatibility purposes, new fields are only appended to this object.Existing fields will never be removed, renamed, or reordered.

TheTLSConfiguration object would be defined by the following code:

ServerNameCallback=Callable[[TLSBufferObject,Optional[str],TLSConfiguration],Any]_configuration_fields=['validate_certificates','certificate_chain','ciphers','inner_protocols','lowest_supported_version','highest_supported_version','trust_store','sni_callback',]_DEFAULT_VALUE=object()classTLSConfiguration(namedtuple('TLSConfiguration',_configuration_fields)):"""    An immutable TLS Configuration object. This object has the following    properties:    :param validate_certificates bool: Whether to validate the TLS        certificates. This switch operates at a very broad scope: either        validation is enabled, in which case all forms of validation are        performed including hostname validation if possible, or validation        is disabled, in which case no validation is performed.        Not all backends support having their certificate validation        disabled. If a backend does not support having their certificate        validation disabled, attempting to set this property to ``False``        will throw a ``TLSError`` when this object is passed into a        context object.    :param certificate_chain Tuple[Tuple[Certificate],PrivateKey]: The        certificate, intermediate certificate, and the corresponding        private key for the leaf certificate. These certificates will be        offered to the remote peer during the handshake if required.        The first Certificate in the list must be the leaf certificate. All        subsequent certificates will be offered as intermediate additional        certificates.    :param ciphers Tuple[Union[CipherSuite, int]]:        The available ciphers for TLS connections created with this        configuration, in priority order.    :param inner_protocols Tuple[Union[NextProtocol, bytes]]:        Protocols that connections created with this configuration should        advertise as supported during the TLS handshake. These may be        advertised using either or both of ALPN or NPN. This list of        protocols should be ordered by preference.    :param lowest_supported_version TLSVersion:        The minimum version of TLS that should be allowed on TLS        connections using this configuration.    :param highest_supported_version TLSVersion:        The maximum version of TLS that should be allowed on TLS        connections using this configuration.    :param trust_store TrustStore:        The trust store that connections using this configuration will use        to validate certificates.    :param sni_callback Optional[ServerNameCallback]:        A callback function that will be called after the TLS Client Hello        handshake message has been received by the TLS server when the TLS        client specifies a server name indication.        Only one callback can be set per ``TLSConfiguration``. If the        ``sni_callback`` is ``None`` then the callback is disabled. If the        ``TLSConfiguration`` is used for a ``ClientContext`` then this        setting will be ignored.        The ``callback`` function will be called with three arguments: the        first will be the ``TLSBufferObject`` for the connection; the        second will be a string that represents the server name that the        client is intending to communicate (or ``None`` if the TLS Client        Hello does not contain a server name); and the third argument will        be the original ``TLSConfiguration`` that configured the        connection. The server name argument will be the IDNA *decoded*        server name.        The ``callback`` must return a ``TLSConfiguration`` to allow        negotiation to continue. Other return values signal errors.        Attempting to control what error is signaled by the underlying TLS        implementation is not specified in this API, but is up to the        concrete implementation to handle.        The Context will do its best to apply the ``TLSConfiguration``        changes from its original configuration to the incoming connection.        This will usually include changing the certificate chain, but may        also include changes to allowable ciphers or any other        configuration settings.    """__slots__=()def__new__(cls,validate_certificates:Optional[bool]=None,certificate_chain:Optional[Tuple[Tuple[Certificate],PrivateKey]]=None,ciphers:Optional[Tuple[Union[CipherSuite,int]]]=None,inner_protocols:Optional[Tuple[Union[NextProtocol,bytes]]]=None,lowest_supported_version:Optional[TLSVersion]=None,highest_supported_version:Optional[TLSVersion]=None,trust_store:Optional[TrustStore]=None,sni_callback:Optional[ServerNameCallback]=None):ifvalidate_certificatesisNone:validate_certificates=TrueifciphersisNone:ciphers=DEFAULT_CIPHER_LISTifinner_protocolsisNone:inner_protocols=[]iflowest_supported_versionisNone:lowest_supported_version=TLSVersion.TLSv1ifhighest_supported_versionisNone:highest_supported_version=TLSVersion.MAXIMUM_SUPPORTEDreturnsuper().__new__(cls,validate_certificates,certificate_chain,ciphers,inner_protocols,lowest_supported_version,highest_supported_version,trust_store,sni_callback)defupdate(self,validate_certificates=_DEFAULT_VALUE,certificate_chain=_DEFAULT_VALUE,ciphers=_DEFAULT_VALUE,inner_protocols=_DEFAULT_VALUE,lowest_supported_version=_DEFAULT_VALUE,highest_supported_version=_DEFAULT_VALUE,trust_store=_DEFAULT_VALUE,sni_callback=_DEFAULT_VALUE):"""        Create a new ``TLSConfiguration``, overriding some of the settings        on the original configuration with the new settings.        """ifvalidate_certificatesis_DEFAULT_VALUE:validate_certificates=self.validate_certificatesifcertificate_chainis_DEFAULT_VALUE:certificate_chain=self.certificate_chainifciphersis_DEFAULT_VALUE:ciphers=self.ciphersifinner_protocolsis_DEFAULT_VALUE:inner_protocols=self.inner_protocolsiflowest_supported_versionis_DEFAULT_VALUE:lowest_supported_version=self.lowest_supported_versionifhighest_supported_versionis_DEFAULT_VALUE:highest_supported_version=self.highest_supported_versioniftrust_storeis_DEFAULT_VALUE:trust_store=self.trust_storeifsni_callbackis_DEFAULT_VALUE:sni_callback=self.sni_callbackreturnself.__class__(validate_certificates,certificate_chain,ciphers,inner_protocols,lowest_supported_version,highest_supported_version,trust_store,sni_callback)

Context

We define two Context abstract base classes. These ABCs define objects thatallow configuration of TLS to be applied to specific connections. They can bethought of as factories forTLSWrappedSocket andTLSWrappedBufferobjects.

Unlike the currentssl module, we provide two context classes instead ofone. Specifically, we provide theClientContext andServerContextclasses. This simplifies the APIs (for example, there is no sense in the serverproviding theserver_hostname parameter tossl.SSLContext.wrap_socket,but because there is only one context class that parameter is still available),and ensures that implementations know as early as possible which side of a TLSconnection they will serve. Additionally, it allows implementations to opt-outof one or either side of the connection. For example, SecureTransport on macOSis not really intended for server use and has an enormous amount offunctionality missing for server-side use. This would allow SecureTransportimplementations to simply not define a concrete subclass ofServerContextto signal their lack of support.

One of the other major differences to the currentssl module is that anumber of flags and options have been removed. Most of these are self-evident,but it is worth noting thatauto_handshake has been removed fromwrap_socket. This was removed because it fundamentally represents an odddesign wart that saves very minimal effort at the cost of a complexity increaseboth for users and implementers. This PEP requires that all users calldo_handshake explicitly after connecting.

As much as possible implementers should aim to make these classes immutable:that is, they should prefer not to allow users to mutate their internal statedirectly, instead preferring to create new contexts from new TLSConfigurationobjects. Obviously, the ABCs cannot enforce this constraint, and so they do notattempt to.

TheContext abstract base class has the following class definition:

TLSBufferObject=Union[TLSWrappedSocket,TLSWrappedBuffer]class_BaseContext(metaclass=ABCMeta):@abstractmethoddef__init__(self,configuration:TLSConfiguration):"""        Create a new context object from a given TLS configuration.        """@property@abstractmethoddefconfiguration(self)->TLSConfiguration:"""        Returns the TLS configuration that was used to create the context.        """classClientContext(_BaseContext):defwrap_socket(self,socket:socket.socket,server_hostname:Optional[str])->TLSWrappedSocket:"""        Wrap an existing Python socket object ``socket`` and return a        ``TLSWrappedSocket`` object. ``socket`` must be a ``SOCK_STREAM``        socket: all other socket types are unsupported.        The returned SSL socket is tied to the context, its settings and        certificates. The socket object originally passed to this method        should not be used again: attempting to use it in any way will lead        to undefined behaviour, especially across different TLS        implementations. To get the original socket object back once it has        been wrapped in TLS, see the ``unwrap`` method of the        TLSWrappedSocket.        The parameter ``server_hostname`` specifies the hostname of the        service which we are connecting to. This allows a single server to        host multiple SSL-based services with distinct certificates, quite        similarly to HTTP virtual hosts. This is also used to validate the        TLS certificate for the given hostname. If hostname validation is        not desired, then pass ``None`` for this parameter. This parameter        has no default value because opting-out of hostname validation is        dangerous, and should not be the default behaviour.        """buffer=self.wrap_buffers(server_hostname)returnTLSWrappedSocket(socket,buffer)@abstractmethoddefwrap_buffers(self,server_hostname:Optional[str])->TLSWrappedBuffer:"""        Create an in-memory stream for TLS, using memory buffers to store        incoming and outgoing ciphertext. The TLS routines will read        received TLS data from one buffer, and write TLS data that needs to        be emitted to another buffer.        The implementation details of how this buffering works are up to        the individual TLS implementation. This allows TLS libraries that        have their own specialised support to continue to do so, while        allowing those without to use whatever Python objects they see fit.        The ``server_hostname`` parameter has the same meaning as in        ``wrap_socket``.        """classServerContext(_BaseContext):defwrap_socket(self,socket:socket.socket)->TLSWrappedSocket:"""        Wrap an existing Python socket object ``socket`` and return a        ``TLSWrappedSocket`` object. ``socket`` must be a ``SOCK_STREAM``        socket: all other socket types are unsupported.        The returned SSL socket is tied to the context, its settings and        certificates. The socket object originally passed to this method        should not be used again: attempting to use it in any way will lead        to undefined behaviour, especially across different TLS        implementations. To get the original socket object back once it has        been wrapped in TLS, see the ``unwrap`` method of the        TLSWrappedSocket.        """buffer=self.wrap_buffers()returnTLSWrappedSocket(socket,buffer)@abstractmethoddefwrap_buffers(self)->TLSWrappedBuffer:"""        Create an in-memory stream for TLS, using memory buffers to store        incoming and outgoing ciphertext. The TLS routines will read        received TLS data from one buffer, and write TLS data that needs to        be emitted to another buffer.        The implementation details of how this buffering works are up to        the individual TLS implementation. This allows TLS libraries that        have their own specialised support to continue to do so, while        allowing those without to use whatever Python objects they see fit.        """

Buffer

The buffer-wrapper ABC will be defined by theTLSWrappedBuffer ABC, whichhas the following definition:

classTLSWrappedBuffer(metaclass=ABCMeta):@abstractmethoddefread(self,amt:int)->bytes:"""        Read up to ``amt`` bytes of data from the input buffer and return        the result as a ``bytes`` instance.        Once EOF is reached, all further calls to this method return the        empty byte string ``b''``.        May read "short": that is, fewer bytes may be returned than were        requested.        Raise ``WantReadError`` or ``WantWriteError`` if there is        insufficient data in either the input or output buffer and the        operation would have caused data to be written or read.        May raise ``RaggedEOF`` if the connection has been closed without a        graceful TLS shutdown. Whether this is an exception that should be        ignored or not is up to the specific application.        As at any time a re-negotiation is possible, a call to ``read()``        can also cause write operations.        """@abstractmethoddefreadinto(self,buffer:Any,amt:int)->int:"""        Read up to ``amt`` bytes of data from the input buffer into        ``buffer``, which must be an object that implements the buffer        protocol. Returns the number of bytes read.        Once EOF is reached, all further calls to this method return the        empty byte string ``b''``.        Raises ``WantReadError`` or ``WantWriteError`` if there is        insufficient data in either the input or output buffer and the        operation would have caused data to be written or read.        May read "short": that is, fewer bytes may be read than were        requested.        May raise ``RaggedEOF`` if the connection has been closed without a        graceful TLS shutdown. Whether this is an exception that should be        ignored or not is up to the specific application.        As at any time a re-negotiation is possible, a call to        ``readinto()`` can also cause write operations.        """@abstractmethoddefwrite(self,buf:Any)->int:"""        Write ``buf`` in encrypted form to the output buffer and return the        number of bytes written. The ``buf`` argument must be an object        supporting the buffer interface.        Raise ``WantReadError`` or ``WantWriteError`` if there is        insufficient data in either the input or output buffer and the        operation would have caused data to be written or read. In either        case, users should endeavour to resolve that situation and then        re-call this method. When re-calling this method users *should*        re-use the exact same ``buf`` object, as some backends require that        the exact same buffer be used.        This operation may write "short": that is, fewer bytes may be        written than were in the buffer.        As at any time a re-negotiation is possible, a call to ``write()``        can also cause read operations.        """@abstractmethoddefdo_handshake(self)->None:"""        Performs the TLS handshake. Also performs certificate validation        and hostname verification.        """@abstractmethoddefcipher(self)->Optional[Union[CipherSuite,int]]:"""        Returns the CipherSuite entry for the cipher that has been        negotiated on the connection. If no connection has been negotiated,        returns ``None``. If the cipher negotiated is not defined in        CipherSuite, returns the 16-bit integer representing that cipher        directly.        """@abstractmethoddefnegotiated_protocol(self)->Optional[Union[NextProtocol,bytes]]:"""        Returns the protocol that was selected during the TLS handshake.        This selection may have been made using ALPN, NPN, or some future        negotiation mechanism.        If the negotiated protocol is one of the protocols defined in the        ``NextProtocol`` enum, the value from that enum will be returned.        Otherwise, the raw bytestring of the negotiated protocol will be        returned.        If ``Context.set_inner_protocols()`` was not called, if the other        party does not support protocol negotiation, if this socket does        not support any of the peer's proposed protocols, or if the        handshake has not happened yet, ``None`` is returned.        """@property@abstractmethoddefcontext(self)->Context:"""        The ``Context`` object this buffer is tied to.        """@abstractpropertydefnegotiated_tls_version(self)->Optional[TLSVersion]:"""        The version of TLS that has been negotiated on this connection.        """@abstractmethoddefshutdown(self)->None:"""        Performs a clean TLS shut down. This should generally be used        whenever possible to signal to the remote peer that the content is        finished.        """@abstractmethoddefreceive_from_network(self,data):"""        Receives some TLS data from the network and stores it in an        internal buffer.        """@abstractmethoddefpeek_outgoing(self,amt):"""        Returns the next ``amt`` bytes of data that should be written to        the network from the outgoing data buffer, without removing it from        the internal buffer.        """@abstractmethoddefconsume_outgoing(self,amt):"""        Discard the next ``amt`` bytes from the outgoing data buffer. This        should be used when ``amt`` bytes have been sent on the network, to        signal that the data no longer needs to be buffered.        """

Socket

The socket-wrapper class will be a concrete class that accepts two items in itsconstructor: a regular socket object, and aTLSWrappedBuffer object. Thisobject will be too large to recreate in this PEP, but will be submitted as partof the work to build the module.

The wrapped socket will implement all of the socket API, though it will havestub implementations of methods that only work for sockets with types otherthanSOCK_STREAM (e.g.sendto/recvfrom). That limitation can belifted as-and-when support for DTLS is added to this module.

In addition, the socket class will include the followingextra methods on topof the regular socket methods:

classTLSWrappedSocket:defdo_handshake(self)->None:"""        Performs the TLS handshake. Also performs certificate validation        and hostname verification. This must be called after the socket has        connected (either via ``connect`` or ``accept``), before any other        operation is performed on the socket.        """defcipher(self)->Optional[Union[CipherSuite,int]]:"""        Returns the CipherSuite entry for the cipher that has been        negotiated on the connection. If no connection has been negotiated,        returns ``None``. If the cipher negotiated is not defined in        CipherSuite, returns the 16-bit integer representing that cipher        directly.        """defnegotiated_protocol(self)->Optional[Union[NextProtocol,bytes]]:"""        Returns the protocol that was selected during the TLS handshake.        This selection may have been made using ALPN, NPN, or some future        negotiation mechanism.        If the negotiated protocol is one of the protocols defined in the        ``NextProtocol`` enum, the value from that enum will be returned.        Otherwise, the raw bytestring of the negotiated protocol will be        returned.        If ``Context.set_inner_protocols()`` was not called, if the other        party does not support protocol negotiation, if this socket does        not support any of the peer's proposed protocols, or if the        handshake has not happened yet, ``None`` is returned.        """@propertydefcontext(self)->Context:"""        The ``Context`` object this socket is tied to.        """defnegotiated_tls_version(self)->Optional[TLSVersion]:"""        The version of TLS that has been negotiated on this connection.        """defunwrap(self)->socket.socket:"""        Cleanly terminate the TLS connection on this wrapped socket. Once        called, this ``TLSWrappedSocket`` can no longer be used to transmit        data. Returns the socket that was wrapped with TLS.        """

Cipher Suites

Supporting cipher suites in a truly library-agnostic fashion is a remarkablydifficult undertaking. Different TLS implementations often haveradicallydifferent APIs for specifying cipher suites, but more problematically theseAPIs frequently differ in capability as well as in style. Some examples areshown below:

OpenSSL

OpenSSL uses a well-known cipher string format. This format has been adopted asa configuration language by most products that use OpenSSL, including Python.This format is relatively easy to read, but has a number of downsides: it isa string, which makes it remarkably easy to provide bad inputs; it lacks muchdetailed validation, meaning that it is possible to configure OpenSSL in a waythat doesn’t allow it to negotiate any cipher at all; and it allows specifyingcipher suites in a number of different ways that make it tricky to parse. Thebiggest problem with this format is that there is no formal specification forit, meaning that the only way to parse a given string the way OpenSSL would isto get OpenSSL to parse it.

OpenSSL’s cipher strings can look like this:

'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5'

This string demonstrates some of the complexity of the OpenSSL format. Forexample, it is possible for one entry to specify multiple cipher suites: theentryECDH+AESGCM means “all ciphers suites that include bothelliptic-curve Diffie-Hellman key exchange and AES in Galois Counter Mode”.More explicitly, that will expand to four cipher suites:

"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"

That makes parsing a complete OpenSSL cipher string extremely tricky. Add tothe fact that there are other meta-characters, such as “!” (exclude all ciphersuites that match this criterion, even if they would otherwise be included:“!MD5” means that no cipher suites using the MD5 hash algorithm should beincluded), “-” (exclude matching ciphers if they were already included, butallow them to be re-added later if they get included again), and “+” (includethe matching ciphers, but place them at the end of the list), and you get anextremely complex format to parse. On top of this complexity it should benoted that the actual result depends on the OpenSSL version, as an OpenSSLcipher string is valid so long as it contains at least one cipher that OpenSSLrecognises.

OpenSSL also uses different names for its ciphers than the names used in therelevant specifications. See the manual page forciphers(1) for moredetails.

The actual API inside OpenSSL for the cipher string is simple:

char*cipher_list=<somecipherlist>;intrc=SSL_CTX_set_cipher_list(context,cipher_list);

This means that any format that is used by this module must be able to beconverted to an OpenSSL cipher string for use with OpenSSL.

SecureTransport

SecureTransport is the macOS system TLS library. This library is substantiallymore restricted than OpenSSL in many ways, as it has a much more restrictedclass of users. One of these substantial restrictions is in controllingsupported cipher suites.

Ciphers in SecureTransport are represented by a Cenum. This enum has oneentry per cipher suite, with no aggregate entries, meaning that it is notpossible to reproduce the meaning of an OpenSSL cipher string like“ECDH+AESGCM” without hand-coding which categories each enum member falls into.

However, the names of most of the enum members are in line with the formalnames of the cipher suites: that is, the cipher suite that OpenSSL calls“ECDHE-ECDSA-AES256-GCM-SHA384” is called“TLS_ECDHE_ECDHSA_WITH_AES_256_GCM_SHA384” in SecureTransport.

The API for configuring cipher suites inside SecureTransport is simple:

SSLCipherSuiteciphers[]={TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,...};OSStatusstatus=SSLSetEnabledCiphers(context,ciphers,sizeof(ciphers));
SChannel

SChannel is the Windows system TLS library.

SChannel has extremely restrictive support for controlling available TLScipher suites, and additionally adopts a third method of expressing what TLScipher suites are supported.

Specifically, SChannel defines a set ofALG_ID constants (C unsigned ints).Each of these constants does not refer to an entire cipher suite, but insteadan individual algorithm. Some examples areCALG_3DES andCALG_AES_256,which refer to the bulk encryption algorithm used in a cipher suite,CALG_DH_EPHEM andCALG_RSA_KEYX which refer to part of the key exchangealgorithm used in a cipher suite,CALG_SHA1 andCALG_MD5 which refer tothe message authentication code used in a cipher suite, andCALG_ECDSA andCALG_RSA_SIGN which refer to the signing portions of the key exchangealgorithm.

This can be thought of as the half of OpenSSL’s functionality thatSecureTransport doesn’t have: SecureTransport only allows specifying exactcipher suites, while SChannel only allows specifyingparts of the ciphersuite, while OpenSSL allows both.

Determining which cipher suites are allowed on a given connection is done byproviding a pointer to an array of theseALG_ID constants. This means thatany suitable API must allow the Python code to determine whichALG_IDconstants must be provided.

Network Security Services (NSS)

NSS is Mozilla’s crypto and TLS library. It’s used in Firefox, Thunderbird,and as alternative to OpenSSL in multiple libraries, e.g. curl.

By default, NSS comes with secure configuration of allowed ciphers. On someplatforms such as Fedora, the list of enabled ciphers is globally configuredin a system policy. Generally, applications should not modify cipher suitesunless they have specific reasons to do so.

NSS has both process global and per-connection settings for cipher suites. Itdoes not have a concept of SSLContext like OpenSSL. A SSLContext-like behaviorcan be easily emulated. Specifically, ciphers can be enabled or disabledglobally withSSL_CipherPrefSetDefault(PRInt32cipher,PRBoolenabled),andSSL_CipherPrefSet(PRFileDesc*fd,PRInt32cipher,PRBoolenabled)for a connection. The cipherPRInt32 number is a signed 32bit integerthat directly corresponds to an registered IANA id, e.g.0x1301isTLS_AES_128_GCM_SHA256. Contrary to OpenSSL, the preference orderof ciphers is fixed and cannot be modified at runtime.

Like SecureTransport, NSS has no API for aggregated entries. Some consumersof NSS have implemented custom mappings from OpenSSL cipher names and rulesto NSS ciphers, e.g.mod_nss.

Proposed Interface

The proposed interface for the new module is influenced by the combined set oflimitations of the above implementations. Specifically, as every implementationexcept OpenSSL requires that each individual cipher be provided, there is nooption but to provide that lowest-common denominator approach.

The simplest approach is to provide an enumerated type that includes a largesubset of the cipher suites defined for TLS. The values of the enum memberswill be their two-octet cipher identifier as used in the TLS handshake,stored as a 16 bit integer. The names of the enum members will be theirIANA-registered cipher suite names.

As of now, theIANA cipher suite registry contains over 320 cipher suites.A large portion of the cipher suites are irrelevant for TLS connections tonetwork services. Other suites specify deprecated and insecure algorithmsthat are no longer provided by recent versions of implementations. The enumdoes not contain ciphers with:

  • key exchange: NULL, Kerberos (KRB5), pre-shared key (PSK), secure remotetransport (TLS-SRP)
  • authentication: NULL, anonymous, export grade, Kerberos (KRB5),pre-shared key (PSK), secure remote transport (TLS-SRP), DSA cert (DSS)
  • encryption: NULL, ARIA, DES, RC2, export grade 40bit
  • PRF: MD5
  • SCSV cipher suites

3DES, RC4, SEED, and IDEA are included for legacy applications. Further morefive additional cipher suites from the TLS 1.3 draft (draft-ietf-tls-tls13-18)are included, too. TLS 1.3 does not share any cipher suites with TLS 1.2 andearlier. The resulting enum will contain roughly 110 suites.

Because of these limitations, and because the enum doesn’t contain everydefined cipher, and also to allow for forward-looking applications, all partsof this API that acceptCipherSuite objects will also accept raw 16-bitintegers directly.

Rather than populate this enum by hand, we have aTLS enum script thatbuilds it from Christian Heimes’tlsdb JSON file (warning:large file) andIANA cipher suite registry. The TLSDB also opens up thepossibility of extending the API with additional querying function,such as determining which TLS versions support which ciphers, if thatfunctionality is found to be useful or necessary.

If users find this approach to be onerous, a future extension to this API canprovide helpers that can reintroduce OpenSSL’s aggregation functionality.

classCipherSuite(IntEnum):TLS_RSA_WITH_RC4_128_SHA=0x0005TLS_RSA_WITH_IDEA_CBC_SHA=0x0007TLS_RSA_WITH_3DES_EDE_CBC_SHA=0x000aTLS_DH_RSA_WITH_3DES_EDE_CBC_SHA=0x0010TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA=0x0016TLS_RSA_WITH_AES_128_CBC_SHA=0x002fTLS_DH_RSA_WITH_AES_128_CBC_SHA=0x0031TLS_DHE_RSA_WITH_AES_128_CBC_SHA=0x0033TLS_RSA_WITH_AES_256_CBC_SHA=0x0035TLS_DH_RSA_WITH_AES_256_CBC_SHA=0x0037TLS_DHE_RSA_WITH_AES_256_CBC_SHA=0x0039TLS_RSA_WITH_AES_128_CBC_SHA256=0x003cTLS_RSA_WITH_AES_256_CBC_SHA256=0x003dTLS_DH_RSA_WITH_AES_128_CBC_SHA256=0x003fTLS_RSA_WITH_CAMELLIA_128_CBC_SHA=0x0041TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA=0x0043TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA=0x0045TLS_DHE_RSA_WITH_AES_128_CBC_SHA256=0x0067TLS_DH_RSA_WITH_AES_256_CBC_SHA256=0x0069TLS_DHE_RSA_WITH_AES_256_CBC_SHA256=0x006bTLS_RSA_WITH_CAMELLIA_256_CBC_SHA=0x0084TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA=0x0086TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA=0x0088TLS_RSA_WITH_SEED_CBC_SHA=0x0096TLS_DH_RSA_WITH_SEED_CBC_SHA=0x0098TLS_DHE_RSA_WITH_SEED_CBC_SHA=0x009aTLS_RSA_WITH_AES_128_GCM_SHA256=0x009cTLS_RSA_WITH_AES_256_GCM_SHA384=0x009dTLS_DHE_RSA_WITH_AES_128_GCM_SHA256=0x009eTLS_DHE_RSA_WITH_AES_256_GCM_SHA384=0x009fTLS_DH_RSA_WITH_AES_128_GCM_SHA256=0x00a0TLS_DH_RSA_WITH_AES_256_GCM_SHA384=0x00a1TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256=0x00baTLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256=0x00bcTLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256=0x00beTLS_RSA_WITH_CAMELLIA_256_CBC_SHA256=0x00c0TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256=0x00c2TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256=0x00c4TLS_AES_128_GCM_SHA256=0x1301TLS_AES_256_GCM_SHA384=0x1302TLS_CHACHA20_POLY1305_SHA256=0x1303TLS_AES_128_CCM_SHA256=0x1304TLS_AES_128_CCM_8_SHA256=0x1305TLS_ECDH_ECDSA_WITH_RC4_128_SHA=0xc002TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA=0xc003TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA=0xc004TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA=0xc005TLS_ECDHE_ECDSA_WITH_RC4_128_SHA=0xc007TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA=0xc008TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA=0xc009TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA=0xc00aTLS_ECDH_RSA_WITH_RC4_128_SHA=0xc00cTLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA=0xc00dTLS_ECDH_RSA_WITH_AES_128_CBC_SHA=0xc00eTLS_ECDH_RSA_WITH_AES_256_CBC_SHA=0xc00fTLS_ECDHE_RSA_WITH_RC4_128_SHA=0xc011TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA=0xc012TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA=0xc013TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA=0xc014TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256=0xc023TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384=0xc024TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256=0xc025TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384=0xc026TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256=0xc027TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384=0xc028TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256=0xc029TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384=0xc02aTLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256=0xc02bTLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384=0xc02cTLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256=0xc02dTLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384=0xc02eTLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256=0xc02fTLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384=0xc030TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256=0xc031TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384=0xc032TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256=0xc072TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384=0xc073TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256=0xc074TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384=0xc075TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256=0xc076TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384=0xc077TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256=0xc078TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384=0xc079TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256=0xc07aTLS_RSA_WITH_CAMELLIA_256_GCM_SHA384=0xc07bTLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256=0xc07cTLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384=0xc07dTLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256=0xc07eTLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384=0xc07fTLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256=0xc086TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384=0xc087TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256=0xc088TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384=0xc089TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256=0xc08aTLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384=0xc08bTLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256=0xc08cTLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384=0xc08dTLS_RSA_WITH_AES_128_CCM=0xc09cTLS_RSA_WITH_AES_256_CCM=0xc09dTLS_DHE_RSA_WITH_AES_128_CCM=0xc09eTLS_DHE_RSA_WITH_AES_256_CCM=0xc09fTLS_RSA_WITH_AES_128_CCM_8=0xc0a0TLS_RSA_WITH_AES_256_CCM_8=0xc0a1TLS_DHE_RSA_WITH_AES_128_CCM_8=0xc0a2TLS_DHE_RSA_WITH_AES_256_CCM_8=0xc0a3TLS_ECDHE_ECDSA_WITH_AES_128_CCM=0xc0acTLS_ECDHE_ECDSA_WITH_AES_256_CCM=0xc0adTLS_ECDHE_ECDSA_WITH_AES_128_CCM_8=0xc0aeTLS_ECDHE_ECDSA_WITH_AES_256_CCM_8=0xc0afTLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256=0xcca8TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256=0xcca9TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256=0xccaa

Enum members can be mapped to OpenSSL cipher names:

>>>importssl>>>ctx=ssl.SSLContext(ssl.PROTOCOL_TLS)>>>ctx.set_ciphers('ALL:COMPLEMENTOFALL')>>>ciphers={c['id']&0xffff:c['name']forcinctx.get_ciphers()}>>>ciphers[CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]'ECDHE-RSA-AES128-GCM-SHA256'

For SecureTransport, these enum members directly refer to the values of thecipher suite constants. For example, SecureTransport defines the cipher suiteenum memberTLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 as having the value0xC02C. Not coincidentally, that is identical to its value in the aboveenum. This makes mapping between SecureTransport and the above enum very easyindeed.

For SChannel there is no easy direct mapping, due to the fact that SChannelconfigures ciphers, instead of cipher suites. This represents an ongoingconcern with SChannel, which is that it is very difficult to configure in aspecific manner compared to other TLS implementations.

For the purposes of this PEP, any SChannel implementation will need todetermine which ciphers to choose based on the enum members. This may be moreopen than the actual cipher suite list actually wants to allow, or it may bemore restrictive, depending on the choices of the implementation. This PEPrecommends that it be more restrictive, but of course this cannot be enforced.

Protocol Negotiation

Both NPN and ALPN allow for protocol negotiation as part of the HTTP/2handshake. While NPN and ALPN are, at their fundamental level, built on top ofbytestrings, string-based APIs are frequently problematic as they allow forerrors in typing that can be hard to detect.

For this reason, this module would define a type that protocol negotiationimplementations can pass and be passed. This type would wrap a bytestring toallow for aliases for well-known protocols. This allows us to avoid theproblems inherent in typos for well-known protocols, while allowing the fullextensibility of the protocol negotiation layer if needed by letting users passbyte strings directly.

classNextProtocol(Enum):H2=b'h2'H2C=b'h2c'HTTP1=b'http/1.1'WEBRTC=b'webrtc'C_WEBRTC=b'c-webrtc'FTP=b'ftp'STUN=b'stun.nat-discovery'TURN=b'stun.turn'

TLS Versions

It is often useful to be able to restrict the versions of TLS you’re willing tosupport. There are many security advantages in refusing to use old versions ofTLS, and some misbehaving servers will mishandle TLS clients advertisingsupport for newer versions.

The following enumerated type can be used to gate TLS versions. Forward-lookingapplications should almost never set a maximum TLS version unless theyabsolutely must, as a TLS backend that is newer than the Python that uses itmay support TLS versions that are not in this enumerated type.

Additionally, this enumerated type defines two additional flags that can alwaysbe used to request either the lowest or highest TLS version supported by animplementation.

classTLSVersion(Enum):MINIMUM_SUPPORTED=auto()SSLv2=auto()SSLv3=auto()TLSv1=auto()TLSv1_1=auto()TLSv1_2=auto()TLSv1_3=auto()MAXIMUM_SUPPORTED=auto()

Errors

This module would define four base classes for use with error handling. Unlikemany of the other classes defined here, these classes are not abstract, asthey have no behaviour. They exist simply to signal certain common behaviours.Backends should subclass these exceptions in their own packages, but needn’tdefine any behaviour for them.

In general, concrete implementations should subclass these exceptions ratherthan throw them directly. This makes it moderately easier to determine whichconcrete TLS implementation is in use during debugging of unexpected errors.However, this is not mandatory.

The definitions of the errors are below:

classTLSError(Exception):"""    The base exception for all TLS related errors from any backend.    Catching this error should be sufficient to catch *all* TLS errors,    regardless of what backend is used.    """classWantWriteError(TLSError):"""    A special signaling exception used only when non-blocking or    buffer-only I/O is used. This error signals that the requested    operation cannot complete until more data is written to the network,    or until the output buffer is drained.    This error is should only be raised when it is completely impossible    to write any data. If a partial write is achievable then this should    not be raised.    """classWantReadError(TLSError):"""    A special signaling exception used only when non-blocking or    buffer-only I/O is used. This error signals that the requested    operation cannot complete until more data is read from the network, or    until more data is available in the input buffer.    This error should only be raised when it is completely impossible to    write any data. If a partial write is achievable then this should not    be raised.    """classRaggedEOF(TLSError):"""    A special signaling exception used when a TLS connection has been    closed gracelessly: that is, when a TLS CloseNotify was not received    from the peer before the underlying TCP socket reached EOF. This is a    so-called "ragged EOF".    This exception is not guaranteed to be raised in the face of a ragged    EOF: some implementations may not be able to detect or report the    ragged EOF.    This exception is not always a problem. Ragged EOFs are a concern only    when protocols are vulnerable to length truncation attacks. Any    protocol that can detect length truncation attacks at the application    layer (e.g. HTTP/1.1 and HTTP/2) is not vulnerable to this kind of    attack and so can ignore this exception.    """

Certificates

This module would define an abstract X509 certificate class. This class wouldhave almost no behaviour, as the goal of this module is not to provide allpossible relevant cryptographic functionality that could be provided by X509certificates. Instead, all we need is the ability to signal the source of acertificate to a concrete implementation.

For that reason, this certificate implementation defines only constructors. Inessence, the certificate object in this module could be as abstract as a handlethat can be used to locate a specific certificate.

Concrete implementations may choose to provide alternative constructors, e.g.to load certificates from HSMs. If a common interface emerges for doing this,this module may be updated to provide a standard constructor for this use-caseas well.

Concrete implementations should aim to have Certificate objects be hashable ifat all possible. This will help ensure that TLSConfiguration objects used withan individual concrete implementation are also hashable.

classCertificate(metaclass=ABCMeta):@abstractclassmethoddeffrom_buffer(cls,buffer:bytes):"""        Creates a Certificate object from a byte buffer. This byte buffer        may be either PEM-encoded or DER-encoded. If the buffer is PEM        encoded it *must* begin with the standard PEM preamble (a series of        dashes followed by the ASCII bytes "BEGIN CERTIFICATE" and another        series of dashes). In the absence of that preamble, the        implementation may assume that the certificate is DER-encoded        instead.        """@abstractclassmethoddeffrom_file(cls,path:Union[pathlib.Path,AnyStr]):"""        Creates a Certificate object from a file on disk. This method may        be a convenience method that wraps ``open`` and ``from_buffer``,        but some TLS implementations may be able to provide more-secure or        faster methods of loading certificates that do not involve Python        code.        """

Private Keys

This module would define an abstract private key class. Much like theCertificate class, this class has almost no behaviour in order to give as muchfreedom as possible to the concrete implementations to treat keys carefully.

This class has all the caveats of theCertificate class.

classPrivateKey(metaclass=ABCMeta):@abstractclassmethoddeffrom_buffer(cls,buffer:bytes,password:Optional[Union[Callable[[],Union[bytes,bytearray]],bytes,bytearray]]=None):"""        Creates a PrivateKey object from a byte buffer. This byte buffer        may be either PEM-encoded or DER-encoded. If the buffer is PEM        encoded it *must* begin with the standard PEM preamble (a series of        dashes followed by the ASCII bytes "BEGIN", the key type, and        another series of dashes). In the absence of that preamble, the        implementation may assume that the certificate is DER-encoded        instead.        The key may additionally be encrypted. If it is, the ``password``        argument can be used to decrypt the key. 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 encrypted        and a password is necessary. It will be called with no arguments,        and it should return either bytes or bytearray containing the        password. Alternatively a bytes, or bytearray value may be supplied        directly as the password argument. It will be ignored if the        private key is not encrypted and no password is needed.        """@abstractclassmethoddeffrom_file(cls,path:Union[pathlib.Path,bytes,str],password:Optional[Union[Callable[[],Union[bytes,bytearray]],bytes,bytearray]]=None):"""        Creates a PrivateKey object from a file on disk. This method may        be a convenience method that wraps ``open`` and ``from_buffer``,        but some TLS implementations may be able to provide more-secure or        faster methods of loading certificates that do not involve Python        code.        The ``password`` parameter behaves exactly as the equivalent        parameter on ``from_buffer``.        """

Trust Store

As discussed above, loading a trust store represents an issue because differentTLS implementations vary wildly in how they allow users to select trust stores.For this reason, we need to provide a model that assumes very little about theform that trust stores take.

This problem is the same as the one that the Certificate and PrivateKey typesneed to solve. For this reason, we use the exact same model, by creating anopaque type that can encapsulate the various means that TLS backends may opena trust store.

A given TLS implementation is not required to implement all of theconstructors. However, it is strongly recommended that a given TLSimplementation provide thesystem constructor if at all possible, as thisis the most common validation trust store that is used. Concreteimplementations may also add their own constructors.

Concrete implementations should aim to have TrustStore objects be hashable ifat all possible. This will help ensure that TLSConfiguration objects used withan individual concrete implementation are also hashable.

classTrustStore(metaclass=ABCMeta):@abstractclassmethoddefsystem(cls)->TrustStore:"""        Returns a TrustStore object that represents the system trust        database.        """@abstractclassmethoddeffrom_pem_file(cls,path:Union[pathlib.Path,bytes,str])->TrustStore:"""        Initializes a trust store from a single file full of PEMs.        """

Runtime Access

A not-uncommon use case for library users is to want to allow the library tocontrol the TLS configuration, but to want to select what backend is in use.For example, users of Requests may want to be able to select between OpenSSL ora platform-native solution on Windows and macOS, or between OpenSSL and NSS onsome Linux platforms. These users, however, may not care about exactly howtheir TLS configuration is done.

This poses a problem: given an arbitrary concrete implementation, how can alibrary work out how to load certificates into the trust store? There are twooptions: either all concrete implementations can be required to fit into aspecific naming scheme, or we can provide an API that makes it possible to grabthese objects.

This PEP proposes that we use the second approach. This grants the greatestfreedom to concrete implementations to structure their code as they see fit,requiring only that they provide a single object that has the appropriateproperties in place. Users can then pass this “backend” object to librariesthat support it, and those libraries can take care of configuring and using theconcrete implementation.

All concrete implementations must provide a method of obtaining aBackendobject. TheBackend object can be a global singleton or can be created by acallable if there is an advantage in doing that.

TheBackend object has the following definition:

Backend=namedtuple('Backend',['client_context','server_context','certificate','private_key','trust_store'])

Each of the properties must provide the concrete implementation of the relevantABC. This ensures that code like this will work for any backend:

trust_store=backend.trust_store.system()

Changes to the Standard Library

The portions of the standard library that interact with TLS should be revisedto use these ABCs. This will allow them to function with other TLS backends.This includes the following modules:

  • asyncio
  • ftplib
  • http
  • imaplib
  • nntplib
  • poplib
  • smtplib
  • urllib

Migration of the ssl module

Naturally, we will need to extend thessl module itself to conform to theseABCs. This extension will take the form of new classes, potentially in anentirely new module. This will allow applications that take advantage of thecurrentssl module to continue to do so, while enabling the new APIs forapplications and libraries that want to use them.

In general, migrating from thessl module to the new ABCs is not expectedto be one-to-one. This is normally acceptable: most tools that use thesslmodule hide it from the user, and so refactoring to use the new module shouldbe invisible.

However, a specific problem comes from libraries or applications that leakexceptions from thessl module, either as part of their defined API or byaccident (which is easily done). Users of those tools may have written codethat tolerates and handles exceptions from thessl module being raised:migrating to the ABCs presented here would potentially cause the exceptionsdefined above to be thrown instead, and existingexcept blocks will notcatch them.

For this reason, part of the migration of thessl module would require thatthe exceptions in thessl module alias those defined above. That is, theywould require the following statements to all succeed:

assertssl.SSLErroristls.TLSErrorassertssl.SSLWantReadErroristls.WantReadErrorassertssl.SSLWantWriteErroristls.WantWriteError

The exact mechanics of how this will be done are beyond the scope of this PEP,as they are made more complex due to the fact that the currentsslexceptions are defined in C code, but more details can be found inan email sent to the Security-SIG by Christian Heimes.

Future

Major future TLS features may require revisions of these ABCs. These revisionsshould be made cautiously: many backends may not be able to move forwardswiftly, and will be invalidated by changes in these ABCs. This is acceptable,but wherever possible features that are specific to individual implementationsshould not be added to the ABCs. The ABCs should restrict themselves tohigh-level descriptions of IETF-specified features.

However, well-justified extensions to this API absolutely should be made. Thefocus of this API is to provide a unifying lowest-common-denominatorconfiguration option for the Python community. TLS is not a static target, andas TLS evolves so must this API.

Credits

This document has received extensive review from a number of individuals in thecommunity who have substantially helped shape it. Detailed review was providedby:

  • Alex Chan
  • Alex Gaynor
  • Antoine Pitrou
  • Ashwini Oruganti
  • Donald Stufft
  • Ethan Furman
  • Glyph
  • Hynek Schlawack
  • Jim J Jewett
  • Nathaniel J. Smith
  • Alyssa Coghlan
  • Paul Kehrer
  • Steve Dower
  • Steven Fackler
  • Wes Turner
  • Will Bond

Further review was provided by the Security-SIG and python-ideas mailing lists.

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-0543.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2025 Movatter.jp