Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 543 – Eine einheitliche TLS-API für Python

Autor:
Cory Benfield <cory at lukasa.co.uk>, Christian Heimes <christian at python.org>
Status:
Zurückgezogen
Typ:
Standards Track
Erstellt:
17-Okt-2016
Python-Version:
3.7
Post-History:
11-Jan-2017, 19-Jan-2017, 02-Feb-2017, 09-Feb-2017
Ersetzt-Durch:
748

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP würde eine standardmäßige TLS-Schnittstelle in Form einer Sammlung von abstrakten Basisklassen definieren. Diese Schnittstelle würde es Python-Implementierungen und Drittanbieterbibliotheken ermöglichen, Bindings zu anderen TLS-Bibliotheken als OpenSSL bereitzustellen, die von Tools verwendet werden können, die die von der Python-Standardbibliothek bereitgestellte Schnittstelle erwarten. Ziel ist es, die Abhängigkeit des Python-Ökosystems von OpenSSL zu reduzieren.

Entscheidung

2020-06-25: Mit zeitgenössischer Zustimmung eines Autors und früherer Zustimmung eines anderen wird dieses PEP aufgrund von Änderungen an den APIs der zugrunde liegenden Betriebssysteme zurückgezogen.

Begründung

Im 21. Jahrhundert ist immer deutlicher geworden, dass eine robuste und benutzerfreundliche TLS-Unterstützung ein äußerst wichtiger Bestandteil des Ökosystems jeder populären Programmiersprache ist. Die meiste Zeit wurde diese Rolle im Python-Ökosystem hauptsächlich vom ssl-Modul wahrgenommen, das eine Python-API für die OpenSSL-Bibliothek bereitstellt.

Da das ssl-Modul mit der Python-Standardbibliothek ausgeliefert wird, ist es zur mit Abstand beliebtesten Methode für die TLS-Verarbeitung in Python geworden. Eine überwältigende Mehrheit der Python-Bibliotheken, sowohl in der Standardbibliothek als auch im Python Package Index, verlässt sich für ihre TLS-Konnektivität auf das ssl-Modul.

Leider hatte die Vormachtstellung des ssl-Moduls eine Reihe von unbeabsichtigten Nebenwirkungen, die dazu geführt haben, dass das gesamte Python-Ökosystem eng an OpenSSL gebunden ist. Dies zwang Python-Benutzer, OpenSSL auch in Situationen zu verwenden, in denen es eine schlechtere Benutzererfahrung als alternative TLS-Implementierungen bieten könnte, was eine kognitive Belastung darstellt und es schwierig macht, „plattformnative“ Erlebnisse zu bieten.

Probleme

Die Tatsache, dass das ssl-Modul in die Standardbibliothek integriert ist, hat dazu geführt, dass alle Netzwerkbibliotheken der Python-Standardbibliothek vollständig von dem OpenSSL abhängen, mit dem die Python-Implementierung verknüpft wurde. Dies führt zu den folgenden Problemen:

  • Es ist schwierig, neue, sicherere TLS-Versionen zu nutzen, ohne Python neu zu kompilieren, um ein neues OpenSSL zu erhalten. Zwar gibt es Drittanbieter-Bindings für OpenSSL (z. B. pyOpenSSL), diese müssen jedoch in ein Format „geschirmt“ werden, das die Standardbibliothek versteht. Dies zwingt Projekte, die diese nutzen möchten, erhebliche Kompatibilitätsschichten zu pflegen.
  • Für Windows-Distributionen von Python müssen diese mit einer Kopie von OpenSSL ausgeliefert werden. Dies versetzt das CPython-Entwicklungsteam in die Lage, OpenSSL neu zu verteilen und möglicherweise Sicherheitsupdates für die Windows-Python-Distributionen zu liefern, wenn OpenSSL-Schwachstellen veröffentlicht werden.
  • Für macOS-Distributionen von Python müssen entweder eine Kopie von OpenSSL mitgeliefert oder gegen die System-OpenSSL-Bibliothek gelinkt werden. Apple hat das Linken gegen die System-OpenSSL-Bibliothek formell als veraltet erklärt, und selbst wenn sie dies nicht getan hätten, ist diese Bibliotheksversion zum Zeitpunkt der Erstellung dieser Zeilen seit fast einem Jahr nicht mehr von Upstream unterstützt worden. Das CPython-Entwicklungsteam hat damit begonnen, neuere OpenSSLs mit dem von python.org verfügbaren Python auszuliefern, aber dies hat dasselbe Problem wie bei Windows.
  • Viele Systeme, einschließlich, aber nicht beschränkt auf Windows und macOS, machen ihre System-Zertifikatsspeicher für OpenSSL nicht zugänglich. Dies zwingt Benutzer entweder, ihre Vertrauenswurzeln von anderswo zu beziehen (z. B. certifi) oder zu versuchen, ihre System-Vertrauensspeicher in irgendeiner Form zu exportieren.

    Die Abhängigkeit von certifi ist weniger als ideal, da die meisten Systemadministratoren nicht erwarten, sicherheitskritische Software-Updates von PyPI zu erhalten. Darüber hinaus ist es nicht einfach, das certifi-Vertrauensbündel um benutzerdefinierte Wurzeln zu erweitern oder das Vertrauen zentral mit dem certifi-Modell zu verwalten.

    Selbst in Situationen, in denen die System-Zertifikatsspeicher in irgendeiner Form für OpenSSL zugänglich gemacht werden, ist die Erfahrung immer noch unterdurchschnittlich, da OpenSSL andere Validierungsprüfungen durchführt als die plattformnative TLS-Implementierung. Dies kann dazu führen, dass Benutzer ein anderes Verhalten in ihren Browsern oder anderen plattformnativen Tools erleben als in Python, mit wenig oder gar keiner Möglichkeit, das Problem zu lösen.

  • Benutzer möchten sich möglicherweise aus vielen anderen Gründen mit anderen TLS-Bibliotheken als OpenSSL integrieren, z. B. weil OpenSSL Funktionen fehlen (z. B. TLS 1.3-Unterstützung) oder weil OpenSSL einfach zu groß und unhandlich für die Plattform ist (z. B. für eingebettetes Python). Diese Benutzer sind gezwungen, Drittanbieter-Netzwerkbibliotheken zu verwenden, die mit ihrer bevorzugten TLS-Bibliothek interagieren können, oder ihre bevorzugte Bibliothek in die OpenSSL-spezifische ssl-Modul-API zu schirmen.

Darüber hinaus schränkt das ssl-Modul in seiner heutigen Implementierung die Fähigkeit von CPython selbst ein, Unterstützung für alternative TLS-Backends hinzuzufügen oder die OpenSSL-Unterstützung vollständig zu entfernen, falls dies notwendig oder nützlich werden sollte. Das ssl-Modul legt zu viele OpenSSL-spezifische Funktionsaufrufe und -merkmale offen, um sie leicht auf ein alternatives TLS-Backend abbilden zu können.

Vorschlag

Dieses PEP schlägt vor, ein paar neue abstrakte Basisklassen in Python 3.7 einzuführen, um TLS-Funktionalität bereitzustellen, die nicht so stark an OpenSSL gebunden ist. Es wird auch vorgeschlagen, die Module der Standardbibliothek so zu aktualisieren, dass sie, wo immer möglich, nur die von diesen abstrakten Basisklassen bereitgestellte Schnittstelle verwenden. Hier gibt es drei Ziele:

  1. Bereitstellung einer gemeinsamen API-Oberfläche, auf die sich sowohl Kern- als auch Drittentwickler für ihre TLS-Implementierungen ausrichten können. Dies ermöglicht es TLS-Entwicklern, Schnittstellen bereitzustellen, die von den meisten Python-Codes verwendet werden können, und ermöglicht es Netzwergentwicklern, eine Schnittstelle zu haben, auf die sie abzielen können und die mit einer Vielzahl von TLS-Implementierungen funktioniert.
  2. Bereitstellung einer API, bei der wenige bis keine OpenSSL-spezifischen Konzepte durchsickern. Das ssl-Modul hat heute eine Reihe von Eigenheiten, die durch das Durchsickern von OpenSSL-Konzepten in die API verursacht werden: Die neuen ABCs würden diese spezifischen Konzepte entfernen.
  3. Bereitstellung eines Weges für das Kernentwicklungsteam, OpenSSL zu einem von vielen möglichen TLS-Backends zu machen, anstatt zu verlangen, dass es auf einem System vorhanden sein muss, damit Python TLS-Unterstützung hat.

Die vorgeschlagene Schnittstelle ist unten dargelegt.

Schnittstellen

Es gibt mehrere Schnittstellen, die standardisiert werden müssen. Diese Schnittstellen sind:

  1. Konfiguration von TLS, derzeit implementiert durch die Klasse SSLContext im ssl-Modul.
  2. Bereitstellung eines In-Memory-Puffers für die In-Memory-Verschlüsselung oder -Entschlüsselung ohne tatsächliche I/O (notwendig für asynchrone I/O-Modelle), derzeit implementiert durch die Klasse SSLObject im ssl-Modul.
  3. Wrapping eines Socket-Objekts, derzeit implementiert durch die Klasse SSLSocket im ssl-Modul.
  4. Anwendung der TLS-Konfiguration auf die Wrapper-Objekte in (2) und (3). Derzeit wird dies ebenfalls durch die Klasse SSLContext im ssl-Modul implementiert.
  5. Angabe von TLS-Cipher-Suiten. Derzeit gibt es keinen Code dafür in der Standardbibliothek: stattdessen verwendet die Standardbibliothek OpenSSL-Cipher-String-Formate.
  6. Angabe von Anwendungsschichtprotokollen, die während des TLS-Handshakes ausgehandelt werden können.
  7. Angabe von TLS-Versionen.
  8. Fehlerberichterstattung an den Aufrufer, derzeit implementiert durch die Klasse SSLError im ssl-Modul.
  9. Angabe von zu ladenden Zertifikaten, entweder als Client- oder Serverzertifikate.
  10. Angabe, welche Vertrauensdatenbank zur Validierung von Zertifikaten, die von einem entfernten Peer präsentiert werden, verwendet werden soll.
  11. Finden eines Weges, um diese Schnittstellen zur Laufzeit zu erhalten.

Der Einfachheit halber schlägt dieses PEP einen einheitlichen Ansatz für (2) und (3) (d. h. Puffer und Sockets) vor. Die Python-Socket-API ist recht umfangreich, und die Implementierung eines umhüllten Sockets mit demselben Verhalten wie ein regulärer Python-Socket ist eine subtile und knifflige Angelegenheit. Es ist jedoch durchaus möglich, einen *generischen* umhüllten Socket auf Basis von umhüllten Puffern zu implementieren: Das heißt, es ist möglich, einen umhüllten Socket (3) zu schreiben, der für jede Implementierung funktioniert, die (2) bereitstellt. Aus diesem Grund schlägt dieses PEP vor, eine ABC für umhüllte Puffer (2) bereitzustellen, aber eine konkrete Klasse für umhüllte Sockets (3).

Diese Entscheidung hat zur Folge, dass es unmöglich ist, eine kleine Anzahl von TLS-Bibliotheken an diese ABC zu binden, da diese TLS-Bibliotheken *keine* umhüllte Pufferimplementierung bereitstellen können. Die bemerkenswerteste davon scheint derzeit Amazons s2n zu sein, die derzeit keine I/O-Abstraktionsschicht bereitstellt. Diese Bibliothek betrachtet dies jedoch selbst als fehlende Funktion und arbeitet daran, sie hinzuzufügen. Aus diesem Grund ist es sicher anzunehmen, dass eine konkrete Implementierung von (3) auf Basis von (2) eine erhebliche Aufwandsersparnis und ein großartiges Werkzeug für die Korrektheit darstellt. Daher schlägt dieses PEP genau das vor.

(5) erfordert offensichtlich keine abstrakte Basisklasse: Stattdessen erfordert es eine umfangreichere API zur Konfiguration unterstützter Cipher-Suiten, die leicht mit unterstützten Cipher-Suiten für verschiedene Implementierungen aktualisiert werden kann.

(9) stellt ein kniffliges Problem dar, denn im Idealfall würden die privaten Schlüssel, die diesen Zertifikaten zugeordnet sind, nie im Speicher des Python-Prozesses landen (d. h. die TLS-Bibliothek würde mit einem Hardware Security Module (HSM) zusammenarbeiten, um den privaten Schlüssel so bereitzustellen, dass er nicht aus dem Prozessspeicher extrahiert werden kann). Daher müssen wir ein erweiterbares Modell für die Bereitstellung von Zertifikaten bereitstellen, das konkreten Implementierungen die Möglichkeit gibt, dieses höhere Sicherheitsniveau zu bieten, und gleichzeitig eine niedrigere Schwelle für diejenigen Implementierungen ermöglicht, die dies nicht können. Diese niedrigere Schwelle wäre dieselbe wie der Status Quo: das heißt, das Zertifikat kann aus einem In-Memory-Puffer oder aus einer Datei auf der Festplatte geladen werden.

(10) stellt ebenfalls ein Problem dar, da verschiedene TLS-Implementierungen stark variieren, wie Benutzer Vertrauensspeicher auswählen können. Einige Implementierungen haben spezifische Vertrauensspeicherformate, die nur sie verwenden können (wie das OpenSSL CA-Verzeichnisformat, das von c_rehash erstellt wird), und andere erlauben es möglicherweise nicht, einen Vertrauensspeicher anzugeben, der nicht ihren Standard-Vertrauensspeicher enthält.

Aus diesem Grund müssen wir ein Modell bereitstellen, das sehr wenig über die Form von Vertrauensspeichern annimmt. Der Abschnitt „Vertrauensspeicher“ unten beschreibt detaillierter, wie dies erreicht wird.

Schließlich wird diese API die Verantwortlichkeiten aufteilen, die derzeit vom SSLContext-Objekt übernommen werden: insbesondere die Verantwortung für das Halten und Verwalten der Konfiguration und die Verantwortung für die Verwendung dieser Konfiguration zum Erstellen von Wrapper-Objekten.

Dies dient zwangsläufig hauptsächlich der Unterstützung von Funktionen wie Server Name Indication (SNI). In OpenSSL (und damit im ssl-Modul) hat der Server die Möglichkeit, die TLS-Konfiguration zu ändern, als Reaktion darauf, dass der Client dem Server mitteilt, welchen Hostnamen er erreichen möchte. Dies wird meist verwendet, um die Zertifikatkette zu ändern, um die korrekte TLS-Zertifikatkette für den jeweiligen Hostnamen zu präsentieren. Der spezifische Mechanismus, wie dies geschieht, ist die Rückgabe eines neuen SSLContext-Objekts mit der entsprechenden Konfiguration.

Dies ist kein Modell, das sich gut auf andere TLS-Implementierungen abbilden lässt. Stattdessen müssen wir es ermöglichen, einen Rückgabewert vom SNI-Callback bereitzustellen, der verwendet werden kann, um anzuzeigen, welche Konfigurationsänderungen vorgenommen werden sollen. Dies bedeutet die Bereitstellung eines Objekts, das TLS-Konfigurationen speichern kann. Dieses Objekt muss auf spezifische TLSWrappedBuffer- und TLSWrappedSocket-Objekte angewendet werden.

Aus diesem Grund teilen wir die Verantwortung des SSLContext in zwei separate Objekte auf. Das Objekt TLSConfiguration ist ein Objekt, das als Container für die TLS-Konfiguration fungiert: die Objekte ClientContext und ServerContext sind Objekte, die mit einem TLSConfiguration-Objekt instanziiert werden. Alle drei Objekte wären unveränderlich.

Hinweis

Die folgenden API-Deklarationen verwenden durchgängig Typ-Hints zur besseren Lesbarkeit. Einige dieser Typ-Hints können in der Praxis nicht verwendet werden, da sie zirkulär referenziell sind. Betrachten Sie sie eher als Richtlinie denn als Widerspiegelung des endgültigen Codes im Modul.

Konfiguration

Die konkrete Klasse TLSConfiguration definiert ein Objekt, das TLS-Konfigurationen speichern und verwalten kann. Die Ziele dieser Klasse sind wie folgt:

  1. Bereitstellung einer Methode zur Angabe der TLS-Konfiguration, die das Risiko von Tippfehlern vermeidet (dies schließt die Verwendung eines einfachen Wörterbuchs aus).
  2. Bereitstellung eines Objekts, das sicher mit anderen Konfigurationsobjekten verglichen werden kann, um Änderungen der TLS-Konfiguration zu erkennen, für die Verwendung mit dem SNI-Callback.

Diese Klasse ist keine ABC, hauptsächlich weil kein implementierungsspezifisches Verhalten erwartet wird. Die Verantwortung für die Umwandlung eines TLSConfiguration-Objekts in einen nützlichen Satz von Konfigurationen für eine gegebene TLS-Implementierung liegt bei den unten erläuterten Kontext-Objekten.

Diese Klasse hat eine weitere bemerkenswerte Eigenschaft: Sie ist unveränderlich. Dies ist aus mehreren Gründen wünschenswert. Der wichtigste ist, dass diese Objekte als Wörterbuchschlüssel verwendet werden können, was für bestimmte TLS-Backends und deren SNI-Konfigurationen potenziell äußerst wertvoll ist. Darüber hinaus müssen Implementierungen sich keine Sorgen machen, dass ihre Konfigurationsobjekte im Hintergrund geändert werden, was ihnen ermöglicht, Änderungen zwischen ihren konkreten Datenstrukturen und dem Konfigurationsobjekt sorgfältig zu synchronisieren.

Dieses Objekt ist erweiterbar: Das heißt, zukünftige Python-Versionen können Konfigurationsfelder zu diesem Objekt hinzufügen, sobald diese nützlich werden. Aus Gründen der Abwärtskompatibilität werden neue Felder nur an dieses Objekt angehängt. Bestehende Felder werden niemals entfernt, umbenannt oder neu geordnet.

Das Objekt TLSConfiguration würde durch den folgenden Code definiert:

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()


