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
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:
- 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.
- 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. - 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:
- Konfiguration von TLS, derzeit implementiert durch die Klasse SSLContext im
ssl-Modul. - 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. - Wrapping eines Socket-Objekts, derzeit implementiert durch die Klasse SSLSocket im
ssl-Modul. - Anwendung der TLS-Konfiguration auf die Wrapper-Objekte in (2) und (3). Derzeit wird dies ebenfalls durch die Klasse SSLContext im
ssl-Modul implementiert. - Angabe von TLS-Cipher-Suiten. Derzeit gibt es keinen Code dafür in der Standardbibliothek: stattdessen verwendet die Standardbibliothek OpenSSL-Cipher-String-Formate.
- Angabe von Anwendungsschichtprotokollen, die während des TLS-Handshakes ausgehandelt werden können.
- Angabe von TLS-Versionen.
- Fehlerberichterstattung an den Aufrufer, derzeit implementiert durch die Klasse SSLError im
ssl-Modul. - Angabe von zu ladenden Zertifikaten, entweder als Client- oder Serverzertifikate.
- Angabe, welche Vertrauensdatenbank zur Validierung von Zertifikaten, die von einem entfernten Peer präsentiert werden, verwendet werden soll.
- 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:
- Bereitstellung einer Methode zur Angabe der TLS-Konfiguration, die das Risiko von Tippfehlern vermeidet (dies schließt die Verwendung eines einfachen Wörterbuchs aus).
- 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.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0543.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT