PEP 543 – Python을 위한 통합 TLS API
- Author:
- Cory Benfield <cory at lukasa.co.uk>, Christian Heimes <christian at python.org>
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 17-Oct-2016
- Python-Version:
- 3.7
- Post-History:
- 11-Jan-2017, 19-Jan-2017, 02-Feb-2017, 09-Feb-2017
- Superseded-By:
- 748
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 추상 베이스 클래스 모음 형태의 표준 TLS 인터페이스를 정의합니다. 이 인터페이스를 통해 Python 구현과 서드파티 라이브러리는 OpenSSL 이외의 TLS 라이브러리에 대한 바인딩을 제공할 수 있으며, 이러한 바인딩은 Python 표준 라이브러리가 제공하는 인터페이스를 기대하는 도구에서 사용할 수 있습니다. 이는 Python 생태계의 OpenSSL 의존성을 줄이는 것을 목표로 합니다.
결정
2020-06-25: 한 저자의 현재 동의와 다른 저자의 과거 동의에 따라, 기반 운영 체제 API의 변경으로 이 PEP를 철회합니다.
근거
21세기에 들어서면서 견고하고 사용자 친화적인 TLS 지원이 널리 사용되는 모든 프로그래밍 언어 생태계에서 매우 중요한 부분이라는 사실이 점점 더 분명해졌습니다. 존속 기간 대부분 동안 Python 생태계에서 이 역할은 주로 ssl module이 담당해 왔으며, 이 모듈은 OpenSSL library를 위한 Python API를 제공합니다.
ssl 모듈은 Python 표준 라이브러리와 함께 배포되므로, Python에서 TLS를 처리하는 압도적으로 가장 인기 있는 방법이 되었습니다. 표준 라이브러리와 Python 패키지 색인 모두에 있는 Python 라이브러리의 압도적 다수가 TLS 연결을 위해 ssl 모듈에 의존합니다.
안타깝게도 ssl 모듈의 우위는 예상하지 못한 여러 부작용을 초래했으며, 그 결과 Python 생태계 전체가 OpenSSL에 긴밀하게 묶이게 되었습니다. 이로 인해 Python 사용자는 대체 TLS 구현보다 사용자 경험이 나쁠 수 있는 상황에서도 OpenSSL을 사용해야 했으며, 이는 인지 부담을 초래하고 “플랫폼 네이티브” 경험을 제공하기 어렵게 만듭니다.
문제점
ssl 모듈이 표준 라이브러리에 내장되어 있다는 사실은 모든 표준 라이브러리 Python 네트워킹 라이브러리가 해당 Python 구현이 링크된 OpenSSL에 전적으로 의존한다는 것을 의미합니다. 이로 인해 다음과 같은 문제가 발생합니다.
- 새로운 OpenSSL을 사용하기 위해 Python을 다시 컴파일하지 않고는 더 높은 보안 수준의 새로운 TLS를 활용하기 어렵습니다. OpenSSL에 대한 서드파티 바인딩(예: pyOpenSSL)이 있더라도, 이를 표준 라이브러리가 이해하는 형식으로 심을 적용해야 하므로 이를 사용하려는 프로젝트는 상당한 호환성 계층을 유지해야 합니다.
- Windows용 Python 배포판은 OpenSSL 사본과 함께 제공해야 합니다. 이로 인해 CPython 개발 팀은 OpenSSL 재배포자의 입장에 놓이며, OpenSSL 취약점이 공개될 때 Windows Python 배포판에 보안 업데이트를 제공해야 할 수도 있습니다.
- macOS용 Python 배포판은 OpenSSL 사본과 함께 제공하거나 시스템 OpenSSL 라이브러리에 링크해야 합니다. Apple은 시스템 OpenSSL 라이브러리에 링크하는 것을 공식적으로 더 이상 사용하지 않도록 했으며, 그렇지 않았더라도 작성 시점을 기준으로 해당 라이브러리 버전은 거의 1년 동안 업스트림의 지원을 받지 못했습니다. CPython 개발 팀은 python.org에서 제공하는 Python에 최신 OpenSSL을 함께 제공하기 시작했지만, 이는 Windows와 동일한 문제를 안고 있습니다.
- Windows와 macOS를 포함하되 이에 국한되지 않는 많은 시스템은 시스템 인증서 저장소를 OpenSSL에서 사용할 수 있도록 하지 않습니다. 이로 인해 사용자는 다른 곳에서 신뢰 루트를 얻거나(예: certifi) 시스템 신뢰 저장소를 어떤 형태로든 내보내려고 해야 합니다.
certifi 에 의존하는 것은 대부분의 시스템 관리자가 PyPI에서 보안에 중요한 소프트웨어 업데이트를 받을 것으로 기대하지 않으므로 이상적이지 않습니다. 또한 certifi 신뢰 번들에 사용자 지정 루트를 포함하도록 확장하거나 certifi 모델을 사용하여 신뢰를 중앙에서 관리하기도 쉽지 않습니다.
시스템 인증서 저장소를 어떤 형태로든 OpenSSL에서 사용할 수 있게 한 경우에도, OpenSSL은 플랫폼 네이티브 TLS 구현과 다른 검증 검사를 수행하므로 사용 경험은 여전히 표준에 미치지 못합니다. 이로 인해 사용자는 브라우저나 다른 플랫폼 네이티브 도구에서 경험하는 동작과 Python에서 경험하는 동작이 다를 수 있으며, 문제를 해결할 방법이 거의 또는 전혀 없을 수 있습니다.
- 사용자가 OpenSSL 이외의 TLS 라이브러리와 통합하려는 데에는 OpenSSL에 기능이 없기 때문(예: TLS 1.3 지원)이나 OpenSSL이 해당 플랫폼에 단순히 너무 크고 다루기 어렵기 때문(예: 임베디드 Python) 등 여러 다른 이유가 있을 수 있습니다. 이러한 사용자는 선호하는 TLS 라이브러리와 상호 작용할 수 있는 서드파티 네트워킹 라이브러리를 사용하거나, 선호하는 라이브러리를 OpenSSL 전용
ssl모듈 API에 심으로 적용해야 합니다.
또한 현재 구현된 ssl 모듈은 대체 TLS 백엔드에 대한 지원을 추가하거나 OpenSSL 지원을 완전히 제거하는 CPython 자체의 능력을 제한합니다. 이러한 작업이 필요하거나 유용해지는 경우에도 그렇습니다. ssl 모듈은 대체 TLS 백엔드에 쉽게 매핑하기에는 너무 많은 OpenSSL 전용 함수 호출과 기능을 노출합니다.
제안
이 PEP는 OpenSSL에 강하게 결합되지 않은 TLS 기능을 제공하기 위해 Python 3.7에 몇 가지 새로운 추상 베이스 클래스를 도입할 것을 제안합니다. 또한 가능한 경우 표준 라이브러리 모듈을 이러한 추상 베이스 클래스가 노출하는 인터페이스만 사용하도록 업데이트할 것을 제안합니다. 여기에는 세 가지 목표가 있습니다.
- 핵심 개발자와 서드파티 개발자 모두가 자신의 TLS 구현을 대상으로 삼을 수 있는 공통 API 표면을 제공하는 것입니다. 이를 통해 TLS 개발자는 대부분의 Python 코드에서 사용할 수 있는 인터페이스를 제공할 수 있으며, 네트워크 개발자는 광범위한 TLS 구현에서 작동하는 대상으로 삼을 인터페이스를 확보할 수 있습니다.
- OpenSSL 전용 개념이 거의 또는 전혀 새어 나오지 않는 API를 제공하는 것입니다. 오늘날
ssl모듈의 API에는 OpenSSL 개념이 노출되어 발생한 여러 결함이 있습니다. 새로운 추상 베이스 클래스는 이러한 특정 개념을 제거합니다. - 핵심 개발 팀이 OpenSSL을 여러 가능한 TLS 백엔드 중 하나로 만들 수 있는 경로를 제공하여, Python이 TLS를 지원하려면 시스템에 OpenSSL이 있어야 한다는 요구를 없애는 것입니다.
제안된 인터페이스는 아래에 설명되어 있습니다.
인터페이스
표준화가 필요한 인터페이스가 몇 가지 있습니다. 해당 인터페이스는 다음과 같습니다.
- TLS 구성: 현재
ssl모듈의 SSLContext 클래스가 구현합니다. - 실제 I/O 없이 메모리 내 암호화 또는 복호화를 수행하기 위한 메모리 내 버퍼 제공(비동기 I/O 모델에 필요): 현재
ssl모듈의 SSLObject 클래스가 구현합니다. - 소켓 객체 래핑: 현재
ssl모듈의 SSLSocket 클래스가 구현합니다. - (2)와 (3)의 래핑 객체에 TLS 구성을 적용하는 것입니다. 현재 이 작업 역시
ssl모듈의 SSLContext 클래스가 구현합니다. - TLS 암호 스위트 지정입니다. 현재 표준 라이브러리에는 이를 수행하는 코드가 없습니다. 대신 표준 라이브러리는 OpenSSL 암호 스위트 문자열을 사용합니다.
- TLS 핸드셰이크 중 협상할 수 있는 애플리케이션 계층 프로토콜 지정입니다.
- TLS 버전 지정입니다.
- 호출자에게 오류 보고: 현재
ssl모듈의 SSLError 클래스가 구현합니다. - 클라이언트 인증서 또는 서버 인증서로 로드할 인증서 지정입니다.
- 원격 피어가 제시한 인증서를 검증하는 데 사용할 신뢰 데이터베이스 지정입니다.
- 런타임에 이러한 인터페이스를 얻을 방법을 찾는 것입니다.
단순성을 위해 이 PEP는 통합된 접근 방식을 취할 것을 제안합니다. (2) 및 (3)(즉, 버퍼와 소켓)에 대해 Python 소켓 API는 상당한 규모이며, 일반 Python 소켓과 동일하게 동작하는 래핑된 소켓을 구현하는 것은 미묘하고 까다로운 작업입니다. 그러나 래핑된 버퍼를 기반으로 범용 래핑된 소켓을 구현하는 것은 전적으로 가능합니다. 즉, (2)를 제공하는 모든 구현에서 작동하는 래핑된 소켓 (3)을 작성할 수 있습니다. 이러한 이유로 이 PEP는 래핑된 버퍼 (2)에 대해서는 ABC를 제공하고, 래핑된 소켓 (3)에 대해서는 구체적인 클래스를 제공할 것을 제안합니다.
이 결정으로 인해 소수의 TLS 라이브러리를 이 ABC에 바인딩할 수 없게 됩니다. 해당 TLS 라이브러리들은 래핑된 버퍼 구현을 제공할 수 없기 때문입니다. 현재 가장 주목할 만한 예는 Amazon의 s2n인 것으로 보이며, 이 라이브러리는 현재 I/O 추상화 계층을 제공하지 않습니다. 그러나 이 라이브러리조차도 이를 누락된 기능으로 간주하며, 이를 추가하기 위해 working to add it하고 있습니다. 이러한 이유로 (2)를 기반으로 (3)을 구체적으로 구현하는 것이 상당한 노력을 절감하는 수단이자 정확성을 위한 훌륭한 도구가 될 것이라고 가정해도 안전합니다. 따라서 이 PEP는 바로 그렇게 할 것을 제안합니다.
물론 (5)에는 추상 베이스 클래스가 필요하지 않습니다. 대신 지원되는 암호 스위트를 구성하기 위한 더 풍부한 API가 필요하며, 이 API는 서로 다른 구현에서 지원되는 암호 스위트로 쉽게 업데이트할 수 있어야 합니다.
(9) 이는 까다로운 문제입니다. 이상적인 환경에서는 이 인증서와 연결된 개인 키가 Python 프로세스의 메모리에 절대 들어가지 않아야 하기 때문입니다. 즉, TLS 라이브러리가 하드웨어 보안 모듈(HSM)과 협력하여 프로세스 메모리에서 추출할 수 없는 방식으로 개인 키를 제공해야 합니다. 따라서 구체적인 구현이 이러한 더 높은 수준의 보안을 제공할 수 있도록 허용하는 동시에, 그렇게 할 수 없는 구현에는 더 낮은 기준도 허용하는, 인증서 제공을 위한 확장 가능한 모델을 제공해야 합니다. 이 낮은 기준은 현재 상태와 동일합니다. 즉, 인증서를 메모리 내 버퍼 또는 디스크의 파일에서 로드할 수 있습니다.
(10) 또한 서로 다른 TLS 구현이 매우 크게 다르기 때문에 문제가 됩니다.
즉, 사용자가 신뢰 저장소를 선택할 수 있도록 허용하는 방식이 서로 크게 다릅니다. 일부 구현에는 해당 구현만 사용할 수 있는 특정 신뢰 저장소 형식이 있습니다. 예를 들어 c_rehash로 생성되는 OpenSSL CA 디렉터리 형식이 있습니다. 다른 구현에서는 기본 신뢰 저장소를 포함하지 않는 신뢰 저장소를 지정하지 못하게 할 수도 있습니다.
이러한 이유로 신뢰 저장소가 취하는 형식에 대해 거의 가정하지 않는 모델을 제공해야 합니다. 아래의 “Trust Store” 섹션에서는 이를 달성하는 방법을 더 자세히 설명합니다.
마지막으로 이 API는 현재 SSLContext 객체가 맡고 있는 책임을 분리합니다. 구체적으로는 구성을 보유하고 관리하는 책임과, 해당 구성을 사용하여 래퍼 객체를 만드는 책임을 분리합니다.
이는 반드시 주로 서버 이름 표시(Server Name Indication, SNI)와 같은 기능을 지원하기 위한 것입니다. OpenSSL(따라서 ssl 모듈)에서는 클라이언트가 연결하려는 호스트 이름을 서버에 알리는 것에 응답하여 서버가 TLS 구성을 수정할 수 있습니다. 이는 주로 해당 호스트 이름에 맞는 올바른 TLS 인증서 체인을 제시하도록 인증서 체인을 변경하는 데 사용됩니다. 이를 수행하는 구체적인 메커니즘은 적절한 구성을 갖춘 새 SSLContext 객체를 반환하는 것입니다.
이는 다른 TLS 구현에는 잘 맞지 않는 모델입니다. 대신 SNI 콜백에서 구성에 어떤 변경을 적용해야 하는지 나타내는 데 사용할 수 있는 반환값을 제공할 수 있도록 해야 합니다. 이를 위해 TLS 구성을 보유할 수 있는 객체를 제공해야 합니다. 이 객체는 특정 TLSWrappedBuffer 및 TLSWrappedSocket 객체에 적용되어야 합니다.
이러한 이유로 SSLContext의 책임을 두 개의 별도 객체로 분리합니다. TLSConfiguration 객체는 TLS 구성의 컨테이너 역할을 하는 객체입니다. ClientContext 및 ServerContext 객체는 TLSConfiguration 객체로 인스턴스화되는 객체입니다. 이 세 객체는 모두 불변일 것입니다.
Note
다음 API 선언들은 가독성을 돕기 위해 일관되게 타입 힌트를 사용 합니다. 이러한 타입 힌트 중 일부는 순환 참조이므로 실제로는 사용할 수 없습니다. 이를 모듈의 최종 코드를 반영하는 것이라기보다 지침으로 간주하십시오.
구성
TLSConfiguration 구체 클래스는 TLS 구성을 보유하고 관리할 수 있는 객체를 정의합니다. 이 클래스의 목표는 다음과 같습니다:
- 입력 오류의 위험을 방지하는 TLS 구성 지정 방법을 제공하는 것입니다(단순한 딕셔너리 사용은 제외합니다).
- SNI 콜백에 사용하기 위해 TLS 구성의 변경 사항을 감지할 수 있도록 다른 구성 객체와 안전하게 비교할 수 있는 객체를 제공하는 것입니다.
이 클래스는 주로 구현별 동작을 가질 것으로 예상되지 않기 때문에 ABC가 아닙니다. TLSConfiguration 객체를 특정 TLS 구현에 유용한 구성 집합으로 변환하는 책임은 아래에서 설명하는 Context 객체에 있습니다.
이 클래스에는 한 가지 주목할 만한 특성이 더 있습니다. 바로 변경 불가능하다는 점입니다. 이는 몇 가지 이유로 바람직한 특성입니다. 가장 중요한 이유는 이러한 객체를 딕셔너리 키로 사용할 수 있다는 점이며, 이는 특정 TLS 백엔드와 해당 SNI 구성에 잠재적으로 매우 유용합니다. 또한 구현에서 구성 객체가 자신도 모르게 변경되는 상황을 걱정할 필요가 없게 하므로, 구체적인 데이터 구조와 구성 객체 간의 변경 사항을 신중하게 동기화할 필요를 피할 수 있습니다.
이 객체는 확장 가능합니다. 즉, Python의 향후 릴리스에서는 유용해지는 구성 필드를 이 객체에 추가할 수 있습니다. 하위 호환성을 위해 새 필드는 이 객체의 끝에만 추가됩니다. 기존 필드는 제거되거나 이름이 변경되거나 순서가 바뀌는 일이 절대 없습니다.
TLSConfiguration 객체는 다음 코드로 정의됩니다.:
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
)
컨텍스트
두 개의 컨텍스트 추상 베이스 클래스를 정의합니다. 이러한 ABC는 특정 연결에 TLS 구성을 적용할 수 있는 객체를 정의합니다. 이는 TLSWrappedSocket 및 TLSWrappedBuffer 객체를 생성하는 팩토리로 생각할 수 있습니다.
현재의 ssl 모듈과 달리 하나가 아닌 두 개의 컨텍스트 클래스를 제공합니다. 구체적으로 ClientContext 및 ServerContext 클래스를 제공합니다. 이를 통해 API가 단순해지고(예를 들어 서버가 ssl.SSLContext.wrap_socket에 server_hostname 매개변수를 제공할 이유는 없지만, 컨텍스트 클래스가 하나뿐이므로 해당 매개변수를 여전히 사용할 수 있습니다), 구현에서 TLS 연결의 어느 쪽을 담당할지 최대한 이른 시점에 알 수 있습니다. 또한 구현에서 연결의 한쪽 또는 양쪽을 선택적으로 지원하지 않을 수 있습니다. 예를 들어 macOS의 SecureTransport는 실제로 서버 용도로 사용하도록 설계된 것이 아니며 서버 측 사용에 필요한 기능 중 상당 부분이 빠져 있습니다. 이를 통해 SecureTransport 구현에서는 지원하지 않음을 나타내기 위해 ServerContext의 구체 서브클래스를 정의하지 않기만 하면 됩니다.
현재의 ssl 모듈과 다른 주요 차이점 중 하나는 여러 플래그와 옵션이 제거되었다는 점입니다. 대부분은 자명하지만 wrap_socket에서 auto_handshake가 제거되었다는 점은 주목할 필요가 있습니다. 이는 사용자와 구현자 모두의 복잡성을 증가시키는 대가로 아주 적은 노력만 절약하는, 근본적으로 기묘한 설계상의 결함을 나타내기 때문에 제거되었습니다. 이 PEP에서는 모든 사용자가 연결한 후 do_handshake를 명시적으로 호출하도록 요구합니다.
가능한 한 구현자는 이러한 클래스를 불변으로 만들도록 해야 합니다. 즉, 사용자가 내부 상태를 직접 변경하도록 허용하지 않고, 대신 새로운 TLSConfiguration 객체에서 새로운 컨텍스트를 생성하는 방식을 선호해야 합니다. 물론 ABC는 이러한 제약을 강제할 수 없으므로, 이를 시도하지도 않습니다.
Context 추상 베이스 클래스에는 다음과 같은 클래스 정의가 있습니다.:
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.
"""
버퍼
버퍼 래퍼 추상 베이스 클래스는 다음 정의를 갖는 TLSWrappedBuffer 추상 베이스 클래스로 정의됩니다.:
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.
"""
소켓
소켓 래퍼 클래스는 생성자에서 일반 소켓 객체와 TLSWrappedBuffer 객체라는 두 항목을 받는 구체 클래스입니다. 이 객체는 이 PEP에서 다시 작성하기에는 너무 크지만, 모듈을 구축하는 작업의 일부로 제출될 예정입니다.
래핑된 소켓은 모든 소켓 API를 구현하지만, SOCK_STREAM이외의 유형을 가진 소켓에서만 작동하는 메서드(예: sendto/recvfrom)는 스텁 구현을 갖습니다. 이 모듈에 DTLS 지원이 추가되면 해당 제한을 해제할 수 있습니다.
또한 소켓 클래스에는 일반 소켓 메서드에 더해 다음과 같은 추가 메서드가 포함됩니다.:
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.
"""
암호 스위트
진정으로 라이브러리에 독립적인 방식으로 암호 스위트를 지원하는 일은 놀라울 정도로 어려운 작업입니다. 서로 다른 TLS 구현은 암호 스위트를 지정하기 위한 API가 서로 극단적으로 다른 경우가 많지만, 더 문제가 되는 점은 이러한 API가 형식뿐 아니라 기능 면에서도 자주 다르다는 것입니다. 몇 가지 예가 아래에 나와 있습니다.
OpenSSL
OpenSSL은 널리 알려진 암호 문자열 형식을 사용합니다. 이 형식은 Python을 비롯해 OpenSSL을 사용하는 대부분의 제품에서 구성 언어로 채택되었습니다. 이 형식은 비교적 읽기 쉽지만 여러 단점이 있습니다. 문자열이므로 잘못된 입력을 제공하기가 매우 쉽고, 상세한 유효성 검사가 부족하여 어떤 암호도 협상할 수 없도록 OpenSSL을 구성할 수 있으며, 암호 스위트를 여러 가지 방식으로 지정할 수 있어 구문 분석이 까다롭습니다. 이 형식의 가장 큰 문제는 이를 위한 공식 사양이 없다는 점입니다. 따라서 주어진 문자열을 OpenSSL과 같은 방식으로 구문 분석하는 유일한 방법은 OpenSSL에 해당 문자열을 구문 분석하도록 하는 것입니다.
OpenSSL의 암호 문자열은 다음과 같은 형태일 수 있습니다.:
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5'
이 문자열은 OpenSSL 형식의 복잡성을 일부 보여 줍니다. 예를 들어 하나의 항목으로 여러 암호 스위트를 지정할 수 있습니다. 항목 ECDH+AESGCM은 “타원 곡선 Diffie-Hellman 키 교환과 Galois Counter Mode의 AES를 모두 포함하는 모든 암호 스위트”를 의미합니다. 더 구체적으로는 이것이 네 개의 암호 스위트로 확장됩니다.:
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
따라서 완전한 OpenSSL 암호 문자열을 구문 분석하는 일은 매우 까다롭습니다. 여기에 “!”(이 기준과 일치하는 모든 암호 스위트를 제외하며, 그렇지 않았다면 포함되었을 암호 스위트도 제외합니다. “!MD5”는 MD5 해시 알고리즘을 사용하는 암호 스위트를 포함하지 않는다는 의미입니다), “-“(이미 포함된 경우 일치하는 암호를 제외하지만, 나중에 다시 포함되면 다시 추가할 수 있도록 허용합니다), “+”(일치하는 암호를 포함하되 목록의 끝에 배치합니다)와 같은 다른 메타 문자가 있다는 사실까지 더하면, 구문 분석해야 할 형식은 극도로 복잡해집니다. 이러한 복잡성에 더해 실제 결과는 OpenSSL 버전에 따라 달라진다는 점도 유의해야 합니다. OpenSSL 암호 문자열은 OpenSSL이 인식하는 암호를 하나 이상 포함하는 한 유효하기 때문입니다.
OpenSSL은 관련 사양에서 사용되는 이름과는 다른 이름을 암호에 사용하기도 합니다. 자세한 내용은 ciphers(1)매뉴얼 페이지를 참조하십시오.
암호 문자열에 대한 OpenSSL 내부의 실제 API는 간단합니다.:
char *cipher_list = <some cipher list>;
int rc = SSL_CTX_set_cipher_list(context, cipher_list);
따라서 이 모듈에서 사용하는 모든 형식은 OpenSSL에서 사용할 수 있도록 OpenSSL 암호 문자열로 변환될 수 있어야 합니다.
SecureTransport
SecureTransport는 macOS 시스템 TLS 라이브러리입니다. 이 라이브러리는 사용자 계층이 훨씬 더 제한적이므로 여러 측면에서 OpenSSL보다 상당히 제한적입니다. 이러한 주요 제한 사항 중 하나는 지원되는 암호 스위트를 제어하는 것입니다.
SecureTransport의 암호는 C enum으로 표현됩니다. 이 열거형에는 각 암호 스위트마다 하나의 항목이 있고 집계 항목은 없으므로, 각 열거형 멤버가 어느 범주에 속하는지 일일이 코드로 작성하지 않고서는 “ECDH+AESGCM”과 같은 OpenSSL 암호 문자열의 의미를 재현할 수 없습니다.
그러나 대부분의 열거형 멤버 이름은 암호 스위트의 공식 이름과 일치합니다. 즉, OpenSSL에서 “ECDHE-ECDSA-AES256-GCM-SHA384”라고 부르는 암호 스위트를 SecureTransport에서는 “TLS_ECDHE_ECDHSA_WITH_AES_256_GCM_SHA384”라고 부릅니다.
SecureTransport 내부에서 암호 스위트를 구성하는 API는 간단합니다.:
SSLCipherSuite ciphers[] = {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ...};
OSStatus status = SSLSetEnabledCiphers(context, ciphers, sizeof(ciphers));
SChannel
SChannel은 Windows 시스템 TLS 라이브러리입니다.
SChannel은 사용 가능한 TLS 암호 스위트를 제어하는 기능이 매우 제한적이며, TLS 암호 스위트가 지원됨을 표현하는 세 번째 방법도 채택합니다.
구체적으로 SChannel은 ALG_ID상수 집합(C 부호 없는 정수)을 정의합니다. 이러한 각 상수는 전체 암호 스위트를 가리키는 것이 아니라 개별 알고리즘을 가리킵니다. 예를 들어 CALG_3DES와 CALG_AES_256은 암호 스위트에서 사용되는 대칭 암호화 알고리즘을 가리키고, CALG_DH_EPHEM과 CALG_RSA_KEYX는 암호 스위트에서 사용되는 키 교환 알고리즘의 일부를 가리키며, CALG_SHA1과 CALG_MD5는 암호 스위트에서 사용되는 메시지 인증 코드를 가리키고, CALG_ECDSA와 CALG_RSA_SIGN은 키 교환 알고리즘의 서명 부분을 가리킵니다.
이는 SecureTransport에 없는 OpenSSL 기능의 절반으로 생각할 수 있습니다. SecureTransport는 정확한 암호 스위트만 지정할 수 있는 반면, SChannel은 암호 스위트의 부분만 지정할 수 있으며, OpenSSL은 둘 다 허용합니다.
특정 연결에서 허용되는 암호 스위트를 확인하는 작업은 이러한 ALG_ID상수 배열을 가리키는 포인터를 제공하여 수행합니다. 따라서 적절한 API는 Python 코드가 어떤 ALG_ID상수를 제공해야 하는지 확인할 수 있도록 해야 합니다.
Network Security Services (NSS)
NSS는 Mozilla의 암호화 및 TLS 라이브러리입니다. Firefox와 Thunderbird에서 사용되며, curl과 같은 여러 라이브러리에서는 OpenSSL의 대안으로 사용됩니다.
기본적으로 NSS에는 허용되는 암호에 대한 안전한 구성이 제공됩니다. Fedora와 같은 일부 플랫폼에서는 활성화된 암호 목록이 시스템 정책에서 전역적으로 구성됩니다. 일반적으로 애플리케이션은 특별한 이유가 없는 한 암호 스위트를 수정해서는 안 됩니다.
NSS에는 암호 스위트에 대한 프로세스 전역 설정과 연결별 설정이 모두 있습니다. NSS에는 OpenSSL의 SSLContext와 같은 개념이 없습니다. SSLContext와 유사한 동작은 쉽게 에뮬레이션할 수 있습니다. 구체적으로 연결에 대해 SSL_CipherPrefSetDefault(PRInt32 cipher, PRBool enabled)로 암호를 전역적으로 활성화하거나 비활성화할 수 있고, SSL_CipherPrefSet(PRFileDesc *fd, PRInt32 cipher, PRBool enabled)로도 활성화하거나 비활성화할 수 있습니다. 암호 PRInt32번호는 등록된 IANA ID에 직접 대응하는 부호 있는 32비트 정수이며, 예를 들어 0x1301은 TLS_AES_128_GCM_SHA256에 해당합니다. OpenSSL과 달리 암호의 우선순위 순서는 고정되어 있으며 런타임에 수정할 수 없습니다.
SecureTransport와 마찬가지로 NSS에는 집계 항목을 위한 API가 없습니다. NSS를 사용하는 일부 소비자는 OpenSSL 암호 이름과 규칙에서 NSS 암호로의 사용자 지정 매핑을 구현했습니다. 예를 들어 mod_nss가 있습니다.
제안된 인터페이스
새 모듈에 제안된 인터페이스는 위 구현들의 제한 사항을 종합한 결과에 영향을 받습니다. 구체적으로, OpenSSL을 제외한 모든 구현은 각 개별 암호를 제공해야 하므로, 최소 공통 분모 방식을 제공할 수밖에 없습니다.
가장 간단한 방법은 TLS에 정의된 암호 스위트의 상당 부분을 포함하는 열거형을 제공하는 것입니다. 열거형 멤버의 값은 TLS 핸드셰이크에서 사용되는 2옥텟 암호 식별자를 16비트 정수로 저장한 값이 됩니다. 열거형 멤버의 이름은 IANA에 등록된 암호 스위트 이름이 됩니다.
현재 IANA cipher suite registry에는 320개가 넘는 암호 스위트가 포함되어 있습니다. 암호 스위트의 상당 부분은 네트워크 서비스에 대한 TLS 연결과 관련이 없습니다. 다른 스위트는 더 이상 최신 구현 버전에서 제공되지 않는, 폐기되었거나 안전하지 않은 알고리즘을 지정합니다. 열거형에는 다음 암호가 포함되지 않습니다:
- 키 교환: NULL, Kerberos (KRB5), 사전 공유 키 (PSK), 보안 원격 전송 (TLS-SRP)
- 인증: NULL, 익명, 수출 등급, Kerberos (KRB5), 사전 공유 키 (PSK), 보안 원격 전송 (TLS-SRP), DSA 인증서 (DSS)
- 암호화: NULL, ARIA, DES, RC2, 수출 등급 40비트
- PRF: MD5
- SCSV 암호 스위트
3DES, RC4, SEED 및 IDEA는 레거시 애플리케이션을 위해 포함됩니다. 게다가 TLS 1.3 초안(draft-ietf-tls-tls13-18)의 추가 암호 스위트 다섯 개도 포함됩니다. TLS 1.3은 TLS 1.2 및 그 이전 버전과 어떠한 암호 스위트도 공유하지 않습니다. 결과적으로 생성되는 열거형에는 대략 110개의 스위트가 포함됩니다.
이러한 제한 사항과 열거형에 정의된 모든 암호가 포함되지 않는다는 점을 고려하고, 미래 지향적인 애플리케이션도 지원하기 위해, CipherSuite 객체를 허용하는 이 API의 모든 부분은 원시 16비트 정수도 직접 허용합니다.
이 열거형을 수작업으로 채우는 대신, TLS enum script를 사용하여 Christian Heimes의 tlsdb JSON file (주의: 큰 파일) 및 IANA cipher suite registry로부터 열거형을 생성합니다. TLSDB는 추가 쿼리 기능으로 API를 확장할 가능성도 열어 줍니다. 예를 들어 해당 기능이 유용하거나 필요하다고 판단되면 어떤 TLS 버전이 어떤 암호를 지원하는지 확인할 수 있습니다.
사용자가 이 방식을 번거롭다고 판단하면, 향후 이 API를 확장하여 OpenSSL의 집계 기능을 다시 제공하는 도우미를 추가할 수 있습니다.
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
열거형 멤버는 OpenSSL 암호 이름에 매핑할 수 있습니다.:
>>> 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'
SecureTransport의 경우, 이러한 열거형 멤버는 암호 스위트 상수의 값을 직접 참조합니다. 예를 들어 SecureTransport는 암호 스위트 열거형 멤버 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384가 0xC02C라는 값을 갖도록 정의합니다. 우연의 일치가 아니지만, 이는 위 열거형에서의 값과 동일합니다. 따라서 SecureTransport와 위 열거형 간의 매핑은 매우 간단합니다.
SChannel의 경우 SChannel이 암호 스위트 대신 암호를 구성하므로 직접 매핑하기가 쉽지 않습니다. 이는 SChannel에 대한 지속적인 우려 사항입니다. 다른 TLS 구현과 비교할 때 특정 방식으로 구성하기가 매우 어렵기 때문입니다.
이 PEP의 목적상, 모든 SChannel 구현은 열거형 멤버를 기반으로 선택할 암호를 결정해야 합니다. 이는 실제 암호 스위트 목록이 허용하려는 범위보다 개방적일 수도 있고, 구현의 선택에 따라 더 제한적일 수도 있습니다. 이 PEP에서는 더 제한적인 방식이 되도록 권장하지만, 물론 이를 강제할 수는 없습니다.
프로토콜 협상
NPN과 ALPN 모두 HTTP/2 핸드셰이크의 일부로 프로토콜 협상을 수행할 수 있게 합니다. NPN과 ALPN은 근본적으로 바이트 문자열을 기반으로 하지만, 문자열 기반 API는 감지하기 어려운 입력 오류를 허용하므로 자주 문제가 됩니다.
이러한 이유로 이 모듈은 프로토콜 협상 구현체가 전달하고 전달받을 수 있는 타입을 정의합니다. 이 타입은 잘 알려진 프로토콜의 별칭을 사용할 수 있도록 바이트 문자열을 래핑합니다. 이를 통해 잘 알려진 프로토콜의 오타에 내재한 문제를 피하면서도, 필요한 경우 사용자가 바이트 문자열을 직접 전달하여 프로토콜 협상 계층의 완전한 확장성을 활용할 수 있습니다.
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 버전
지원할 TLS 버전을 제한할 수 있으면 유용한 경우가 많습니다. 오래된 TLS 버전의 사용을 거부하면 보안상 많은 이점이 있으며, 일부 비정상적인 서버는 최신 버전 지원을 알리는 TLS 클라이언트를 잘못 처리합니다.
다음 열거형 타입을 사용하여 TLS 버전을 제한할 수 있습니다. 미래 지향적인 애플리케이션은 반드시 그래야 하는 경우가 아니라면 최대 TLS 버전을 거의 설정하지 않아야 합니다. 이를 사용하는 Python보다 최신인 TLS 백엔드는 이 열거형 타입에 포함되지 않은 TLS 버전을 지원할 수 있기 때문입니다.
또한 이 열거형 타입은 구현체가 지원하는 가장 낮거나 가장 높은 TLS 버전을 요청하는 데 항상 사용할 수 있는 두 개의 추가 플래그를 정의합니다.
class TLSVersion(Enum):
MINIMUM_SUPPORTED = auto()
SSLv2 = auto()
SSLv3 = auto()
TLSv1 = auto()
TLSv1_1 = auto()
TLSv1_2 = auto()
TLSv1_3 = auto()
MAXIMUM_SUPPORTED = auto()
오류
이 모듈은 오류 처리에 사용할 네 개의 기본 클래스를 정의합니다. 여기에서 정의하는 다른 여러 클래스와 달리, 이 클래스들은 동작이 없으므로 추상 클래스가 아닙니다. 이 클래스들은 단순히 특정한 공통 동작을 알리는 역할을 합니다. 백엔드는 자체 패키지에서 이 예외들을 서브클래싱해야 하지만, 해당 예외에 대한 동작을 정의할 필요는 없습니다.
일반적으로 구체적인 구현체는 이 예외들을 직접 발생시키기보다 서브클래싱해야 합니다. 이렇게 하면 예기치 않은 오류를 디버깅하는 동안 어떤 구체적인 TLS 구현체가 사용 중인지 파악하기가 어느 정도 쉬워집니다. 그러나 이는 필수 사항이 아닙니다.
오류의 정의는 다음과 같습니다.:
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.
"""
인증서
이 모듈은 추상 X509 인증서 클래스를 정의합니다. 이 클래스는 거의 어떠한 동작도 하지 않습니다. 이 모듈의 목표는 X509 인증서가 제공할 수 있는 모든 가능한 관련 암호화 기능을 제공하는 것이 아니기 때문입니다. 대신 필요한 것은 인증서의 출처를 구체적인 구현체에 알리는 기능뿐입니다.
이러한 이유로 이 인증서 구현체는 생성자만 정의합니다. 본질적으로 이 모듈의 인증서 객체는 특정 인증서를 찾는 데 사용할 수 있는 핸들만큼 추상적일 수 있습니다.
구체적인 구현체는 대체 생성자를 제공할 수 있습니다. 예를 들어 HSM에서 인증서를 로드하는 생성자를 제공할 수 있습니다. 이를 위한 공통 인터페이스가 등장하면, 이 모듈은 이 사용 사례를 위한 표준 생성자도 제공하도록 업데이트될 수 있습니다.
구체적인 구현체는 가능한 경우 Certificate 객체를 해시 가능하게 만드는 것을 목표로 해야 합니다. 이렇게 하면 개별 구체적인 구현체와 함께 사용되는 TLSConfiguration 객체도 해시 가능하도록 보장하는 데 도움이 됩니다.
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.
"""
개인 키
이 모듈은 추상 프라이빗 키 클래스를 정의할 것입니다. Certificate 클래스와 마찬가지로, 이 클래스는 구체적인 구현이 키를 신중하게 처리할 수 있도록 최대한 자유를 부여하기 위해 동작이 거의 없습니다.
이 클래스에는 Certificate 클래스의 모든 주의 사항이 적용됩니다.
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``.
"""
트러스트 저장소
위에서 설명한 것처럼, 서로 다른 TLS 구현은 사용자가 트러스트 저장소를 선택하도록 허용하는 방식이 크게 다르기 때문에 트러스트 저장소를 로드하는 것은 문제가 됩니다. 이러한 이유로 트러스트 저장소가 취하는 형식에 대해 거의 가정하지 않는 모델을 제공해야 합니다.
이 문제는 Certificate 및 PrivateKey 형식이 해결해야 하는 문제와 동일합니다. 이러한 이유로 TLS 백엔드가 트러스트 저장소를 여는 다양한 방식을 캡슐화할 수 있는 불투명 형식을 만들어 정확히 동일한 모델을 사용합니다.
특정 TLS 구현이 모든 생성자를 구현할 필요는 없습니다. 그러나 가능하다면 특정 TLS 구현이 system 생성자를 제공하는 것이 강력히 권장됩니다. 이는 사용되는 검증용 트러스트 저장소 중 가장 일반적이기 때문입니다. 구체적인 구현은 자체 생성자를 추가할 수도 있습니다.
구체적인 구현은 가능하다면 TrustStore 객체를 해시 가능하게 만드는 것을 목표로 해야 합니다. 이를 통해 개별 구체 구현과 함께 사용되는 TLSConfiguration 객체도 해시 가능하도록 보장할 수 있습니다.
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.
"""
런타임 액세스
라이브러리 사용자가 라이브러리에서 TLS 구성을 제어하도록 하면서 어떤 백엔드를 사용할지 선택하려는 것은 드물지 않은 사용 사례입니다. 예를 들어 Requests 사용자는 Windows 및 macOS에서 OpenSSL과 플랫폼 네이티브 솔루션 중 하나를 선택하거나, 일부 Linux 플랫폼에서 OpenSSL과 NSS 중 하나를 선택할 수 있기를 원할 수 있습니다. 그러나 이러한 사용자는 TLS 구성이 정확히 어떻게 수행되는지에는 관심이 없을 수 있습니다.
이로 인해 문제가 발생합니다. 임의의 구체 구현이 주어졌을 때, 라이브러리는 인증서를 트러스트 저장소에 로드하는 방법을 어떻게 알아낼 수 있습니까? 두 가지 선택지가 있습니다. 모든 구체적인 구현이 특정 명명 규칙에 맞도록 요구하거나, 이러한 객체를 가져올 수 있게 하는 API를 제공하는 것입니다.
이 PEP는 두 번째 접근 방식을 사용할 것을 제안합니다. 이를 통해 구체적인 구현은 적절한 속성을 갖춘 단일 객체만 제공하면 되므로 원하는 방식으로 코드를 구성할 수 있는 최대한의 자유를 얻습니다. 그러면 사용자는 이 “backend” 객체를 이를 지원하는 라이브러리에 전달할 수 있으며, 해당 라이브러리가 구체적인 구현의 구성과 사용을 처리할 수 있습니다.
모든 구체적인 구현은 Backend 객체를 얻는 방법을 제공해야 합니다. Backend 객체는 전역 싱글턴일 수도 있고, 그렇게 하는 것이 유리하다면 호출 가능 객체를 통해 생성될 수도 있습니다.
Backend 객체의 정의는 다음과 같습니다.:
Backend = namedtuple(
'Backend',
['client_context', 'server_context',
'certificate', 'private_key', 'trust_store']
)
각 속성은 관련 ABC의 구체적인 구현을 제공해야 합니다. 이를 통해 이와 같은 코드가 모든 백엔드에서 작동하도록 보장합니다.:
trust_store = backend.trust_store.system()
표준 라이브러리 변경 사항
TLS와 상호 작용하는 표준 라이브러리의 부분은 이러한 ABC를 사용하도록 수정해야 합니다. 이를 통해 해당 부분이 다른 TLS 백엔드에서도 작동할 수 있습니다. 여기에는 다음 모듈이 포함됩니다:
- asyncio
- ftplib
- http
- imaplib
- nntplib
- poplib
- smtplib
- urllib
ssl 모듈의 마이그레이션
당연히 이러한 추상 베이스 클래스에 맞도록 ssl 모듈 자체를 확장해야 합니다. 이 확장은 새로운 클래스의 형태를 취하며, 완전히 새로운 모듈에 포함될 수도 있습니다. 이를 통해 현재 ssl 모듈을 활용하는 애플리케이션은 계속해서 그렇게 사용할 수 있으며, 새로운 API를 사용하려는 애플리케이션과 라이브러리에서는 새로운 API를 사용할 수 있습니다.
일반적으로 ssl 모듈에서 새로운 추상 베이스 클래스로의 마이그레이션은 일대일 대응이 아닐 것으로 예상됩니다. 이는 일반적으로 허용할 수 있습니다. ssl 모듈을 사용하는 대부분의 도구는 이를 사용자에게 숨기므로, 새로운 모듈을 사용하도록 리팩터링해도 사용자에게는 보이지 않아야 합니다.
그러나 특정 문제는 정의된 API의 일부로 또는 쉽게 발생할 수 있는 실수로 ssl 모듈의 예외를 외부로 노출하는 라이브러리나 애플리케이션에서 발생합니다. 이러한 도구의 사용자는 ssl 모듈의 예외가 발생하는 상황을 허용하고 처리하는 코드를 작성했을 수 있습니다. 이 문서에서 제시한 추상 베이스 클래스로 마이그레이션하면 대신 위에서 정의한 예외가 발생할 수 있으며, 기존 except 블록에서는 이를 포착하지 못합니다.
이러한 이유로 ssl 모듈 마이그레이션의 일부로 ssl 모듈의 예외가 위에서 정의한 예외의 별칭이 되도록 해야 합니다. 즉, 다음 문들이 모두 성공하도록 해야 합니다.:
assert ssl.SSLError is tls.TLSError
assert ssl.SSLWantReadError is tls.WantReadError
assert ssl.SSLWantWriteError is tls.WantWriteError
현재 ssl 예외가 C 코드로 정의되어 있어 구현 방식이 더욱 복잡해지므로, 이를 수행하는 정확한 메커니즘은 이 PEP의 범위를 벗어나지만, 더 자세한 내용은 an email sent to the Security-SIG by Christian Heimes에서 확인할 수 있습니다.
향후
향후의 주요 TLS 기능을 지원하려면 이러한 추상 베이스 클래스를 개정해야 할 수 있습니다. 이러한 개정은 신중하게 이루어져야 합니다. 많은 백엔드가 신속하게 발전하지 못할 수 있으며, 이러한 추상 베이스 클래스의 변경으로 무효화될 수 있기 때문입니다. 이는 허용할 수 있지만, 가능한 경우 개별 구현에 특화된 기능을 추상 베이스 클래스에 추가해서는 안 됩니다. 추상 베이스 클래스는 IETF에서 명시한 기능에 대한 고수준 설명으로 범위를 제한해야 합니다.
그러나 충분히 타당한 근거가 있는 이 API의 확장은 반드시 이루어져야 합니다. 이 API의 초점은 Python 커뮤니티에 통합된 최소 공통 분모 구성 옵션을 제공하는 것입니다. TLS는 정적인 대상이 아니므로 TLS가 발전함에 따라 이 API도 발전해야 합니다.
기여자
이 문서는 커뮤니티의 여러 인사로부터 광범위한 검토를 받았으며, 이들은 문서의 형성에 크게 기여했습니다. 다음 분들이 상세한 검토를 제공했습니다.
- 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
추가 검토는 Security-SIG 및 python-ideas 메일링 리스트에서 제공되었습니다.
Copyright
This document has been placed in the public domain.