class TLSConfiguration(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):

        if validate_certificates is None:
            validate_certificates = True

        if ciphers is None:
            ciphers = DEFAULT_CIPHER_LIST

        if inner_protocols is None:
            inner_protocols = []

        if lowest_supported_version is None:
            lowest_supported_version = TLSVersion.TLSv1

        if highest_supported_version is None:
            highest_supported_version = TLSVersion.MAXIMUM_SUPPORTED

        return super().__new__(
            cls, validate_certificates, certificate_chain, ciphers,
            inner_protocols, lowest_supported_version,
            highest_supported_version, trust_store, sni_callback
        )

    def update(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.
        """
        if validate_certificates is _DEFAULT_VALUE:
            validate_certificates = self.validate_certificates

        if certificate_chain is _DEFAULT_VALUE:
            certificate_chain = self.certificate_chain

        if ciphers is _DEFAULT_VALUE:
            ciphers = self.ciphers

        if inner_protocols is _DEFAULT_VALUE:
            inner_protocols = self.inner_protocols

        if lowest_supported_version is _DEFAULT_VALUE:
            lowest_supported_version = self.lowest_supported_version

        if highest_supported_version is _DEFAULT_VALUE:
            highest_supported_version = self.highest_supported_version

        if trust_store is _DEFAULT_VALUE:
            trust_store = self.trust_store

        if sni_callback is _DEFAULT_VALUE:
            sni_callback = self.sni_callback

        return self.__class__(
            validate_certificates, certificate_chain, ciphers,
            inner_protocols, lowest_supported_version,
            highest_supported_version, trust_store, sni_callback
        )

Kontext

Wir definieren zwei abstrakte Basisklassen für Kontexte. Diese ABCs definieren Objekte, die die Konfiguration von TLS auf spezifische Verbindungen anwenden lassen. Sie können als Fabriken für TLSWrappedSocket und TLSWrappedBuffer-Objekte betrachtet werden.

Im Gegensatz zum aktuellen ssl-Modul stellen wir zwei Kontextklassen statt einer bereit. Speziell stellen wir die Klassen ClientContext und ServerContext bereit. Dies vereinfacht die APIs (z. B. hat es keinen Sinn, dass der Server den Parameter server_hostname an ssl.SSLContext.wrap_socket übergibt, aber da es nur eine Kontextklasse gibt, ist dieser Parameter immer noch verfügbar) und stellt sicher, dass Implementierungen so früh wie möglich wissen, welche Seite einer TLS-Verbindung sie bedienen werden. Zusätzlich ermöglicht es Implementierungen, sich von einer oder beiden Seiten der Verbindung abzumelden. Zum Beispiel ist SecureTransport unter macOS nicht wirklich für den Servergebrauch gedacht und verfügt über eine enorme Menge an Funktionalität, die für die serverseitige Verwendung fehlt. Dies würde es SecureTransport-Implementierungen ermöglichen, einfach keine konkrete Unterklasse von ServerContext zu definieren, um ihre fehlende Unterstützung zu signalisieren.

Ein weiterer wesentlicher Unterschied zum aktuellen ssl-Modul ist, dass eine Reihe von Flags und Optionen entfernt wurden. Die meisten davon sind selbsterklärend, aber es ist erwähnenswert, dass auto_handshake aus wrap_socket entfernt wurde. Dies wurde entfernt, weil es im Grunde eine seltsame Design-Macke darstellt, die nur minimalen Aufwand auf Kosten einer erhöhten Komplexität sowohl für Benutzer als auch für Implementierer spart. Dieses PEP verlangt, dass alle Benutzer do_handshake nach dem Verbinden explizit aufrufen.

Soweit wie möglich sollten Implementierer darauf abzielen, diese Klassen unveränderlich zu machen: Das heißt, sie sollten es vermeiden, Benutzern zu erlauben, ihren internen Zustand direkt zu verändern, und stattdessen neue Kontexte aus neuen TLSConfiguration-Objekten erstellen. Offensichtlich können die ABCs diese Einschränkung nicht erzwingen, und so versuchen sie es auch nicht.

Die abstrakte Basisklasse Context hat die folgende Klassendefinition:

TLSBufferObject = Union[TLSWrappedSocket, TLSWrappedBuffer]


class _BaseContext(metaclass=ABCMeta):
    @abstractmethod
    def __init__(self, configuration: TLSConfiguration):
        """
        Create a new context object from a given TLS configuration.
        """

    @property
    @abstractmethod
    def configuration(self) -> TLSConfiguration:
        """
        Returns the TLS configuration that was used to create the context.
        """


class ClientContext(_BaseContext):
    def wrap_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)
        return TLSWrappedSocket(socket, buffer)

    @abstractmethod
    def wrap_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``.
        """


class ServerContext(_BaseContext):
    def wrap_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()
        return TLSWrappedSocket(socket, buffer)

    @abstractmethod
    def wrap_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.
        """

Puffer

Die Puffer-Wrapper-ABC wird durch die TLSWrappedBuffer ABC definiert, die die folgende Definition hat:

class TLSWrappedBuffer(metaclass=ABCMeta):
    @abstractmethod
    def read(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.
        """

    @abstractmethod
    def readinto(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.
        """

    @abstractmethod
    def write(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.
        """

    @abstractmethod
    def do_handshake(self) -> None:
        """
        Performs the TLS handshake. Also performs certificate validation
        and hostname verification.
        """

    @abstractmethod
    def cipher(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.
        """

    @abstractmethod
    def negotiated_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
    @abstractmethod
    def context(self) -> Context:
        """
        The ``Context`` object this buffer is tied to.
        """

    @abstractproperty
    def negotiated_tls_version(self) -> Optional[TLSVersion]:
        """
        The version of TLS that has been negotiated on this connection.
        """

    @abstractmethod
    def shutdown(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.
        """

    @abstractmethod
    def receive_from_network(self, data):
        """
        Receives some TLS data from the network and stores it in an
        internal buffer.
        """

    @abstractmethod
    def peek_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.
        """

    @abstractmethod
    def consume_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

Die Socket-Wrapper-Klasse wird eine konkrete Klasse sein, die zwei Elemente in ihrem Konstruktor akzeptiert: ein reguläres Socket-Objekt und ein TLSWrappedBuffer-Objekt. Dieses Objekt wird zu groß sein, um es in diesem PEP neu zu erstellen, wird aber als Teil der Arbeit zur Erstellung des Moduls eingereicht.

Der umhüllte Socket wird die gesamte Socket-API implementieren, wird aber Stub-Implementierungen von Methoden haben, die nur für Sockets mit anderen Typen als SOCK_STREAM funktionieren (z. B. sendto/recvfrom). Diese Einschränkung kann aufgehoben werden, sobald Unterstützung für DTLS zu diesem Modul hinzugefügt wird.

Zusätzlich wird die Socket-Klasse die folgenden *zusätzlichen* Methoden über die regulären Socket-Methoden hinaus enthalten:

class TLSWrappedSocket:
    def do_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.
        """

    def cipher(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.
        """

    def negotiated_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
    def context(self) -> Context:
        """
        The ``Context`` object this socket is tied to.
        """

    def negotiated_tls_version(self) -> Optional[TLSVersion]:
        """
        The version of TLS that has been negotiated on this connection.
        """

    def unwrap(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 Suiten

Die Unterstützung von Cipher-Suiten auf eine wirklich bibliotheksunabhängige Weise ist ein bemerkenswert schwieriges Unterfangen. Verschiedene TLS-Implementierungen haben oft *radikal* unterschiedliche APIs zur Angabe von Cipher-Suiten, aber problematischer ist, dass diese APIs oft in ihrer Fähigkeit und ihrem Stil variieren. Einige Beispiele sind unten gezeigt:

OpenSSL

OpenSSL verwendet ein bekanntes Cipher-String-Format. Dieses Format wurde als Konfigurationssprache von den meisten Produkten übernommen, die OpenSSL verwenden, einschließlich Python. Dieses Format ist relativ leicht zu lesen, hat aber eine Reihe von Nachteilen: Es ist ein String, was die Eingabe fehlerhafter Eingaben sehr einfach macht; es fehlt eine detaillierte Validierung, was bedeutet, dass es möglich ist, OpenSSL so zu konfigurieren, dass es überhaupt keine Cipher aushandeln kann; und es ermöglicht die Angabe von Cipher-Suiten auf verschiedene Arten, die das Parsen erschweren. Das größte Problem mit diesem Format ist, dass es keine formelle Spezifikation dafür gibt, was bedeutet, dass der einzige Weg, einen gegebenen String so zu parsen, wie es OpenSSL tun würde, darin besteht, OpenSSL den String parsen zu lassen.

Die OpenSSL-Cipher-Strings können so aussehen:

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

Dieser String demonstriert einige der Komplexität des OpenSSL-Formats. Zum Beispiel ist es möglich, dass ein Eintrag mehrere Cipher-Suiten angibt: Der Eintrag ECDH+AESGCM bedeutet „alle Cipher-Suiten, die sowohl Elliptic-Curve Diffie-Hellman-Schlüsselaustausch als auch AES im Galois Counter Mode umfassen“. Genauer gesagt wird dies zu vier Cipher-Suiten expandiert:

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

Das macht das Parsen eines vollständigen OpenSSL-Cipher-Strings extrem schwierig. Hinzu kommt, dass es andere Meta-Zeichen gibt, wie z. B. „!“ (schließe alle Cipher-Suiten aus, die diesem Kriterium entsprechen, auch wenn sie sonst enthalten wären: „!MD5“ bedeutet, dass keine Cipher-Suiten, die den MD5-Hash-Algorithmus verwenden, enthalten sein dürfen), „-“ (schließe übereinstimmende Cipher aus, wenn sie bereits enthalten waren, erlaube ihnen aber, später wieder hinzugefügt zu werden, wenn sie wieder enthalten sind) und „+“ (füge die übereinstimmenden Cipher hinzu, aber platziere sie am Ende der Liste), und Sie erhalten ein *extrem* komplexes Format zum Parsen. Zusätzlich zu dieser Komplexität ist zu beachten, dass das tatsächliche Ergebnis von der OpenSSL-Version abhängt, da ein OpenSSL-Cipher-String gültig ist, solange er mindestens einen Cipher enthält, den OpenSSL erkennt.

OpenSSL verwendet auch andere Namen für seine Cipher als die Namen, die in den relevanten Spezifikationen verwendet werden. Siehe die Manpage für ciphers(1) für weitere Details.

Die tatsächliche API innerhalb von OpenSSL für den Cipher-String ist einfach:

char *cipher_list = <some cipher list>;
int rc = SSL_CTX_set_cipher_list(context, cipher_list);

Das bedeutet, dass jedes Format, das von diesem Modul verwendet wird, in einen OpenSSL-Cipher-String konvertiert werden muss, um es mit OpenSSL zu verwenden.

SecureTransport

SecureTransport ist die TLS-Systembibliothek für macOS. Diese Bibliothek ist in vielerlei Hinsicht erheblich eingeschränkter als OpenSSL, da sie eine viel eingeschränktere Benutzerklasse hat. Eine dieser wesentlichen Einschränkungen betrifft die Kontrolle der unterstützten Cipher-Suiten.

Cipher in SecureTransport werden durch eine C enum dargestellt. Diese Enum hat einen Eintrag pro Cipher-Suite, ohne aggregierte Einträge, was bedeutet, dass es nicht möglich ist, die Bedeutung eines OpenSSL-Cipher-Strings wie „ECDH+AESGCM“ zu reproduzieren, ohne handkodiert zu haben, in welche Kategorien jedes Enum-Mitglied fällt.

Die Namen der meisten Enum-Mitglieder stimmen jedoch mit den formalen Namen der Cipher-Suiten überein: Das heißt, die Cipher-Suite, die OpenSSL „ECDHE-ECDSA-AES256-GCM-SHA384“ nennt, heißt in SecureTransport „TLS_ECDHE_ECDHSA_WITH_AES_256_GCM_SHA384“.

Die API zur Konfiguration von Cipher-Suiten in SecureTransport ist einfach:

SSLCipherSuite ciphers[] = {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ...};
OSStatus status = SSLSetEnabledCiphers(context, ciphers, sizeof(ciphers));
SChannel

SChannel ist die TLS-Systembibliothek für Windows.

SChannel hat extrem eingeschränkte Unterstützung für die Kontrolle verfügbarer TLS-Cipher-Suiten und übernimmt zusätzlich eine dritte Methode zur Ausdrucksweise, welche TLS-Cipher-Suiten unterstützt werden.

Spezifisch definiert SChannel eine Reihe von ALG_ID-Konstanten (C unsigned ints). Jede dieser Konstanten bezieht sich nicht auf eine gesamte Cipher-Suite, sondern auf einen einzelnen Algorithmus. Einige Beispiele sind CALG_3DES und CALG_AES_256, die sich auf den Bulk-Verschlüsselungsalgorithmus beziehen, der in einer Cipher-Suite verwendet wird, CALG_DH_EPHEM und CALG_RSA_KEYX, die sich auf einen Teil des Schlüsselaustauschalgorithmus beziehen, der in einer Cipher-Suite verwendet wird, CALG_SHA1 und CALG_MD5, die sich auf den Message Authentication Code beziehen, der in einer Cipher-Suite verwendet wird, und CALG_ECDSA und CALG_RSA_SIGN, die sich auf die Signaturteile des Schlüsselaustauschalgorithmus beziehen.

Dies kann als die Hälfte der Funktionalität von OpenSSL betrachtet werden, die SecureTransport nicht hat: SecureTransport erlaubt nur die Angabe exakter Cipher-Suiten, während SChannel nur die Angabe von *Teilen* der Cipher-Suite erlaubt, während OpenSSL beides erlaubt.

Die Bestimmung, welche Cipher-Suiten bei einer gegebenen Verbindung zulässig sind, erfolgt durch Angabe eines Zeigers auf ein Array dieser ALG_ID-Konstanten. Dies bedeutet, dass jede geeignete API es dem Python-Code ermöglichen muss, zu bestimmen, welche ALG_ID-Konstanten bereitgestellt werden müssen.

Network Security Services (NSS)

NSS ist Mozillas Krypto- und TLS-Bibliothek. Sie wird in Firefox, Thunderbird und als Alternative zu OpenSSL in mehreren Bibliotheken verwendet, z. B. curl.

Standardmäßig kommt NSS mit sicherer Konfiguration von erlaubten Ciphern. Auf einigen Plattformen wie Fedora ist die Liste der aktivierten Cipher global in einer Systemrichtlinie konfiguriert. Im Allgemeinen sollten Anwendungen Cipher-Suiten nicht ändern, es sei denn, sie haben dafür spezifische Gründe.

NSS verfügt sowohl über prozessglobale als auch über pro-Verbindungs-Einstellungen für Cipher-Suiten. Es gibt kein Konzept wie SSLContext wie bei OpenSSL. Ein SSLContext-ähnliches Verhalten kann leicht emuliert werden. Insbesondere können Cipher global mit SSL_CipherPrefSetDefault(PRInt32 cipher, PRBool enabled) und für eine Verbindung mit SSL_CipherPrefSet(PRFileDesc *fd, PRInt32 cipher, PRBool enabled) aktiviert oder deaktiviert werden. Die Cipher PRInt32-Nummer ist eine vorzeichenbehaftete 32-Bit-Ganzzahl, die direkt einer registrierten IANA-ID entspricht, z. B. ist 0x1301 TLS_AES_128_GCM_SHA256. Im Gegensatz zu OpenSSL ist die Präferenzreihenfolge der Cipher fest und kann zur Laufzeit nicht geändert werden.

Wie SecureTransport hat NSS keine API für aggregierte Einträge. Einige Verbraucher von NSS haben benutzerdefinierte Mappings von OpenSSL-Cipher-Namen und Regeln zu NSS-Ciphern implementiert, z. B. mod_nss.

Vorgeschlagene Schnittstelle

Die vorgeschlagene Schnittstelle für das neue Modul ist durch die kombinierte Menge der Einschränkungen der oben genannten Implementierungen beeinflusst. Insbesondere da jede Implementierung *außer* OpenSSL verlangt, dass jeder einzelne Cipher bereitgestellt wird, bleibt nur die Bereitstellung dieses kleinsten gemeinsamen Nenners übrig.

Der einfachste Ansatz ist die Bereitstellung eines Aufzählungstyps, der eine große Teilmenge der für TLS definierten Cipher-Suiten enthält. Die Werte der Enum-Mitglieder sind ihre zwei Byte-Cipher-Identifikatoren, wie sie im TLS-Handshake verwendet werden, gespeichert als 16-Bit-Ganzzahl. Die Namen der Enum-Mitglieder sind ihre IANA-registrierten Cipher-Suite-Namen.

Derzeit enthält die IANA Cipher Suite Registry über 320 Cipher-Suiten. Ein großer Teil der Cipher-Suiten ist für TLS-Verbindungen zu Netzwerkdiensten irrelevant. Andere Suiten spezifizieren veraltete und unsichere Algorithmen, die von neueren Versionen von Implementierungen nicht mehr bereitgestellt werden. Das Enum enthält keine Cipher mit

  • Schlüsselaustausch: NULL, Kerberos (KRB5), Pre-Shared Key (PSK), Secure Remote Transport (TLS-SRP)
  • Authentifizierung: NULL, anonym, Export-Grade, Kerberos (KRB5), Pre-Shared Key (PSK), Secure Remote Transport (TLS-SRP), DSA-Zertifikat (DSS)
  • Verschlüsselung: NULL, ARIA, DES, RC2, Export-Grade 40bit
  • PRF: MD5
  • SCSV-Cipher-Suiten

3DES, RC4, SEED und IDEA sind für Legacy-Anwendungen enthalten. Weiterhin sind fünf zusätzliche Cipher-Suiten aus dem TLS 1.3 Entwurf (draft-ietf-tls-tls13-18) enthalten. TLS 1.3 teilt keine Cipher-Suiten mit TLS 1.2 und früheren Versionen. Das resultierende Enum wird ungefähr 110 Suiten enthalten.

Aufgrund dieser Einschränkungen, und weil das Enum nicht jede definierte Cipher enthält, und um auch zukunftsorientierte Anwendungen zu ermöglichen, akzeptieren alle Teile dieser API, die CipherSuite-Objekte akzeptieren, auch rohe 16-Bit-Ganzzahlen direkt.

Anstatt dieses Enum manuell zu befüllen, haben wir ein TLS-Enum-Skript, das es aus Christian Heimes’ tlsdb JSON-Datei (Warnung: große Datei) und der IANA Cipher Suite Registry erstellt. Die TLSDB eröffnet auch die Möglichkeit, die API mit zusätzlichen Abfragefunktionen zu erweitern, z. B. zur Bestimmung, welche TLS-Versionen welche Cipher unterstützen, falls sich diese Funktionalität als nützlich oder notwendig erweist.

Wenn Benutzer diesen Ansatz als umständlich empfinden, kann eine zukünftige Erweiterung dieser API Hilfsprogramme bereitstellen, die die Aggregationsfunktionalität von OpenSSL wieder einführen können.

class CipherSuite(IntEnum):
    TLS_RSA_WITH_RC4_128_SHA = 0x0005
    TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007
    TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a
    TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010
    TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016
    TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f
    TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031
    TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033
    TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035
    TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037
    TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039
    TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003c
    TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003d
    TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003f
    TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041
    TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043
    TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045
    TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067
    TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069
    TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006b
    TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084
    TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086
    TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088
    TLS_RSA_WITH_SEED_CBC_SHA = 0x0096
    TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098
    TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009a
    TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009c
    TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009d
    TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009e
    TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009f
    TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00a0
    TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00a1
    TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00ba
    TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00bc
    TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00be
    TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00c0
    TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00c2
    TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00c4
    TLS_AES_128_GCM_SHA256 = 0x1301
    TLS_AES_256_GCM_SHA384 = 0x1302
    TLS_CHACHA20_POLY1305_SHA256 = 0x1303
    TLS_AES_128_CCM_SHA256 = 0x1304
    TLS_AES_128_CCM_8_SHA256 = 0x1305
    TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xc002
    TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xc003
    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xc004
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xc005
    TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xc007
    TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xc008
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xc009
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a
    TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xc00c
    TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xc00d
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xc00e
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xc00f
    TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xc011
    TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xc012
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xc023
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc024
    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xc025
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc026
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xc027
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xc028
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xc029
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xc02a
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xc02b
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02c
    TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xc02d
    TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02e
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030
    TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xc031
    TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xc032
    TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xc072
    TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xc073
    TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xc074
    TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xc075
    TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xc076
    TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xc077
    TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xc078
    TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xc079
    TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc07a
    TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc07b
    TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc07c
    TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc07d
    TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc07e
    TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc07f
    TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc086
    TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc087
    TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc088
    TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc089
    TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc08a
    TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc08b
    TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xc08c
    TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xc08d
    TLS_RSA_WITH_AES_128_CCM = 0xc09c
    TLS_RSA_WITH_AES_256_CCM = 0xc09d
    TLS_DHE_RSA_WITH_AES_128_CCM = 0xc09e
    TLS_DHE_RSA_WITH_AES_256_CCM = 0xc09f
    TLS_RSA_WITH_AES_128_CCM_8 = 0xc0a0
    TLS_RSA_WITH_AES_256_CCM_8 = 0xc0a1
    TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xc0a2
    TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xc0a3
    TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xc0ac
    TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xc0ad
    TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xc0ae
    TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xc0af
    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8
    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca9
    TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xccaa

Enum-Mitglieder können auf OpenSSL-Chiffriernamen abgebildet werden

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

Für SecureTransport beziehen sich diese Enum-Mitglieder direkt auf die Werte der Cipher-Suite-Konstanten. Beispielsweise definiert SecureTransport das Enum-Mitglied TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 mit dem Wert 0xC02C. Nicht zufällig ist dies identisch mit seinem Wert im obigen Enum. Dies macht die Abbildung zwischen SecureTransport und dem obigen Enum sehr einfach.

Für SChannel gibt es keine einfache direkte Abbildung, da SChannel Chiffren anstelle von Cipher-Suites konfiguriert. Dies stellt ein fortlaufendes Problem für SChannel dar, nämlich dass es im Vergleich zu anderen TLS-Implementierungen sehr schwierig ist, es auf eine bestimmte Weise zu konfigurieren.

Für die Zwecke dieses PEP muss jede SChannel-Implementierung bestimmen, welche Chiffren basierend auf den Enum-Mitgliedern ausgewählt werden sollen. Dies kann offener sein als die tatsächliche Cipher-Suite-Liste zulässt, oder es kann restriktiver sein, abhängig von den Entscheidungen der Implementierung. Dieses PEP empfiehlt, dass es restriktiver ist, aber dies kann natürlich nicht erzwungen werden.

Protokollverhandlung

Sowohl NPN als auch ALPN ermöglichen die Protokollverhandlung als Teil des HTTP/2-Handshakes. Obwohl NPN und ALPN auf grundlegender Ebene auf Byte-Strings aufbauen, sind stringbasierte APIs oft problematisch, da sie Tippfehler ermöglichen, die schwer zu erkennen sind.

Aus diesem Grund würde dieses Modul einen Typ definieren, den Protokollverhandlungs-Implementierungen übergeben und übergeben können. Dieser Typ würde einen Byte-String kapseln, um Aliase für bekannte Protokolle zu ermöglichen. Dies ermöglicht es uns, die Probleme zu vermeiden, die inhärenten Tippfehlern bei bekannten Protokollen innewohnen, während die volle Erweiterbarkeit der Protokollverhandlungsschicht erhalten bleibt, wenn dies von Benutzern benötigt wird, indem Byte-Strings direkt übergeben werden.

class NextProtocol(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-Versionen

Es ist oft nützlich, die zu unterstützenden TLS-Versionen einschränken zu können. Es gibt viele Sicherheitsvorteile, wenn alte TLS-Versionen nicht verwendet werden, und einige schlecht funktionierende Server behandeln TLS-Clients, die die Unterstützung für neuere Versionen bewerben, schlecht.

Der folgende enumerierte Typ kann verwendet werden, um TLS-Versionen zu filtern. Zukunftsweisende Anwendungen sollten fast nie eine maximale TLS-Version festlegen, es sei denn, sie müssen es unbedingt, da ein TLS-Backend, das neuer ist als das Python, das es verwendet, TLS-Versionen unterstützen kann, die nicht in diesem enumerierten Typ enthalten sind.

Zusätzlich definiert dieser enumerierte Typ zwei zusätzliche Flags, die immer verwendet werden können, um entweder die niedrigste oder die höchste von einer Implementierung unterstützte TLS-Version anzufordern.

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

Fehler

Dieses Modul würde vier Basisklassen für die Fehlerbehandlung definieren. Im Gegensatz zu vielen der hier definierten anderen Klassen sind diese Klassen nicht abstrakt, da sie kein Verhalten haben. Sie existieren lediglich, um bestimmte gängige Verhaltensweisen zu signalisieren. Backends sollten diese Ausnahmen in ihren eigenen Paketen unterklassifizieren, müssen aber kein eigenes Verhalten dafür definieren.

Im Allgemeinen sollten konkrete Implementierungen diese Ausnahmen unterklassifizieren, anstatt sie direkt auszulösen. Dies erleichtert die Bestimmung, welche konkrete TLS-Implementierung während der Fehlersuche bei unerwarteten Fehlern verwendet wird. Dies ist jedoch nicht zwingend erforderlich.

Die Definitionen der Fehler sind unten aufgeführt

class TLSError(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.
    """

class WantWriteError(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.
    """

class WantReadError(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.
    """

class RaggedEOF(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.
    """

Zertifikate

Dieses Modul würde eine abstrakte X509-Zertifikatsklasse definieren. Diese Klasse hätte fast kein Verhalten, da das Ziel dieses Moduls nicht darin besteht, alle möglichen relevanten kryptografischen Funktionen bereitzustellen, die von X509-Zertifikaten bereitgestellt werden könnten. Stattdessen benötigen wir lediglich die Möglichkeit, die Quelle eines Zertifikats an eine konkrete Implementierung zu signalisieren.

Aus diesem Grund definiert diese Zertifikatsimplementierung nur Konstruktoren. Im Wesentlichen könnte das Zertifikatsobjekt in diesem Modul so abstrakt sein wie ein Handle, das verwendet werden kann, um ein bestimmtes Zertifikat zu lokalisieren.

Konkrete Implementierungen können alternative Konstruktoren bereitstellen, z. B. um Zertifikate aus HSMs zu laden. Wenn sich eine gemeinsame Schnittstelle dafür herausbildet, kann dieses Modul aktualisiert werden, um auch für diesen Anwendungsfall einen Standardkonstruktor bereitzustellen.

Konkrete Implementierungen sollten darauf abzielen, dass Zertifikatsobjekte nach Möglichkeit hashbar sind. Dies trägt dazu bei, dass TLSConfiguration-Objekte, die mit einer einzelnen konkreten Implementierung verwendet werden, ebenfalls hashbar sind.

class Certificate(metaclass=ABCMeta):
    @abstractclassmethod
    def from_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.
        """

    @abstractclassmethod
    def from_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 Schlüssel

Dieses Modul würde eine abstrakte Klasse für private Schlüssel definieren. Ähnlich wie die Zertifikatsklasse hat diese Klasse fast kein Verhalten, um den konkreten Implementierungen so viel Freiheit wie möglich zu geben, Schlüssel sorgfältig zu behandeln.

Diese Klasse hat alle Vorbehalte der Certificate-Klasse.

class PrivateKey(metaclass=ABCMeta):
    @abstractclassmethod
    def from_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.
        """

    @abstractclassmethod
    def from_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``.
        """

Vertrauensspeicher

Wie oben diskutiert, stellt das Laden eines Trust Stores ein Problem dar, da verschiedene TLS-Implementierungen stark variieren, wie sie es Benutzern ermöglichen, Trust Stores auszuwählen. Aus diesem Grund müssen wir ein Modell bereitstellen, das sehr wenige Annahmen über die Form von Trust Stores macht.

Dieses Problem ist dasselbe wie das, das die Typen Certificate und PrivateKey lösen müssen. Aus diesem Grund verwenden wir genau dasselbe Modell, indem wir einen undurchsichtigen Typ erstellen, der die verschiedenen Mittel kapseln kann, mit denen TLS-Backends einen Trust Store öffnen können.

Eine bestimmte TLS-Implementierung muss nicht alle Konstruktoren implementieren. Es wird jedoch dringend empfohlen, dass eine bestimmte TLS-Implementierung nach Möglichkeit den system-Konstruktor bereitstellt, da dies der am häufigsten verwendete Validierungs-Trust-Store ist. Konkrete Implementierungen können auch eigene Konstruktoren hinzufügen.

Konkrete Implementierungen sollten darauf abzielen, dass TrustStore-Objekte nach Möglichkeit hashbar sind. Dies trägt dazu bei, dass TLSConfiguration-Objekte, die mit einer einzelnen konkreten Implementierung verwendet werden, ebenfalls hashbar sind.

class TrustStore(metaclass=ABCMeta):
    @abstractclassmethod
    def system(cls) -> TrustStore:
        """
        Returns a TrustStore object that represents the system trust
        database.
        """

    @abstractclassmethod
    def from_pem_file(cls, path: Union[pathlib.Path, bytes, str]) -> TrustStore:
        """
        Initializes a trust store from a single file full of PEMs.
        """

Laufzeitzugriff

Ein nicht ungewöhnlicher Anwendungsfall für Bibliotheksbenutzer ist es, der Bibliothek die Kontrolle über die TLS-Konfiguration zu überlassen, aber auszuwählen, welches Backend verwendet wird. Beispielsweise möchten Benutzer von Requests zwischen OpenSSL oder einer plattformnativen Lösung unter Windows und macOS wählen können oder zwischen OpenSSL und NSS unter einigen Linux-Plattformen. Diese Benutzer kümmern sich jedoch möglicherweise nicht darum, wie genau ihre TLS-Konfiguration erfolgt.

Dies stellt ein Problem dar: Wie kann eine Bibliothek anhand einer beliebigen konkreten Implementierung herausfinden, wie Zertifikate in den Trust Store geladen werden? Es gibt zwei Optionen: Entweder müssen alle konkreten Implementierungen einem bestimmten Namensschema entsprechen, oder wir können eine API bereitstellen, die es ermöglicht, diese Objekte abzurufen.

Dieses PEP schlägt vor, den zweiten Ansatz zu verfolgen. Dies gewährt den konkreten Implementierungen die größte Freiheit, ihren Code nach eigenem Ermessen zu strukturieren, und verlangt nur, dass sie ein einzelnes Objekt mit den entsprechenden Eigenschaften bereitstellen. Benutzer können dann dieses „Backend“-Objekt an Bibliotheken übergeben, die es unterstützen, und diese Bibliotheken kümmern sich um die Konfiguration und Verwendung der konkreten Implementierung.

Alle konkreten Implementierungen müssen eine Methode zur Erlangung eines Backend-Objekts bereitstellen. Das Backend-Objekt kann ein globaler Singleton sein oder von einem aufrufbaren Objekt erstellt werden, wenn dies von Vorteil ist.

Das Backend-Objekt hat die folgende Definition

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

Jede der Eigenschaften muss die konkrete Implementierung der relevanten ABC bereitstellen. Dies stellt sicher, dass Code wie dieser für jedes Backend funktioniert

trust_store = backend.trust_store.system()

Änderungen an der Standardbibliothek

Die Teile der Standardbibliothek, die mit TLS interagieren, sollten überarbeitet werden, um diese ABCs zu verwenden. Dies ermöglicht ihnen die Zusammenarbeit mit anderen TLS-Backends. Dazu gehören die folgenden Module

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

Migration des ssl-Moduls

Natürlich müssen wir das ssl-Modul selbst erweitern, um diesen ABCs zu entsprechen. Diese Erweiterung erfolgt in Form neuer Klassen, möglicherweise in einem völlig neuen Modul. Dies ermöglicht es Anwendungen, die das aktuelle ssl-Modul nutzen, dies weiterhin zu tun, während die neuen APIs für Anwendungen und Bibliotheken aktiviert werden, die sie verwenden möchten.

Im Allgemeinen wird eine Migration vom ssl-Modul zu den neuen ABCs nicht als Eins-zu-Eins erwartet. Dies ist normalerweise akzeptabel: Die meisten Werkzeuge, die das ssl-Modul verwenden, verbergen es vor dem Benutzer, und so sollte die Refaktorierung zur Verwendung des neuen Moduls unsichtbar sein.

Ein spezifisches Problem ergibt sich jedoch aus Bibliotheken oder Anwendungen, die Ausnahmen aus dem ssl-Modul preisgeben, entweder als Teil ihrer definierten API oder versehentlich (was leicht passieren kann). Benutzer dieser Tools haben möglicherweise Code geschrieben, der Ausnahmen aus dem ssl-Modul toleriert und behandelt: Eine Migration zu den hier vorgestellten ABCs würde potenziell dazu führen, dass die oben definierten Ausnahmen stattdessen ausgelöst werden, und bestehende except-Blöcke würden diese nicht abfangen.

Aus diesem Grund würde ein Teil der Migration des ssl-Moduls erfordern, dass die Ausnahmen im ssl-Modul Aliase für die oben definierten haben. Das heißt, sie würden erfordern, dass die folgenden Anweisungen alle erfolgreich sind

assert ssl.SSLError is tls.TLSError
assert ssl.SSLWantReadError is tls.WantReadError
assert ssl.SSLWantWriteError is tls.WantWriteError

Die genauen Mechanismen, wie dies geschehen wird, liegen außerhalb des Rahmens dieses PEP, da sie aufgrund der Tatsache, dass die aktuellen ssl-Ausnahmen in C-Code definiert sind, komplexer sind, aber weitere Details finden sich in einer E-Mail von Christian Heimes an die Security-SIG.

Zukunft

Zukünftige wichtige TLS-Funktionen können Überarbeitungen dieser ABCs erfordern. Diese Überarbeitungen sollten vorsichtig vorgenommen werden: Viele Backends können möglicherweise nicht schnell vorankommen und werden durch Änderungen an diesen ABCs ungültig. Dies ist akzeptabel, aber wo immer möglich, sollten Features, die für einzelne Implementierungen spezifisch sind, nicht zu den ABCs hinzugefügt werden. Die ABCs sollten sich auf High-Level-Beschreibungen von IETF-spezifizierten Funktionen beschränken.

Gut begründete Erweiterungen dieser API sollten jedoch absolut vorgenommen werden. Der Fokus dieser API liegt darin, eine vereinheitlichende Option für die Konfiguration des kleinsten gemeinsamen Nenners für die Python-Community bereitzustellen. TLS ist kein statisches Ziel, und mit der Weiterentwicklung von TLS muss sich auch diese API weiterentwickeln.

Danksagungen

Dieses Dokument hat eine ausführliche Prüfung von einer Reihe von Personen aus der Community erhalten, die es wesentlich mitgestaltet haben. Eine detaillierte Prüfung wurde durchgeführt von

  • 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

Weitere Prüfungen wurden von den Mailinglisten Security-SIG und python-ideas durchgeführt.


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

Zuletzt geändert: 2025-02-01 08:59:27 GMT