|
from __future__ import annotations |
|
|
|
import os |
|
import socket |
|
import typing |
|
import warnings |
|
from collections.abc import Sequence |
|
from errno import errorcode |
|
from functools import partial, wraps |
|
from itertools import chain, count |
|
from sys import platform |
|
from typing import Any, Callable, Optional, TypeVar |
|
from weakref import WeakValueDictionary |
|
|
|
from cryptography import x509 |
|
from cryptography.hazmat.primitives.asymmetric import ec |
|
|
|
from OpenSSL._util import ( |
|
StrOrBytesPath as _StrOrBytesPath, |
|
) |
|
from OpenSSL._util import ( |
|
exception_from_error_queue as _exception_from_error_queue, |
|
) |
|
from OpenSSL._util import ( |
|
ffi as _ffi, |
|
) |
|
from OpenSSL._util import ( |
|
lib as _lib, |
|
) |
|
from OpenSSL._util import ( |
|
make_assert as _make_assert, |
|
) |
|
from OpenSSL._util import ( |
|
no_zero_allocator as _no_zero_allocator, |
|
) |
|
from OpenSSL._util import ( |
|
path_bytes as _path_bytes, |
|
) |
|
from OpenSSL._util import ( |
|
text_to_bytes_and_warn as _text_to_bytes_and_warn, |
|
) |
|
from OpenSSL.crypto import ( |
|
FILETYPE_PEM, |
|
X509, |
|
PKey, |
|
X509Name, |
|
X509Store, |
|
_EllipticCurve, |
|
_PassphraseHelper, |
|
_PrivateKey, |
|
) |
|
|
|
__all__ = [ |
|
"DTLS_CLIENT_METHOD", |
|
"DTLS_METHOD", |
|
"DTLS_SERVER_METHOD", |
|
"MODE_RELEASE_BUFFERS", |
|
"NO_OVERLAPPING_PROTOCOLS", |
|
"OPENSSL_BUILT_ON", |
|
"OPENSSL_CFLAGS", |
|
"OPENSSL_DIR", |
|
"OPENSSL_PLATFORM", |
|
"OPENSSL_VERSION", |
|
"OPENSSL_VERSION_NUMBER", |
|
"OP_ALL", |
|
"OP_CIPHER_SERVER_PREFERENCE", |
|
"OP_COOKIE_EXCHANGE", |
|
"OP_DONT_INSERT_EMPTY_FRAGMENTS", |
|
"OP_EPHEMERAL_RSA", |
|
"OP_MICROSOFT_BIG_SSLV3_BUFFER", |
|
"OP_MICROSOFT_SESS_ID_BUG", |
|
"OP_MSIE_SSLV2_RSA_PADDING", |
|
"OP_NETSCAPE_CA_DN_BUG", |
|
"OP_NETSCAPE_CHALLENGE_BUG", |
|
"OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", |
|
"OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", |
|
"OP_NO_COMPRESSION", |
|
"OP_NO_QUERY_MTU", |
|
"OP_NO_TICKET", |
|
"OP_PKCS1_CHECK_1", |
|
"OP_PKCS1_CHECK_2", |
|
"OP_SINGLE_DH_USE", |
|
"OP_SINGLE_ECDH_USE", |
|
"OP_SSLEAY_080_CLIENT_DH_BUG", |
|
"OP_SSLREF2_REUSE_CERT_TYPE_BUG", |
|
"OP_TLS_BLOCK_PADDING_BUG", |
|
"OP_TLS_D5_BUG", |
|
"OP_TLS_ROLLBACK_BUG", |
|
"RECEIVED_SHUTDOWN", |
|
"SENT_SHUTDOWN", |
|
"SESS_CACHE_BOTH", |
|
"SESS_CACHE_CLIENT", |
|
"SESS_CACHE_NO_AUTO_CLEAR", |
|
"SESS_CACHE_NO_INTERNAL", |
|
"SESS_CACHE_NO_INTERNAL_LOOKUP", |
|
"SESS_CACHE_NO_INTERNAL_STORE", |
|
"SESS_CACHE_OFF", |
|
"SESS_CACHE_SERVER", |
|
"SSL3_VERSION", |
|
"SSLEAY_BUILT_ON", |
|
"SSLEAY_CFLAGS", |
|
"SSLEAY_DIR", |
|
"SSLEAY_PLATFORM", |
|
"SSLEAY_VERSION", |
|
"SSL_CB_ACCEPT_EXIT", |
|
"SSL_CB_ACCEPT_LOOP", |
|
"SSL_CB_ALERT", |
|
"SSL_CB_CONNECT_EXIT", |
|
"SSL_CB_CONNECT_LOOP", |
|
"SSL_CB_EXIT", |
|
"SSL_CB_HANDSHAKE_DONE", |
|
"SSL_CB_HANDSHAKE_START", |
|
"SSL_CB_LOOP", |
|
"SSL_CB_READ", |
|
"SSL_CB_READ_ALERT", |
|
"SSL_CB_WRITE", |
|
"SSL_CB_WRITE_ALERT", |
|
"SSL_ST_ACCEPT", |
|
"SSL_ST_CONNECT", |
|
"SSL_ST_MASK", |
|
"TLS1_1_VERSION", |
|
"TLS1_2_VERSION", |
|
"TLS1_3_VERSION", |
|
"TLS1_VERSION", |
|
"TLS_CLIENT_METHOD", |
|
"TLS_METHOD", |
|
"TLS_SERVER_METHOD", |
|
"VERIFY_CLIENT_ONCE", |
|
"VERIFY_FAIL_IF_NO_PEER_CERT", |
|
"VERIFY_NONE", |
|
"VERIFY_PEER", |
|
"Connection", |
|
"Context", |
|
"Error", |
|
"OP_NO_SSLv2", |
|
"OP_NO_SSLv3", |
|
"OP_NO_TLSv1", |
|
"OP_NO_TLSv1_1", |
|
"OP_NO_TLSv1_2", |
|
"OP_NO_TLSv1_3", |
|
"SSLeay_version", |
|
"SSLv23_METHOD", |
|
"Session", |
|
"SysCallError", |
|
"TLSv1_1_METHOD", |
|
"TLSv1_2_METHOD", |
|
"TLSv1_METHOD", |
|
"WantReadError", |
|
"WantWriteError", |
|
"WantX509LookupError", |
|
"X509VerificationCodes", |
|
"ZeroReturnError", |
|
] |
|
|
|
|
|
OPENSSL_VERSION_NUMBER: int = _lib.OPENSSL_VERSION_NUMBER |
|
OPENSSL_VERSION: int = _lib.OPENSSL_VERSION |
|
OPENSSL_CFLAGS: int = _lib.OPENSSL_CFLAGS |
|
OPENSSL_PLATFORM: int = _lib.OPENSSL_PLATFORM |
|
OPENSSL_DIR: int = _lib.OPENSSL_DIR |
|
OPENSSL_BUILT_ON: int = _lib.OPENSSL_BUILT_ON |
|
|
|
SSLEAY_VERSION = OPENSSL_VERSION |
|
SSLEAY_CFLAGS = OPENSSL_CFLAGS |
|
SSLEAY_PLATFORM = OPENSSL_PLATFORM |
|
SSLEAY_DIR = OPENSSL_DIR |
|
SSLEAY_BUILT_ON = OPENSSL_BUILT_ON |
|
|
|
SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN |
|
RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN |
|
|
|
SSLv23_METHOD = 3 |
|
TLSv1_METHOD = 4 |
|
TLSv1_1_METHOD = 5 |
|
TLSv1_2_METHOD = 6 |
|
TLS_METHOD = 7 |
|
TLS_SERVER_METHOD = 8 |
|
TLS_CLIENT_METHOD = 9 |
|
DTLS_METHOD = 10 |
|
DTLS_SERVER_METHOD = 11 |
|
DTLS_CLIENT_METHOD = 12 |
|
|
|
SSL3_VERSION: int = _lib.SSL3_VERSION |
|
TLS1_VERSION: int = _lib.TLS1_VERSION |
|
TLS1_1_VERSION: int = _lib.TLS1_1_VERSION |
|
TLS1_2_VERSION: int = _lib.TLS1_2_VERSION |
|
TLS1_3_VERSION: int = _lib.TLS1_3_VERSION |
|
|
|
OP_NO_SSLv2: int = _lib.SSL_OP_NO_SSLv2 |
|
OP_NO_SSLv3: int = _lib.SSL_OP_NO_SSLv3 |
|
OP_NO_TLSv1: int = _lib.SSL_OP_NO_TLSv1 |
|
OP_NO_TLSv1_1: int = _lib.SSL_OP_NO_TLSv1_1 |
|
OP_NO_TLSv1_2: int = _lib.SSL_OP_NO_TLSv1_2 |
|
OP_NO_TLSv1_3: int = _lib.SSL_OP_NO_TLSv1_3 |
|
|
|
MODE_RELEASE_BUFFERS: int = _lib.SSL_MODE_RELEASE_BUFFERS |
|
|
|
OP_SINGLE_DH_USE: int = _lib.SSL_OP_SINGLE_DH_USE |
|
OP_SINGLE_ECDH_USE: int = _lib.SSL_OP_SINGLE_ECDH_USE |
|
OP_EPHEMERAL_RSA: int = _lib.SSL_OP_EPHEMERAL_RSA |
|
OP_MICROSOFT_SESS_ID_BUG: int = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG |
|
OP_NETSCAPE_CHALLENGE_BUG: int = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG |
|
OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: int = ( |
|
_lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG |
|
) |
|
OP_SSLREF2_REUSE_CERT_TYPE_BUG: int = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG |
|
OP_MICROSOFT_BIG_SSLV3_BUFFER: int = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER |
|
OP_MSIE_SSLV2_RSA_PADDING: int = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING |
|
OP_SSLEAY_080_CLIENT_DH_BUG: int = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG |
|
OP_TLS_D5_BUG: int = _lib.SSL_OP_TLS_D5_BUG |
|
OP_TLS_BLOCK_PADDING_BUG: int = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG |
|
OP_DONT_INSERT_EMPTY_FRAGMENTS: int = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS |
|
OP_CIPHER_SERVER_PREFERENCE: int = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE |
|
OP_TLS_ROLLBACK_BUG: int = _lib.SSL_OP_TLS_ROLLBACK_BUG |
|
OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 |
|
OP_PKCS1_CHECK_2: int = _lib.SSL_OP_PKCS1_CHECK_2 |
|
OP_NETSCAPE_CA_DN_BUG: int = _lib.SSL_OP_NETSCAPE_CA_DN_BUG |
|
OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: int = ( |
|
_lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG |
|
) |
|
OP_NO_COMPRESSION: int = _lib.SSL_OP_NO_COMPRESSION |
|
|
|
OP_NO_QUERY_MTU: int = _lib.SSL_OP_NO_QUERY_MTU |
|
OP_COOKIE_EXCHANGE: int = _lib.SSL_OP_COOKIE_EXCHANGE |
|
OP_NO_TICKET: int = _lib.SSL_OP_NO_TICKET |
|
|
|
try: |
|
OP_NO_RENEGOTIATION: int = _lib.SSL_OP_NO_RENEGOTIATION |
|
__all__.append("OP_NO_RENEGOTIATION") |
|
except AttributeError: |
|
pass |
|
|
|
try: |
|
OP_IGNORE_UNEXPECTED_EOF: int = _lib.SSL_OP_IGNORE_UNEXPECTED_EOF |
|
__all__.append("OP_IGNORE_UNEXPECTED_EOF") |
|
except AttributeError: |
|
pass |
|
|
|
try: |
|
OP_LEGACY_SERVER_CONNECT: int = _lib.SSL_OP_LEGACY_SERVER_CONNECT |
|
__all__.append("OP_LEGACY_SERVER_CONNECT") |
|
except AttributeError: |
|
pass |
|
|
|
OP_ALL: int = _lib.SSL_OP_ALL |
|
|
|
VERIFY_PEER: int = _lib.SSL_VERIFY_PEER |
|
VERIFY_FAIL_IF_NO_PEER_CERT: int = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
|
VERIFY_CLIENT_ONCE: int = _lib.SSL_VERIFY_CLIENT_ONCE |
|
VERIFY_NONE: int = _lib.SSL_VERIFY_NONE |
|
|
|
SESS_CACHE_OFF: int = _lib.SSL_SESS_CACHE_OFF |
|
SESS_CACHE_CLIENT: int = _lib.SSL_SESS_CACHE_CLIENT |
|
SESS_CACHE_SERVER: int = _lib.SSL_SESS_CACHE_SERVER |
|
SESS_CACHE_BOTH: int = _lib.SSL_SESS_CACHE_BOTH |
|
SESS_CACHE_NO_AUTO_CLEAR: int = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR |
|
SESS_CACHE_NO_INTERNAL_LOOKUP: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP |
|
SESS_CACHE_NO_INTERNAL_STORE: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE |
|
SESS_CACHE_NO_INTERNAL: int = _lib.SSL_SESS_CACHE_NO_INTERNAL |
|
|
|
SSL_ST_CONNECT: int = _lib.SSL_ST_CONNECT |
|
SSL_ST_ACCEPT: int = _lib.SSL_ST_ACCEPT |
|
SSL_ST_MASK: int = _lib.SSL_ST_MASK |
|
|
|
SSL_CB_LOOP: int = _lib.SSL_CB_LOOP |
|
SSL_CB_EXIT: int = _lib.SSL_CB_EXIT |
|
SSL_CB_READ: int = _lib.SSL_CB_READ |
|
SSL_CB_WRITE: int = _lib.SSL_CB_WRITE |
|
SSL_CB_ALERT: int = _lib.SSL_CB_ALERT |
|
SSL_CB_READ_ALERT: int = _lib.SSL_CB_READ_ALERT |
|
SSL_CB_WRITE_ALERT: int = _lib.SSL_CB_WRITE_ALERT |
|
SSL_CB_ACCEPT_LOOP: int = _lib.SSL_CB_ACCEPT_LOOP |
|
SSL_CB_ACCEPT_EXIT: int = _lib.SSL_CB_ACCEPT_EXIT |
|
SSL_CB_CONNECT_LOOP: int = _lib.SSL_CB_CONNECT_LOOP |
|
SSL_CB_CONNECT_EXIT: int = _lib.SSL_CB_CONNECT_EXIT |
|
SSL_CB_HANDSHAKE_START: int = _lib.SSL_CB_HANDSHAKE_START |
|
SSL_CB_HANDSHAKE_DONE: int = _lib.SSL_CB_HANDSHAKE_DONE |
|
|
|
_T = TypeVar("_T") |
|
|
|
|
|
class _NoOverlappingProtocols: |
|
pass |
|
|
|
|
|
NO_OVERLAPPING_PROTOCOLS = _NoOverlappingProtocols() |
|
|
|
|
|
_ALPNSelectCallback = Callable[ |
|
[ |
|
"Connection", |
|
typing.List[bytes], |
|
], |
|
typing.Union[bytes, _NoOverlappingProtocols], |
|
] |
|
_CookieGenerateCallback = Callable[["Connection"], bytes] |
|
_CookieVerifyCallback = Callable[["Connection", bytes], bool] |
|
_OCSPClientCallback = Callable[["Connection", bytes, Optional[_T]], bool] |
|
_OCSPServerCallback = Callable[["Connection", Optional[_T]], bytes] |
|
_PassphraseCallback = Callable[[int, bool, Optional[_T]], bytes] |
|
_VerifyCallback = Callable[["Connection", X509, int, int, int], bool] |
|
|
|
|
|
class X509VerificationCodes: |
|
""" |
|
Success and error codes for X509 verification, as returned by the |
|
underlying ``X509_STORE_CTX_get_error()`` function and passed by pyOpenSSL |
|
to verification callback functions. |
|
|
|
See `OpenSSL Verification Errors |
|
<https://www.openssl.org/docs/manmaster/man3/X509_verify_cert_error_string.html#ERROR-CODES>`_ |
|
for details. |
|
""" |
|
|
|
OK = _lib.X509_V_OK |
|
ERR_UNABLE_TO_GET_ISSUER_CERT = _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT |
|
ERR_UNABLE_TO_GET_CRL = _lib.X509_V_ERR_UNABLE_TO_GET_CRL |
|
ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = ( |
|
_lib.X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE |
|
) |
|
ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = ( |
|
_lib.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE |
|
) |
|
ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = ( |
|
_lib.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY |
|
) |
|
ERR_CERT_SIGNATURE_FAILURE = _lib.X509_V_ERR_CERT_SIGNATURE_FAILURE |
|
ERR_CRL_SIGNATURE_FAILURE = _lib.X509_V_ERR_CRL_SIGNATURE_FAILURE |
|
ERR_CERT_NOT_YET_VALID = _lib.X509_V_ERR_CERT_NOT_YET_VALID |
|
ERR_CERT_HAS_EXPIRED = _lib.X509_V_ERR_CERT_HAS_EXPIRED |
|
ERR_CRL_NOT_YET_VALID = _lib.X509_V_ERR_CRL_NOT_YET_VALID |
|
ERR_CRL_HAS_EXPIRED = _lib.X509_V_ERR_CRL_HAS_EXPIRED |
|
ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = ( |
|
_lib.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD |
|
) |
|
ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = ( |
|
_lib.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD |
|
) |
|
ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = ( |
|
_lib.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD |
|
) |
|
ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = ( |
|
_lib.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD |
|
) |
|
ERR_OUT_OF_MEM = _lib.X509_V_ERR_OUT_OF_MEM |
|
ERR_DEPTH_ZERO_SELF_SIGNED_CERT = ( |
|
_lib.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT |
|
) |
|
ERR_SELF_SIGNED_CERT_IN_CHAIN = _lib.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN |
|
ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = ( |
|
_lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY |
|
) |
|
ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = ( |
|
_lib.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE |
|
) |
|
ERR_CERT_CHAIN_TOO_LONG = _lib.X509_V_ERR_CERT_CHAIN_TOO_LONG |
|
ERR_CERT_REVOKED = _lib.X509_V_ERR_CERT_REVOKED |
|
ERR_INVALID_CA = _lib.X509_V_ERR_INVALID_CA |
|
ERR_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PATH_LENGTH_EXCEEDED |
|
ERR_INVALID_PURPOSE = _lib.X509_V_ERR_INVALID_PURPOSE |
|
ERR_CERT_UNTRUSTED = _lib.X509_V_ERR_CERT_UNTRUSTED |
|
ERR_CERT_REJECTED = _lib.X509_V_ERR_CERT_REJECTED |
|
ERR_SUBJECT_ISSUER_MISMATCH = _lib.X509_V_ERR_SUBJECT_ISSUER_MISMATCH |
|
ERR_AKID_SKID_MISMATCH = _lib.X509_V_ERR_AKID_SKID_MISMATCH |
|
ERR_AKID_ISSUER_SERIAL_MISMATCH = ( |
|
_lib.X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH |
|
) |
|
ERR_KEYUSAGE_NO_CERTSIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CERTSIGN |
|
ERR_UNABLE_TO_GET_CRL_ISSUER = _lib.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER |
|
ERR_UNHANDLED_CRITICAL_EXTENSION = ( |
|
_lib.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION |
|
) |
|
ERR_KEYUSAGE_NO_CRL_SIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN |
|
ERR_UNHANDLED_CRITICAL_CRL_EXTENSION = ( |
|
_lib.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION |
|
) |
|
ERR_INVALID_NON_CA = _lib.X509_V_ERR_INVALID_NON_CA |
|
ERR_PROXY_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED |
|
ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE = ( |
|
_lib.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE |
|
) |
|
ERR_PROXY_CERTIFICATES_NOT_ALLOWED = ( |
|
_lib.X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED |
|
) |
|
ERR_INVALID_EXTENSION = _lib.X509_V_ERR_INVALID_EXTENSION |
|
ERR_INVALID_POLICY_EXTENSION = _lib.X509_V_ERR_INVALID_POLICY_EXTENSION |
|
ERR_NO_EXPLICIT_POLICY = _lib.X509_V_ERR_NO_EXPLICIT_POLICY |
|
ERR_DIFFERENT_CRL_SCOPE = _lib.X509_V_ERR_DIFFERENT_CRL_SCOPE |
|
ERR_UNSUPPORTED_EXTENSION_FEATURE = ( |
|
_lib.X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE |
|
) |
|
ERR_UNNESTED_RESOURCE = _lib.X509_V_ERR_UNNESTED_RESOURCE |
|
ERR_PERMITTED_VIOLATION = _lib.X509_V_ERR_PERMITTED_VIOLATION |
|
ERR_EXCLUDED_VIOLATION = _lib.X509_V_ERR_EXCLUDED_VIOLATION |
|
ERR_SUBTREE_MINMAX = _lib.X509_V_ERR_SUBTREE_MINMAX |
|
ERR_UNSUPPORTED_CONSTRAINT_TYPE = ( |
|
_lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE |
|
) |
|
ERR_UNSUPPORTED_CONSTRAINT_SYNTAX = ( |
|
_lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX |
|
) |
|
ERR_UNSUPPORTED_NAME_SYNTAX = _lib.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX |
|
ERR_CRL_PATH_VALIDATION_ERROR = _lib.X509_V_ERR_CRL_PATH_VALIDATION_ERROR |
|
ERR_HOSTNAME_MISMATCH = _lib.X509_V_ERR_HOSTNAME_MISMATCH |
|
ERR_EMAIL_MISMATCH = _lib.X509_V_ERR_EMAIL_MISMATCH |
|
ERR_IP_ADDRESS_MISMATCH = _lib.X509_V_ERR_IP_ADDRESS_MISMATCH |
|
ERR_APPLICATION_VERIFICATION = _lib.X509_V_ERR_APPLICATION_VERIFICATION |
|
|
|
|
|
|
|
_CERTIFICATE_FILE_LOCATIONS = [ |
|
"/etc/ssl/certs/ca-certificates.crt", |
|
"/etc/pki/tls/certs/ca-bundle.crt", |
|
"/etc/ssl/ca-bundle.pem", |
|
"/etc/pki/tls/cacert.pem", |
|
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", |
|
] |
|
|
|
_CERTIFICATE_PATH_LOCATIONS = [ |
|
"/etc/ssl/certs", |
|
] |
|
|
|
|
|
|
|
_CRYPTOGRAPHY_MANYLINUX_CA_DIR = b"/opt/pyca/cryptography/openssl/certs" |
|
_CRYPTOGRAPHY_MANYLINUX_CA_FILE = b"/opt/pyca/cryptography/openssl/cert.pem" |
|
|
|
|
|
class Error(Exception): |
|
""" |
|
An error occurred in an `OpenSSL.SSL` API. |
|
""" |
|
|
|
|
|
_raise_current_error = partial(_exception_from_error_queue, Error) |
|
_openssl_assert = _make_assert(Error) |
|
|
|
|
|
class WantReadError(Error): |
|
pass |
|
|
|
|
|
class WantWriteError(Error): |
|
pass |
|
|
|
|
|
class WantX509LookupError(Error): |
|
pass |
|
|
|
|
|
class ZeroReturnError(Error): |
|
pass |
|
|
|
|
|
class SysCallError(Error): |
|
pass |
|
|
|
|
|
class _CallbackExceptionHelper: |
|
""" |
|
A base class for wrapper classes that allow for intelligent exception |
|
handling in OpenSSL callbacks. |
|
|
|
:ivar list _problems: Any exceptions that occurred while executing in a |
|
context where they could not be raised in the normal way. Typically |
|
this is because OpenSSL has called into some Python code and requires a |
|
return value. The exceptions are saved to be raised later when it is |
|
possible to do so. |
|
""" |
|
|
|
def __init__(self) -> None: |
|
self._problems: list[Exception] = [] |
|
|
|
def raise_if_problem(self) -> None: |
|
""" |
|
Raise an exception from the OpenSSL error queue or that was previously |
|
captured whe running a callback. |
|
""" |
|
if self._problems: |
|
try: |
|
_raise_current_error() |
|
except Error: |
|
pass |
|
raise self._problems.pop(0) |
|
|
|
|
|
class _VerifyHelper(_CallbackExceptionHelper): |
|
""" |
|
Wrap a callback such that it can be used as a certificate verification |
|
callback. |
|
""" |
|
|
|
def __init__(self, callback: _VerifyCallback) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ok, store_ctx): |
|
x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) |
|
_lib.X509_up_ref(x509) |
|
cert = X509._from_raw_x509_ptr(x509) |
|
error_number = _lib.X509_STORE_CTX_get_error(store_ctx) |
|
error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) |
|
|
|
index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() |
|
ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) |
|
connection = Connection._reverse_mapping[ssl] |
|
|
|
try: |
|
result = callback( |
|
connection, cert, error_number, error_depth, ok |
|
) |
|
except Exception as e: |
|
self._problems.append(e) |
|
return 0 |
|
else: |
|
if result: |
|
_lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) |
|
return 1 |
|
else: |
|
return 0 |
|
|
|
self.callback = _ffi.callback( |
|
"int (*)(int, X509_STORE_CTX *)", wrapper |
|
) |
|
|
|
|
|
class _ALPNSelectHelper(_CallbackExceptionHelper): |
|
""" |
|
Wrap a callback such that it can be used as an ALPN selection callback. |
|
""" |
|
|
|
def __init__(self, callback: _ALPNSelectCallback) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, out, outlen, in_, inlen, arg): |
|
try: |
|
conn = Connection._reverse_mapping[ssl] |
|
|
|
|
|
|
|
|
|
instr = _ffi.buffer(in_, inlen)[:] |
|
protolist = [] |
|
while instr: |
|
encoded_len = instr[0] |
|
proto = instr[1 : encoded_len + 1] |
|
protolist.append(proto) |
|
instr = instr[encoded_len + 1 :] |
|
|
|
|
|
outbytes = callback(conn, protolist) |
|
any_accepted = True |
|
if outbytes is NO_OVERLAPPING_PROTOCOLS: |
|
outbytes = b"" |
|
any_accepted = False |
|
elif not isinstance(outbytes, bytes): |
|
raise TypeError( |
|
"ALPN callback must return a bytestring or the " |
|
"special NO_OVERLAPPING_PROTOCOLS sentinel value." |
|
) |
|
|
|
|
|
|
|
|
|
conn._alpn_select_callback_args = [ |
|
_ffi.new("unsigned char *", len(outbytes)), |
|
_ffi.new("unsigned char[]", outbytes), |
|
] |
|
outlen[0] = conn._alpn_select_callback_args[0][0] |
|
out[0] = conn._alpn_select_callback_args[1] |
|
if not any_accepted: |
|
return _lib.SSL_TLSEXT_ERR_NOACK |
|
return _lib.SSL_TLSEXT_ERR_OK |
|
except Exception as e: |
|
self._problems.append(e) |
|
return _lib.SSL_TLSEXT_ERR_ALERT_FATAL |
|
|
|
self.callback = _ffi.callback( |
|
( |
|
"int (*)(SSL *, unsigned char **, unsigned char *, " |
|
"const unsigned char *, unsigned int, void *)" |
|
), |
|
wrapper, |
|
) |
|
|
|
|
|
class _OCSPServerCallbackHelper(_CallbackExceptionHelper): |
|
""" |
|
Wrap a callback such that it can be used as an OCSP callback for the server |
|
side. |
|
|
|
Annoyingly, OpenSSL defines one OCSP callback but uses it in two different |
|
ways. For servers, that callback is expected to retrieve some OCSP data and |
|
hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, |
|
SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback |
|
is expected to check the OCSP data, and returns a negative value on error, |
|
0 if the response is not acceptable, or positive if it is. These are |
|
mutually exclusive return code behaviours, and they mean that we need two |
|
helpers so that we always return an appropriate error code if the user's |
|
code throws an exception. |
|
|
|
Given that we have to have two helpers anyway, these helpers are a bit more |
|
helpery than most: specifically, they hide a few more of the OpenSSL |
|
functions so that the user has an easier time writing these callbacks. |
|
|
|
This helper implements the server side. |
|
""" |
|
|
|
def __init__(self, callback: _OCSPServerCallback[Any]) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, cdata): |
|
try: |
|
conn = Connection._reverse_mapping[ssl] |
|
|
|
|
|
if cdata != _ffi.NULL: |
|
data = _ffi.from_handle(cdata) |
|
else: |
|
data = None |
|
|
|
|
|
ocsp_data = callback(conn, data) |
|
|
|
if not isinstance(ocsp_data, bytes): |
|
raise TypeError("OCSP callback must return a bytestring.") |
|
|
|
|
|
|
|
|
|
|
|
if not ocsp_data: |
|
return 3 |
|
|
|
|
|
|
|
ocsp_data_length = len(ocsp_data) |
|
data_ptr = _lib.OPENSSL_malloc(ocsp_data_length) |
|
_ffi.buffer(data_ptr, ocsp_data_length)[:] = ocsp_data |
|
|
|
_lib.SSL_set_tlsext_status_ocsp_resp( |
|
ssl, data_ptr, ocsp_data_length |
|
) |
|
|
|
return 0 |
|
except Exception as e: |
|
self._problems.append(e) |
|
return 2 |
|
|
|
self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) |
|
|
|
|
|
class _OCSPClientCallbackHelper(_CallbackExceptionHelper): |
|
""" |
|
Wrap a callback such that it can be used as an OCSP callback for the client |
|
side. |
|
|
|
Annoyingly, OpenSSL defines one OCSP callback but uses it in two different |
|
ways. For servers, that callback is expected to retrieve some OCSP data and |
|
hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, |
|
SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback |
|
is expected to check the OCSP data, and returns a negative value on error, |
|
0 if the response is not acceptable, or positive if it is. These are |
|
mutually exclusive return code behaviours, and they mean that we need two |
|
helpers so that we always return an appropriate error code if the user's |
|
code throws an exception. |
|
|
|
Given that we have to have two helpers anyway, these helpers are a bit more |
|
helpery than most: specifically, they hide a few more of the OpenSSL |
|
functions so that the user has an easier time writing these callbacks. |
|
|
|
This helper implements the client side. |
|
""" |
|
|
|
def __init__(self, callback: _OCSPClientCallback[Any]) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, cdata): |
|
try: |
|
conn = Connection._reverse_mapping[ssl] |
|
|
|
|
|
if cdata != _ffi.NULL: |
|
data = _ffi.from_handle(cdata) |
|
else: |
|
data = None |
|
|
|
|
|
ocsp_ptr = _ffi.new("unsigned char **") |
|
ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) |
|
if ocsp_len < 0: |
|
|
|
ocsp_data = b"" |
|
else: |
|
|
|
ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] |
|
|
|
valid = callback(conn, ocsp_data, data) |
|
|
|
|
|
return int(bool(valid)) |
|
|
|
except Exception as e: |
|
self._problems.append(e) |
|
|
|
return -1 |
|
|
|
self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) |
|
|
|
|
|
class _CookieGenerateCallbackHelper(_CallbackExceptionHelper): |
|
def __init__(self, callback: _CookieGenerateCallback) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, out, outlen): |
|
try: |
|
conn = Connection._reverse_mapping[ssl] |
|
cookie = callback(conn) |
|
out[0 : len(cookie)] = cookie |
|
outlen[0] = len(cookie) |
|
return 1 |
|
except Exception as e: |
|
self._problems.append(e) |
|
|
|
return 0 |
|
|
|
self.callback = _ffi.callback( |
|
"int (*)(SSL *, unsigned char *, unsigned int *)", |
|
wrapper, |
|
) |
|
|
|
|
|
class _CookieVerifyCallbackHelper(_CallbackExceptionHelper): |
|
def __init__(self, callback: _CookieVerifyCallback) -> None: |
|
_CallbackExceptionHelper.__init__(self) |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, c_cookie, cookie_len): |
|
try: |
|
conn = Connection._reverse_mapping[ssl] |
|
return callback(conn, bytes(c_cookie[0:cookie_len])) |
|
except Exception as e: |
|
self._problems.append(e) |
|
return 0 |
|
|
|
self.callback = _ffi.callback( |
|
"int (*)(SSL *, unsigned char *, unsigned int)", |
|
wrapper, |
|
) |
|
|
|
|
|
def _asFileDescriptor(obj: Any) -> int: |
|
fd = None |
|
if not isinstance(obj, int): |
|
meth = getattr(obj, "fileno", None) |
|
if meth is not None: |
|
obj = meth() |
|
|
|
if isinstance(obj, int): |
|
fd = obj |
|
|
|
if not isinstance(fd, int): |
|
raise TypeError("argument must be an int, or have a fileno() method.") |
|
elif fd < 0: |
|
raise ValueError( |
|
f"file descriptor cannot be a negative integer ({fd:i})" |
|
) |
|
|
|
return fd |
|
|
|
|
|
def OpenSSL_version(type: int) -> bytes: |
|
""" |
|
Return a string describing the version of OpenSSL in use. |
|
|
|
:param type: One of the :const:`OPENSSL_` constants defined in this module. |
|
""" |
|
return _ffi.string(_lib.OpenSSL_version(type)) |
|
|
|
|
|
SSLeay_version = OpenSSL_version |
|
|
|
|
|
def _make_requires(flag: int, error: str) -> Callable[[_T], _T]: |
|
""" |
|
Builds a decorator that ensures that functions that rely on OpenSSL |
|
functions that are not present in this build raise NotImplementedError, |
|
rather than AttributeError coming out of cryptography. |
|
|
|
:param flag: A cryptography flag that guards the functions, e.g. |
|
``Cryptography_HAS_NEXTPROTONEG``. |
|
:param error: The string to be used in the exception if the flag is false. |
|
""" |
|
|
|
def _requires_decorator(func): |
|
if not flag: |
|
|
|
@wraps(func) |
|
def explode(*args, **kwargs): |
|
raise NotImplementedError(error) |
|
|
|
return explode |
|
else: |
|
return func |
|
|
|
return _requires_decorator |
|
|
|
|
|
_requires_keylog = _make_requires( |
|
getattr(_lib, "Cryptography_HAS_KEYLOG", 0), "Key logging not available" |
|
) |
|
|
|
|
|
class Session: |
|
""" |
|
A class representing an SSL session. A session defines certain connection |
|
parameters which may be re-used to speed up the setup of subsequent |
|
connections. |
|
|
|
.. versionadded:: 0.14 |
|
""" |
|
|
|
_session: Any |
|
|
|
|
|
class Context: |
|
""" |
|
:class:`OpenSSL.SSL.Context` instances define the parameters for setting |
|
up new SSL connections. |
|
|
|
:param method: One of TLS_METHOD, TLS_CLIENT_METHOD, TLS_SERVER_METHOD, |
|
DTLS_METHOD, DTLS_CLIENT_METHOD, or DTLS_SERVER_METHOD. |
|
SSLv23_METHOD, TLSv1_METHOD, etc. are deprecated and should |
|
not be used. |
|
""" |
|
|
|
_methods: typing.ClassVar[ |
|
dict[int, tuple[Callable[[], Any], int | None]] |
|
] = { |
|
SSLv23_METHOD: (_lib.TLS_method, None), |
|
TLSv1_METHOD: (_lib.TLS_method, TLS1_VERSION), |
|
TLSv1_1_METHOD: (_lib.TLS_method, TLS1_1_VERSION), |
|
TLSv1_2_METHOD: (_lib.TLS_method, TLS1_2_VERSION), |
|
TLS_METHOD: (_lib.TLS_method, None), |
|
TLS_SERVER_METHOD: (_lib.TLS_server_method, None), |
|
TLS_CLIENT_METHOD: (_lib.TLS_client_method, None), |
|
DTLS_METHOD: (_lib.DTLS_method, None), |
|
DTLS_SERVER_METHOD: (_lib.DTLS_server_method, None), |
|
DTLS_CLIENT_METHOD: (_lib.DTLS_client_method, None), |
|
} |
|
|
|
def __init__(self, method: int) -> None: |
|
if not isinstance(method, int): |
|
raise TypeError("method must be an integer") |
|
|
|
try: |
|
method_func, version = self._methods[method] |
|
except KeyError: |
|
raise ValueError("No such protocol") |
|
|
|
method_obj = method_func() |
|
_openssl_assert(method_obj != _ffi.NULL) |
|
|
|
context = _lib.SSL_CTX_new(method_obj) |
|
_openssl_assert(context != _ffi.NULL) |
|
context = _ffi.gc(context, _lib.SSL_CTX_free) |
|
|
|
self._context = context |
|
self._passphrase_helper: _PassphraseHelper | None = None |
|
self._passphrase_callback: _PassphraseCallback[Any] | None = None |
|
self._passphrase_userdata: Any | None = None |
|
self._verify_helper: _VerifyHelper | None = None |
|
self._verify_callback: _VerifyCallback | None = None |
|
self._info_callback = None |
|
self._keylog_callback = None |
|
self._tlsext_servername_callback = None |
|
self._app_data = None |
|
self._alpn_select_helper: _ALPNSelectHelper | None = None |
|
self._alpn_select_callback: _ALPNSelectCallback | None = None |
|
self._ocsp_helper: ( |
|
_OCSPClientCallbackHelper | _OCSPServerCallbackHelper | None |
|
) = None |
|
self._ocsp_callback: ( |
|
_OCSPClientCallback[Any] | _OCSPServerCallback[Any] | None |
|
) = None |
|
self._ocsp_data: Any | None = None |
|
self._cookie_generate_helper: _CookieGenerateCallbackHelper | None = ( |
|
None |
|
) |
|
self._cookie_verify_helper: _CookieVerifyCallbackHelper | None = None |
|
|
|
self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE) |
|
if version is not None: |
|
self.set_min_proto_version(version) |
|
self.set_max_proto_version(version) |
|
|
|
def set_min_proto_version(self, version: int) -> None: |
|
""" |
|
Set the minimum supported protocol version. Setting the minimum |
|
version to 0 will enable protocol versions down to the lowest version |
|
supported by the library. |
|
|
|
If the underlying OpenSSL build is missing support for the selected |
|
version, this method will raise an exception. |
|
""" |
|
_openssl_assert( |
|
_lib.SSL_CTX_set_min_proto_version(self._context, version) == 1 |
|
) |
|
|
|
def set_max_proto_version(self, version: int) -> None: |
|
""" |
|
Set the maximum supported protocol version. Setting the maximum |
|
version to 0 will enable protocol versions up to the highest version |
|
supported by the library. |
|
|
|
If the underlying OpenSSL build is missing support for the selected |
|
version, this method will raise an exception. |
|
""" |
|
_openssl_assert( |
|
_lib.SSL_CTX_set_max_proto_version(self._context, version) == 1 |
|
) |
|
|
|
def load_verify_locations( |
|
self, |
|
cafile: _StrOrBytesPath | None, |
|
capath: _StrOrBytesPath | None = None, |
|
) -> None: |
|
""" |
|
Let SSL know where we can find trusted certificates for the certificate |
|
chain. Note that the certificates have to be in PEM format. |
|
|
|
If capath is passed, it must be a directory prepared using the |
|
``c_rehash`` tool included with OpenSSL. Either, but not both, of |
|
*pemfile* or *capath* may be :data:`None`. |
|
|
|
:param cafile: In which file we can find the certificates (``bytes`` or |
|
``str``). |
|
:param capath: In which directory we can find the certificates |
|
(``bytes`` or ``str``). |
|
|
|
:return: None |
|
""" |
|
if cafile is None: |
|
cafile = _ffi.NULL |
|
else: |
|
cafile = _path_bytes(cafile) |
|
|
|
if capath is None: |
|
capath = _ffi.NULL |
|
else: |
|
capath = _path_bytes(capath) |
|
|
|
load_result = _lib.SSL_CTX_load_verify_locations( |
|
self._context, cafile, capath |
|
) |
|
if not load_result: |
|
_raise_current_error() |
|
|
|
def _wrap_callback( |
|
self, callback: _PassphraseCallback[_T] |
|
) -> _PassphraseHelper: |
|
@wraps(callback) |
|
def wrapper(size: int, verify: bool, userdata: Any) -> bytes: |
|
return callback(size, verify, self._passphrase_userdata) |
|
|
|
return _PassphraseHelper( |
|
FILETYPE_PEM, wrapper, more_args=True, truncate=True |
|
) |
|
|
|
def set_passwd_cb( |
|
self, |
|
callback: _PassphraseCallback[_T], |
|
userdata: _T | None = None, |
|
) -> None: |
|
""" |
|
Set the passphrase callback. This function will be called |
|
when a private key with a passphrase is loaded. |
|
|
|
:param callback: The Python callback to use. This must accept three |
|
positional arguments. First, an integer giving the maximum length |
|
of the passphrase it may return. If the returned passphrase is |
|
longer than this, it will be truncated. Second, a boolean value |
|
which will be true if the user should be prompted for the |
|
passphrase twice and the callback should verify that the two values |
|
supplied are equal. Third, the value given as the *userdata* |
|
parameter to :meth:`set_passwd_cb`. The *callback* must return |
|
a byte string. If an error occurs, *callback* should return a false |
|
value (e.g. an empty string). |
|
:param userdata: (optional) A Python object which will be given as |
|
argument to the callback |
|
:return: None |
|
""" |
|
if not callable(callback): |
|
raise TypeError("callback must be callable") |
|
|
|
self._passphrase_helper = self._wrap_callback(callback) |
|
self._passphrase_callback = self._passphrase_helper.callback |
|
_lib.SSL_CTX_set_default_passwd_cb( |
|
self._context, self._passphrase_callback |
|
) |
|
self._passphrase_userdata = userdata |
|
|
|
def set_default_verify_paths(self) -> None: |
|
""" |
|
Specify that the platform provided CA certificates are to be used for |
|
verification purposes. This method has some caveats related to the |
|
binary wheels that cryptography (pyOpenSSL's primary dependency) ships: |
|
|
|
* macOS will only load certificates using this method if the user has |
|
the ``[email protected]`` `Homebrew <https://brew.sh>`_ formula installed |
|
in the default location. |
|
* Windows will not work. |
|
* manylinux cryptography wheels will work on most common Linux |
|
distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the |
|
manylinux wheel and attempts to load roots via a fallback path. |
|
|
|
:return: None |
|
""" |
|
|
|
|
|
|
|
|
|
set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) |
|
_openssl_assert(set_result == 1) |
|
|
|
|
|
|
|
|
|
|
|
dir_env_var = _ffi.string(_lib.X509_get_default_cert_dir_env()).decode( |
|
"ascii" |
|
) |
|
file_env_var = _ffi.string( |
|
_lib.X509_get_default_cert_file_env() |
|
).decode("ascii") |
|
if not self._check_env_vars_set(dir_env_var, file_env_var): |
|
default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) |
|
default_file = _ffi.string(_lib.X509_get_default_cert_file()) |
|
|
|
|
|
|
|
if ( |
|
default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR |
|
and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE |
|
): |
|
|
|
self._fallback_default_verify_paths( |
|
_CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS |
|
) |
|
|
|
def _check_env_vars_set(self, dir_env_var: str, file_env_var: str) -> bool: |
|
""" |
|
Check to see if the default cert dir/file environment vars are present. |
|
|
|
:return: bool |
|
""" |
|
return ( |
|
os.environ.get(file_env_var) is not None |
|
or os.environ.get(dir_env_var) is not None |
|
) |
|
|
|
def _fallback_default_verify_paths( |
|
self, file_path: list[str], dir_path: list[str] |
|
) -> None: |
|
""" |
|
Default verify paths are based on the compiled version of OpenSSL. |
|
However, when pyca/cryptography is compiled as a manylinux wheel |
|
that compiled location can potentially be wrong. So, like Go, we |
|
will try a predefined set of paths and attempt to load roots |
|
from there. |
|
|
|
:return: None |
|
""" |
|
for cafile in file_path: |
|
if os.path.isfile(cafile): |
|
self.load_verify_locations(cafile) |
|
break |
|
|
|
for capath in dir_path: |
|
if os.path.isdir(capath): |
|
self.load_verify_locations(None, capath) |
|
break |
|
|
|
def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None: |
|
""" |
|
Load a certificate chain from a file. |
|
|
|
:param certfile: The name of the certificate chain file (``bytes`` or |
|
``str``). Must be PEM encoded. |
|
|
|
:return: None |
|
""" |
|
certfile = _path_bytes(certfile) |
|
|
|
result = _lib.SSL_CTX_use_certificate_chain_file( |
|
self._context, certfile |
|
) |
|
if not result: |
|
_raise_current_error() |
|
|
|
def use_certificate_file( |
|
self, certfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM |
|
) -> None: |
|
""" |
|
Load a certificate from a file |
|
|
|
:param certfile: The name of the certificate file (``bytes`` or |
|
``str``). |
|
:param filetype: (optional) The encoding of the file, which is either |
|
:const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is |
|
:const:`FILETYPE_PEM`. |
|
|
|
:return: None |
|
""" |
|
certfile = _path_bytes(certfile) |
|
if not isinstance(filetype, int): |
|
raise TypeError("filetype must be an integer") |
|
|
|
use_result = _lib.SSL_CTX_use_certificate_file( |
|
self._context, certfile, filetype |
|
) |
|
if not use_result: |
|
_raise_current_error() |
|
|
|
def use_certificate(self, cert: X509 | x509.Certificate) -> None: |
|
""" |
|
Load a certificate from a X509 object |
|
|
|
:param cert: The X509 object |
|
:return: None |
|
""" |
|
|
|
if not isinstance(cert, X509): |
|
cert = X509.from_cryptography(cert) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL X509 objects is deprecated. You " |
|
"should use a cryptography.x509.Certificate instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) |
|
if not use_result: |
|
_raise_current_error() |
|
|
|
def add_extra_chain_cert(self, certobj: X509 | x509.Certificate) -> None: |
|
""" |
|
Add certificate to chain |
|
|
|
:param certobj: The X509 certificate object to add to the chain |
|
:return: None |
|
""" |
|
if not isinstance(certobj, X509): |
|
certobj = X509.from_cryptography(certobj) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL X509 objects is deprecated. You " |
|
"should use a cryptography.x509.Certificate instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
copy = _lib.X509_dup(certobj._x509) |
|
add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) |
|
if not add_result: |
|
|
|
_lib.X509_free(copy) |
|
_raise_current_error() |
|
|
|
def _raise_passphrase_exception(self) -> None: |
|
if self._passphrase_helper is not None: |
|
self._passphrase_helper.raise_if_problem(Error) |
|
|
|
_raise_current_error() |
|
|
|
def use_privatekey_file( |
|
self, keyfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM |
|
) -> None: |
|
""" |
|
Load a private key from a file |
|
|
|
:param keyfile: The name of the key file (``bytes`` or ``str``) |
|
:param filetype: (optional) The encoding of the file, which is either |
|
:const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is |
|
:const:`FILETYPE_PEM`. |
|
|
|
:return: None |
|
""" |
|
keyfile = _path_bytes(keyfile) |
|
|
|
if not isinstance(filetype, int): |
|
raise TypeError("filetype must be an integer") |
|
|
|
use_result = _lib.SSL_CTX_use_PrivateKey_file( |
|
self._context, keyfile, filetype |
|
) |
|
if not use_result: |
|
self._raise_passphrase_exception() |
|
|
|
def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: |
|
""" |
|
Load a private key from a PKey object |
|
|
|
:param pkey: The PKey object |
|
:return: None |
|
""" |
|
|
|
if not isinstance(pkey, PKey): |
|
pkey = PKey.from_cryptography_key(pkey) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL PKey objects is deprecated. You " |
|
"should use a cryptography private key instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) |
|
if not use_result: |
|
self._raise_passphrase_exception() |
|
|
|
def check_privatekey(self) -> None: |
|
""" |
|
Check if the private key (loaded with :meth:`use_privatekey`) matches |
|
the certificate (loaded with :meth:`use_certificate`) |
|
|
|
:return: :data:`None` (raises :exc:`Error` if something's wrong) |
|
""" |
|
if not _lib.SSL_CTX_check_private_key(self._context): |
|
_raise_current_error() |
|
|
|
def load_client_ca(self, cafile: bytes) -> None: |
|
""" |
|
Load the trusted certificates that will be sent to the client. Does |
|
not actually imply any of the certificates are trusted; that must be |
|
configured separately. |
|
|
|
:param bytes cafile: The path to a certificates file in PEM format. |
|
:return: None |
|
""" |
|
ca_list = _lib.SSL_load_client_CA_file( |
|
_text_to_bytes_and_warn("cafile", cafile) |
|
) |
|
_openssl_assert(ca_list != _ffi.NULL) |
|
_lib.SSL_CTX_set_client_CA_list(self._context, ca_list) |
|
|
|
def set_session_id(self, buf: bytes) -> None: |
|
""" |
|
Set the session id to *buf* within which a session can be reused for |
|
this Context object. This is needed when doing session resumption, |
|
because there is no way for a stored session to know which Context |
|
object it is associated with. |
|
|
|
:param bytes buf: The session id. |
|
|
|
:returns: None |
|
""" |
|
buf = _text_to_bytes_and_warn("buf", buf) |
|
_openssl_assert( |
|
_lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) |
|
== 1 |
|
) |
|
|
|
def set_session_cache_mode(self, mode: int) -> int: |
|
""" |
|
Set the behavior of the session cache used by all connections using |
|
this Context. The previously set mode is returned. See |
|
:const:`SESS_CACHE_*` for details about particular modes. |
|
|
|
:param mode: One or more of the SESS_CACHE_* flags (combine using |
|
bitwise or) |
|
:returns: The previously set caching mode. |
|
|
|
.. versionadded:: 0.14 |
|
""" |
|
if not isinstance(mode, int): |
|
raise TypeError("mode must be an integer") |
|
|
|
return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) |
|
|
|
def get_session_cache_mode(self) -> int: |
|
""" |
|
Get the current session cache mode. |
|
|
|
:returns: The currently used cache mode. |
|
|
|
.. versionadded:: 0.14 |
|
""" |
|
return _lib.SSL_CTX_get_session_cache_mode(self._context) |
|
|
|
def set_verify( |
|
self, mode: int, callback: _VerifyCallback | None = None |
|
) -> None: |
|
""" |
|
Set the verification flags for this Context object to *mode* and |
|
specify that *callback* should be used for verification callbacks. |
|
|
|
:param mode: The verify mode, this should be one of |
|
:const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If |
|
:const:`VERIFY_PEER` is used, *mode* can be OR:ed with |
|
:const:`VERIFY_FAIL_IF_NO_PEER_CERT` and |
|
:const:`VERIFY_CLIENT_ONCE` to further control the behaviour. |
|
:param callback: The optional Python verification callback to use. |
|
This should take five arguments: A Connection object, an X509 |
|
object, and three integer variables, which are in turn potential |
|
error number, error depth and return code. *callback* should |
|
return True if verification passes and False otherwise. |
|
If omitted, OpenSSL's default verification is used. |
|
:return: None |
|
|
|
See SSL_CTX_set_verify(3SSL) for further details. |
|
""" |
|
if not isinstance(mode, int): |
|
raise TypeError("mode must be an integer") |
|
|
|
if callback is None: |
|
self._verify_helper = None |
|
self._verify_callback = None |
|
_lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) |
|
else: |
|
if not callable(callback): |
|
raise TypeError("callback must be callable") |
|
|
|
self._verify_helper = _VerifyHelper(callback) |
|
self._verify_callback = self._verify_helper.callback |
|
_lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) |
|
|
|
def set_verify_depth(self, depth: int) -> None: |
|
""" |
|
Set the maximum depth for the certificate chain verification that shall |
|
be allowed for this Context object. |
|
|
|
:param depth: An integer specifying the verify depth |
|
:return: None |
|
""" |
|
if not isinstance(depth, int): |
|
raise TypeError("depth must be an integer") |
|
|
|
_lib.SSL_CTX_set_verify_depth(self._context, depth) |
|
|
|
def get_verify_mode(self) -> int: |
|
""" |
|
Retrieve the Context object's verify mode, as set by |
|
:meth:`set_verify`. |
|
|
|
:return: The verify mode |
|
""" |
|
return _lib.SSL_CTX_get_verify_mode(self._context) |
|
|
|
def get_verify_depth(self) -> int: |
|
""" |
|
Retrieve the Context object's verify depth, as set by |
|
:meth:`set_verify_depth`. |
|
|
|
:return: The verify depth |
|
""" |
|
return _lib.SSL_CTX_get_verify_depth(self._context) |
|
|
|
def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None: |
|
""" |
|
Load parameters for Ephemeral Diffie-Hellman |
|
|
|
:param dhfile: The file to load EDH parameters from (``bytes`` or |
|
``str``). |
|
|
|
:return: None |
|
""" |
|
dhfile = _path_bytes(dhfile) |
|
|
|
bio = _lib.BIO_new_file(dhfile, b"r") |
|
if bio == _ffi.NULL: |
|
_raise_current_error() |
|
bio = _ffi.gc(bio, _lib.BIO_free) |
|
|
|
dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) |
|
dh = _ffi.gc(dh, _lib.DH_free) |
|
res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) |
|
_openssl_assert(res == 1) |
|
|
|
def set_tmp_ecdh(self, curve: _EllipticCurve | ec.EllipticCurve) -> None: |
|
""" |
|
Select a curve to use for ECDHE key exchange. |
|
|
|
:param curve: A curve instance from cryptography |
|
(:class:`~cryptogragraphy.hazmat.primitives.asymmetric.ec.EllipticCurve`). |
|
Alternatively (deprecated) a curve object from either |
|
:meth:`OpenSSL.crypto.get_elliptic_curve` or |
|
:meth:`OpenSSL.crypto.get_elliptic_curves`. |
|
|
|
:return: None |
|
""" |
|
|
|
if isinstance(curve, _EllipticCurve): |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL elliptic curves to set_tmp_ecdh is " |
|
"deprecated. You should use cryptography's elliptic curve " |
|
"types instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
_lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) |
|
else: |
|
name = curve.name |
|
if name == "secp192r1": |
|
name = "prime192v1" |
|
elif name == "secp256r1": |
|
name = "prime256v1" |
|
nid = _lib.OBJ_txt2nid(name.encode()) |
|
if nid == _lib.NID_undef: |
|
_raise_current_error() |
|
|
|
ec = _lib.EC_KEY_new_by_curve_name(nid) |
|
_openssl_assert(ec != _ffi.NULL) |
|
ec = _ffi.gc(ec, _lib.EC_KEY_free) |
|
_lib.SSL_CTX_set_tmp_ecdh(self._context, ec) |
|
|
|
def set_cipher_list(self, cipher_list: bytes) -> None: |
|
""" |
|
Set the list of ciphers to be used in this context. |
|
|
|
See the OpenSSL manual for more information (e.g. |
|
:manpage:`ciphers(1)`). |
|
|
|
:param bytes cipher_list: An OpenSSL cipher string. |
|
:return: None |
|
""" |
|
cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list) |
|
|
|
if not isinstance(cipher_list, bytes): |
|
raise TypeError("cipher_list must be a byte string.") |
|
|
|
_openssl_assert( |
|
_lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1 |
|
) |
|
|
|
|
|
|
|
|
|
|
|
tmpconn = Connection(self, None) |
|
if tmpconn.get_cipher_list() == [ |
|
"TLS_AES_256_GCM_SHA384", |
|
"TLS_CHACHA20_POLY1305_SHA256", |
|
"TLS_AES_128_GCM_SHA256", |
|
]: |
|
raise Error( |
|
[ |
|
( |
|
"SSL routines", |
|
"SSL_CTX_set_cipher_list", |
|
"no cipher match", |
|
), |
|
], |
|
) |
|
|
|
def set_client_ca_list( |
|
self, certificate_authorities: Sequence[X509Name] |
|
) -> None: |
|
""" |
|
Set the list of preferred client certificate signers for this server |
|
context. |
|
|
|
This list of certificate authorities will be sent to the client when |
|
the server requests a client certificate. |
|
|
|
:param certificate_authorities: a sequence of X509Names. |
|
:return: None |
|
|
|
.. versionadded:: 0.10 |
|
""" |
|
name_stack = _lib.sk_X509_NAME_new_null() |
|
_openssl_assert(name_stack != _ffi.NULL) |
|
|
|
try: |
|
for ca_name in certificate_authorities: |
|
if not isinstance(ca_name, X509Name): |
|
raise TypeError( |
|
f"client CAs must be X509Name objects, not " |
|
f"{type(ca_name).__name__} objects" |
|
) |
|
copy = _lib.X509_NAME_dup(ca_name._name) |
|
_openssl_assert(copy != _ffi.NULL) |
|
push_result = _lib.sk_X509_NAME_push(name_stack, copy) |
|
if not push_result: |
|
_lib.X509_NAME_free(copy) |
|
_raise_current_error() |
|
except Exception: |
|
_lib.sk_X509_NAME_free(name_stack) |
|
raise |
|
|
|
_lib.SSL_CTX_set_client_CA_list(self._context, name_stack) |
|
|
|
def add_client_ca( |
|
self, certificate_authority: X509 | x509.Certificate |
|
) -> None: |
|
""" |
|
Add the CA certificate to the list of preferred signers for this |
|
context. |
|
|
|
The list of certificate authorities will be sent to the client when the |
|
server requests a client certificate. |
|
|
|
:param certificate_authority: certificate authority's X509 certificate. |
|
:return: None |
|
|
|
.. versionadded:: 0.10 |
|
""" |
|
if not isinstance(certificate_authority, X509): |
|
certificate_authority = X509.from_cryptography( |
|
certificate_authority |
|
) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL X509 objects is deprecated. You " |
|
"should use a cryptography.x509.Certificate instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
add_result = _lib.SSL_CTX_add_client_CA( |
|
self._context, certificate_authority._x509 |
|
) |
|
_openssl_assert(add_result == 1) |
|
|
|
def set_timeout(self, timeout: int) -> None: |
|
""" |
|
Set the timeout for newly created sessions for this Context object to |
|
*timeout*. The default value is 300 seconds. See the OpenSSL manual |
|
for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`). |
|
|
|
:param timeout: The timeout in (whole) seconds |
|
:return: The previous session timeout |
|
""" |
|
if not isinstance(timeout, int): |
|
raise TypeError("timeout must be an integer") |
|
|
|
return _lib.SSL_CTX_set_timeout(self._context, timeout) |
|
|
|
def get_timeout(self) -> int: |
|
""" |
|
Retrieve session timeout, as set by :meth:`set_timeout`. The default |
|
is 300 seconds. |
|
|
|
:return: The session timeout |
|
""" |
|
return _lib.SSL_CTX_get_timeout(self._context) |
|
|
|
def set_info_callback( |
|
self, callback: Callable[[Connection, int, int], None] |
|
) -> None: |
|
""" |
|
Set the information callback to *callback*. This function will be |
|
called from time to time during SSL handshakes. |
|
|
|
:param callback: The Python callback to use. This should take three |
|
arguments: a Connection object and two integers. The first integer |
|
specifies where in the SSL handshake the function was called, and |
|
the other the return code from a (possibly failed) internal |
|
function call. |
|
:return: None |
|
""" |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, where, return_code): |
|
callback(Connection._reverse_mapping[ssl], where, return_code) |
|
|
|
self._info_callback = _ffi.callback( |
|
"void (*)(const SSL *, int, int)", wrapper |
|
) |
|
_lib.SSL_CTX_set_info_callback(self._context, self._info_callback) |
|
|
|
@_requires_keylog |
|
def set_keylog_callback( |
|
self, callback: Callable[[Connection, bytes], None] |
|
) -> None: |
|
""" |
|
Set the TLS key logging callback to *callback*. This function will be |
|
called whenever TLS key material is generated or received, in order |
|
to allow applications to store this keying material for debugging |
|
purposes. |
|
|
|
:param callback: The Python callback to use. This should take two |
|
arguments: a Connection object and a bytestring that contains |
|
the key material in the format used by NSS for its SSLKEYLOGFILE |
|
debugging output. |
|
:return: None |
|
""" |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, line): |
|
line = _ffi.string(line) |
|
callback(Connection._reverse_mapping[ssl], line) |
|
|
|
self._keylog_callback = _ffi.callback( |
|
"void (*)(const SSL *, const char *)", wrapper |
|
) |
|
_lib.SSL_CTX_set_keylog_callback(self._context, self._keylog_callback) |
|
|
|
def get_app_data(self) -> Any: |
|
""" |
|
Get the application data (supplied via :meth:`set_app_data()`) |
|
|
|
:return: The application data |
|
""" |
|
return self._app_data |
|
|
|
def set_app_data(self, data: Any) -> None: |
|
""" |
|
Set the application data (will be returned from get_app_data()) |
|
|
|
:param data: Any Python object |
|
:return: None |
|
""" |
|
self._app_data = data |
|
|
|
def get_cert_store(self) -> X509Store | None: |
|
""" |
|
Get the certificate store for the context. This can be used to add |
|
"trusted" certificates without using the |
|
:meth:`load_verify_locations` method. |
|
|
|
:return: A X509Store object or None if it does not have one. |
|
""" |
|
store = _lib.SSL_CTX_get_cert_store(self._context) |
|
if store == _ffi.NULL: |
|
|
|
return None |
|
|
|
pystore = X509Store.__new__(X509Store) |
|
pystore._store = store |
|
return pystore |
|
|
|
def set_options(self, options: int) -> int: |
|
""" |
|
Add options. Options set before are not cleared! |
|
This method should be used with the :const:`OP_*` constants. |
|
|
|
:param options: The options to add. |
|
:return: The new option bitmask. |
|
""" |
|
if not isinstance(options, int): |
|
raise TypeError("options must be an integer") |
|
|
|
return _lib.SSL_CTX_set_options(self._context, options) |
|
|
|
def set_mode(self, mode: int) -> int: |
|
""" |
|
Add modes via bitmask. Modes set before are not cleared! This method |
|
should be used with the :const:`MODE_*` constants. |
|
|
|
:param mode: The mode to add. |
|
:return: The new mode bitmask. |
|
""" |
|
if not isinstance(mode, int): |
|
raise TypeError("mode must be an integer") |
|
|
|
return _lib.SSL_CTX_set_mode(self._context, mode) |
|
|
|
def set_tlsext_servername_callback( |
|
self, callback: Callable[[Connection], None] |
|
) -> None: |
|
""" |
|
Specify a callback function to be called when clients specify a server |
|
name. |
|
|
|
:param callback: The callback function. It will be invoked with one |
|
argument, the Connection instance. |
|
|
|
.. versionadded:: 0.13 |
|
""" |
|
|
|
@wraps(callback) |
|
def wrapper(ssl, alert, arg): |
|
callback(Connection._reverse_mapping[ssl]) |
|
return 0 |
|
|
|
self._tlsext_servername_callback = _ffi.callback( |
|
"int (*)(SSL *, int *, void *)", wrapper |
|
) |
|
_lib.SSL_CTX_set_tlsext_servername_callback( |
|
self._context, self._tlsext_servername_callback |
|
) |
|
|
|
def set_tlsext_use_srtp(self, profiles: bytes) -> None: |
|
""" |
|
Enable support for negotiating SRTP keying material. |
|
|
|
:param bytes profiles: A colon delimited list of protection profile |
|
names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``. |
|
:return: None |
|
""" |
|
if not isinstance(profiles, bytes): |
|
raise TypeError("profiles must be a byte string.") |
|
|
|
_openssl_assert( |
|
_lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 |
|
) |
|
|
|
def set_alpn_protos(self, protos: list[bytes]) -> None: |
|
""" |
|
Specify the protocols that the client is prepared to speak after the |
|
TLS connection has been negotiated using Application Layer Protocol |
|
Negotiation. |
|
|
|
:param protos: A list of the protocols to be offered to the server. |
|
This list should be a Python list of bytestrings representing the |
|
protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. |
|
""" |
|
|
|
|
|
|
|
if not protos: |
|
raise ValueError("at least one protocol must be specified") |
|
|
|
|
|
|
|
protostr = b"".join( |
|
chain.from_iterable((bytes((len(p),)), p) for p in protos) |
|
) |
|
|
|
|
|
|
|
input_str = _ffi.new("unsigned char[]", protostr) |
|
|
|
|
|
|
|
|
|
|
|
_openssl_assert( |
|
_lib.SSL_CTX_set_alpn_protos( |
|
self._context, input_str, len(protostr) |
|
) |
|
== 0 |
|
) |
|
|
|
def set_alpn_select_callback(self, callback: _ALPNSelectCallback) -> None: |
|
""" |
|
Specify a callback function that will be called on the server when a |
|
client offers protocols using ALPN. |
|
|
|
:param callback: The callback function. It will be invoked with two |
|
arguments: the Connection, and a list of offered protocols as |
|
bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It can return |
|
one of those bytestrings to indicate the chosen protocol, the |
|
empty bytestring to terminate the TLS connection, or the |
|
:py:obj:`NO_OVERLAPPING_PROTOCOLS` to indicate that no offered |
|
protocol was selected, but that the connection should not be |
|
aborted. |
|
""" |
|
self._alpn_select_helper = _ALPNSelectHelper(callback) |
|
self._alpn_select_callback = self._alpn_select_helper.callback |
|
_lib.SSL_CTX_set_alpn_select_cb( |
|
self._context, self._alpn_select_callback, _ffi.NULL |
|
) |
|
|
|
def _set_ocsp_callback( |
|
self, |
|
helper: _OCSPClientCallbackHelper | _OCSPServerCallbackHelper, |
|
data: Any | None, |
|
) -> None: |
|
""" |
|
This internal helper does the common work for |
|
``set_ocsp_server_callback`` and ``set_ocsp_client_callback``, which is |
|
almost all of it. |
|
""" |
|
self._ocsp_helper = helper |
|
self._ocsp_callback = helper.callback |
|
if data is None: |
|
self._ocsp_data = _ffi.NULL |
|
else: |
|
self._ocsp_data = _ffi.new_handle(data) |
|
|
|
rc = _lib.SSL_CTX_set_tlsext_status_cb( |
|
self._context, self._ocsp_callback |
|
) |
|
_openssl_assert(rc == 1) |
|
rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data) |
|
_openssl_assert(rc == 1) |
|
|
|
def set_ocsp_server_callback( |
|
self, |
|
callback: _OCSPServerCallback[_T], |
|
data: _T | None = None, |
|
) -> None: |
|
""" |
|
Set a callback to provide OCSP data to be stapled to the TLS handshake |
|
on the server side. |
|
|
|
:param callback: The callback function. It will be invoked with two |
|
arguments: the Connection, and the optional arbitrary data you have |
|
provided. The callback must return a bytestring that contains the |
|
OCSP data to staple to the handshake. If no OCSP data is available |
|
for this connection, return the empty bytestring. |
|
:param data: Some opaque data that will be passed into the callback |
|
function when called. This can be used to avoid needing to do |
|
complex data lookups or to keep track of what context is being |
|
used. This parameter is optional. |
|
""" |
|
helper = _OCSPServerCallbackHelper(callback) |
|
self._set_ocsp_callback(helper, data) |
|
|
|
def set_ocsp_client_callback( |
|
self, |
|
callback: _OCSPClientCallback[_T], |
|
data: _T | None = None, |
|
) -> None: |
|
""" |
|
Set a callback to validate OCSP data stapled to the TLS handshake on |
|
the client side. |
|
|
|
:param callback: The callback function. It will be invoked with three |
|
arguments: the Connection, a bytestring containing the stapled OCSP |
|
assertion, and the optional arbitrary data you have provided. The |
|
callback must return a boolean that indicates the result of |
|
validating the OCSP data: ``True`` if the OCSP data is valid and |
|
the certificate can be trusted, or ``False`` if either the OCSP |
|
data is invalid or the certificate has been revoked. |
|
:param data: Some opaque data that will be passed into the callback |
|
function when called. This can be used to avoid needing to do |
|
complex data lookups or to keep track of what context is being |
|
used. This parameter is optional. |
|
""" |
|
helper = _OCSPClientCallbackHelper(callback) |
|
self._set_ocsp_callback(helper, data) |
|
|
|
def set_cookie_generate_callback( |
|
self, callback: _CookieGenerateCallback |
|
) -> None: |
|
self._cookie_generate_helper = _CookieGenerateCallbackHelper(callback) |
|
_lib.SSL_CTX_set_cookie_generate_cb( |
|
self._context, |
|
self._cookie_generate_helper.callback, |
|
) |
|
|
|
def set_cookie_verify_callback( |
|
self, callback: _CookieVerifyCallback |
|
) -> None: |
|
self._cookie_verify_helper = _CookieVerifyCallbackHelper(callback) |
|
_lib.SSL_CTX_set_cookie_verify_cb( |
|
self._context, |
|
self._cookie_verify_helper.callback, |
|
) |
|
|
|
|
|
class Connection: |
|
_reverse_mapping: typing.MutableMapping[Any, Connection] = ( |
|
WeakValueDictionary() |
|
) |
|
|
|
def __init__( |
|
self, context: Context, socket: socket.socket | None = None |
|
) -> None: |
|
""" |
|
Create a new Connection object, using the given OpenSSL.SSL.Context |
|
instance and socket. |
|
|
|
:param context: An SSL Context to use for this connection |
|
:param socket: The socket to use for transport layer |
|
""" |
|
if not isinstance(context, Context): |
|
raise TypeError("context must be a Context instance") |
|
|
|
ssl = _lib.SSL_new(context._context) |
|
self._ssl = _ffi.gc(ssl, _lib.SSL_free) |
|
|
|
|
|
|
|
|
|
_lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) |
|
self._context = context |
|
self._app_data = None |
|
|
|
|
|
|
|
|
|
|
|
self._alpn_select_callback_args: Any = None |
|
|
|
|
|
|
|
|
|
self._verify_helper = context._verify_helper |
|
self._verify_callback = context._verify_callback |
|
|
|
|
|
self._cookie_generate_helper = context._cookie_generate_helper |
|
self._cookie_verify_helper = context._cookie_verify_helper |
|
|
|
self._reverse_mapping[self._ssl] = self |
|
|
|
if socket is None: |
|
self._socket = None |
|
|
|
self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) |
|
_openssl_assert(self._into_ssl != _ffi.NULL) |
|
|
|
self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) |
|
_openssl_assert(self._from_ssl != _ffi.NULL) |
|
|
|
_lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) |
|
else: |
|
self._into_ssl = None |
|
self._from_ssl = None |
|
self._socket = socket |
|
set_result = _lib.SSL_set_fd( |
|
self._ssl, _asFileDescriptor(self._socket) |
|
) |
|
_openssl_assert(set_result == 1) |
|
|
|
def __getattr__(self, name: str) -> Any: |
|
""" |
|
Look up attributes on the wrapped socket object if they are not found |
|
on the Connection object. |
|
""" |
|
if self._socket is None: |
|
raise AttributeError( |
|
f"'{self.__class__.__name__}' object has no attribute '{name}'" |
|
) |
|
else: |
|
return getattr(self._socket, name) |
|
|
|
def _raise_ssl_error(self, ssl: Any, result: int) -> None: |
|
if self._context._verify_helper is not None: |
|
self._context._verify_helper.raise_if_problem() |
|
if self._context._alpn_select_helper is not None: |
|
self._context._alpn_select_helper.raise_if_problem() |
|
if self._context._ocsp_helper is not None: |
|
self._context._ocsp_helper.raise_if_problem() |
|
|
|
error = _lib.SSL_get_error(ssl, result) |
|
if error == _lib.SSL_ERROR_WANT_READ: |
|
raise WantReadError() |
|
elif error == _lib.SSL_ERROR_WANT_WRITE: |
|
raise WantWriteError() |
|
elif error == _lib.SSL_ERROR_ZERO_RETURN: |
|
raise ZeroReturnError() |
|
elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: |
|
|
|
raise WantX509LookupError() |
|
elif error == _lib.SSL_ERROR_SYSCALL: |
|
if platform == "win32": |
|
errno = _ffi.getwinerror()[0] |
|
else: |
|
errno = _ffi.errno |
|
if _lib.ERR_peek_error() == 0 or errno != 0: |
|
if result < 0 and errno != 0: |
|
raise SysCallError(errno, errorcode.get(errno)) |
|
raise SysCallError(-1, "Unexpected EOF") |
|
else: |
|
|
|
_raise_current_error() |
|
elif error == _lib.SSL_ERROR_SSL and _lib.ERR_peek_error() != 0: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
peeked_error = _lib.ERR_peek_error() |
|
reason = _lib.ERR_GET_REASON(peeked_error) |
|
if _lib.Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING: |
|
_openssl_assert( |
|
reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING |
|
) |
|
_lib.ERR_clear_error() |
|
raise SysCallError(-1, "Unexpected EOF") |
|
else: |
|
_raise_current_error() |
|
elif error == _lib.SSL_ERROR_NONE: |
|
pass |
|
else: |
|
_raise_current_error() |
|
|
|
def get_context(self) -> Context: |
|
""" |
|
Retrieve the :class:`Context` object associated with this |
|
:class:`Connection`. |
|
""" |
|
return self._context |
|
|
|
def set_context(self, context: Context) -> None: |
|
""" |
|
Switch this connection to a new session context. |
|
|
|
:param context: A :class:`Context` instance giving the new session |
|
context to use. |
|
""" |
|
if not isinstance(context, Context): |
|
raise TypeError("context must be a Context instance") |
|
|
|
_lib.SSL_set_SSL_CTX(self._ssl, context._context) |
|
self._context = context |
|
|
|
def get_servername(self) -> bytes | None: |
|
""" |
|
Retrieve the servername extension value if provided in the client hello |
|
message, or None if there wasn't one. |
|
|
|
:return: A byte string giving the server name or :data:`None`. |
|
|
|
.. versionadded:: 0.13 |
|
""" |
|
name = _lib.SSL_get_servername( |
|
self._ssl, _lib.TLSEXT_NAMETYPE_host_name |
|
) |
|
if name == _ffi.NULL: |
|
return None |
|
|
|
return _ffi.string(name) |
|
|
|
def set_verify( |
|
self, mode: int, callback: _VerifyCallback | None = None |
|
) -> None: |
|
""" |
|
Override the Context object's verification flags for this specific |
|
connection. See :py:meth:`Context.set_verify` for details. |
|
""" |
|
if not isinstance(mode, int): |
|
raise TypeError("mode must be an integer") |
|
|
|
if callback is None: |
|
self._verify_helper = None |
|
self._verify_callback = None |
|
_lib.SSL_set_verify(self._ssl, mode, _ffi.NULL) |
|
else: |
|
if not callable(callback): |
|
raise TypeError("callback must be callable") |
|
|
|
self._verify_helper = _VerifyHelper(callback) |
|
self._verify_callback = self._verify_helper.callback |
|
_lib.SSL_set_verify(self._ssl, mode, self._verify_callback) |
|
|
|
def get_verify_mode(self) -> int: |
|
""" |
|
Retrieve the Connection object's verify mode, as set by |
|
:meth:`set_verify`. |
|
|
|
:return: The verify mode |
|
""" |
|
return _lib.SSL_get_verify_mode(self._ssl) |
|
|
|
def use_certificate(self, cert: X509 | x509.Certificate) -> None: |
|
""" |
|
Load a certificate from a X509 object |
|
|
|
:param cert: The X509 object |
|
:return: None |
|
""" |
|
|
|
if not isinstance(cert, X509): |
|
cert = X509.from_cryptography(cert) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL X509 objects is deprecated. You " |
|
"should use a cryptography.x509.Certificate instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
use_result = _lib.SSL_use_certificate(self._ssl, cert._x509) |
|
if not use_result: |
|
_raise_current_error() |
|
|
|
def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: |
|
""" |
|
Load a private key from a PKey object |
|
|
|
:param pkey: The PKey object |
|
:return: None |
|
""" |
|
|
|
if not isinstance(pkey, PKey): |
|
pkey = PKey.from_cryptography_key(pkey) |
|
else: |
|
warnings.warn( |
|
( |
|
"Passing pyOpenSSL PKey objects is deprecated. You " |
|
"should use a cryptography private key instead." |
|
), |
|
DeprecationWarning, |
|
stacklevel=2, |
|
) |
|
|
|
use_result = _lib.SSL_use_PrivateKey(self._ssl, pkey._pkey) |
|
if not use_result: |
|
self._context._raise_passphrase_exception() |
|
|
|
def set_ciphertext_mtu(self, mtu: int) -> None: |
|
""" |
|
For DTLS, set the maximum UDP payload size (*not* including IP/UDP |
|
overhead). |
|
|
|
Note that you might have to set :data:`OP_NO_QUERY_MTU` to prevent |
|
OpenSSL from spontaneously clearing this. |
|
|
|
:param mtu: An integer giving the maximum transmission unit. |
|
|
|
.. versionadded:: 21.1 |
|
""" |
|
_lib.SSL_set_mtu(self._ssl, mtu) |
|
|
|
def get_cleartext_mtu(self) -> int: |
|
""" |
|
For DTLS, get the maximum size of unencrypted data you can pass to |
|
:meth:`write` without exceeding the MTU (as passed to |
|
:meth:`set_ciphertext_mtu`). |
|
|
|
:return: The effective MTU as an integer. |
|
|
|
.. versionadded:: 21.1 |
|
""" |
|
|
|
if not hasattr(_lib, "DTLS_get_data_mtu"): |
|
raise NotImplementedError("requires OpenSSL 1.1.1 or better") |
|
return _lib.DTLS_get_data_mtu(self._ssl) |
|
|
|
def set_tlsext_host_name(self, name: bytes) -> None: |
|
""" |
|
Set the value of the servername extension to send in the client hello. |
|
|
|
:param name: A byte string giving the name. |
|
|
|
.. versionadded:: 0.13 |
|
""" |
|
if not isinstance(name, bytes): |
|
raise TypeError("name must be a byte string") |
|
elif b"\0" in name: |
|
raise TypeError("name must not contain NUL byte") |
|
|
|
|
|
_lib.SSL_set_tlsext_host_name(self._ssl, name) |
|
|
|
def pending(self) -> int: |
|
""" |
|
Get the number of bytes that can be safely read from the SSL buffer |
|
(**not** the underlying transport buffer). |
|
|
|
:return: The number of bytes available in the receive buffer. |
|
""" |
|
return _lib.SSL_pending(self._ssl) |
|
|
|
def send(self, buf: bytes, flags: int = 0) -> int: |
|
""" |
|
Send data on the connection. NOTE: If you get one of the WantRead, |
|
WantWrite or WantX509Lookup exceptions on this, you have to call the |
|
method again with the SAME buffer. |
|
|
|
:param buf: The string, buffer or memoryview to send |
|
:param flags: (optional) Included for compatibility with the socket |
|
API, the value is ignored |
|
:return: The number of bytes written |
|
""" |
|
|
|
buf = _text_to_bytes_and_warn("buf", buf) |
|
|
|
with _ffi.from_buffer(buf) as data: |
|
|
|
if len(buf) > 2147483647: |
|
raise ValueError( |
|
"Cannot send more than 2**31-1 bytes at once." |
|
) |
|
|
|
result = _lib.SSL_write(self._ssl, data, len(data)) |
|
self._raise_ssl_error(self._ssl, result) |
|
|
|
return result |
|
|
|
write = send |
|
|
|
def sendall(self, buf: bytes, flags: int = 0) -> int: |
|
""" |
|
Send "all" data on the connection. This calls send() repeatedly until |
|
all data is sent. If an error occurs, it's impossible to tell how much |
|
data has been sent. |
|
|
|
:param buf: The string, buffer or memoryview to send |
|
:param flags: (optional) Included for compatibility with the socket |
|
API, the value is ignored |
|
:return: The number of bytes written |
|
""" |
|
buf = _text_to_bytes_and_warn("buf", buf) |
|
|
|
with _ffi.from_buffer(buf) as data: |
|
left_to_send = len(buf) |
|
total_sent = 0 |
|
|
|
while left_to_send: |
|
|
|
|
|
result = _lib.SSL_write( |
|
self._ssl, data + total_sent, min(left_to_send, 2147483647) |
|
) |
|
self._raise_ssl_error(self._ssl, result) |
|
total_sent += result |
|
left_to_send -= result |
|
|
|
return total_sent |
|
|
|
def recv(self, bufsiz: int, flags: int | None = None) -> bytes: |
|
""" |
|
Receive data on the connection. |
|
|
|
:param bufsiz: The maximum number of bytes to read |
|
:param flags: (optional) The only supported flag is ``MSG_PEEK``, |
|
all other flags are ignored. |
|
:return: The string read from the Connection |
|
""" |
|
buf = _no_zero_allocator("char[]", bufsiz) |
|
if flags is not None and flags & socket.MSG_PEEK: |
|
result = _lib.SSL_peek(self._ssl, buf, bufsiz) |
|
else: |
|
result = _lib.SSL_read(self._ssl, buf, bufsiz) |
|
self._raise_ssl_error(self._ssl, result) |
|
return _ffi.buffer(buf, result)[:] |
|
|
|
read = recv |
|
|
|
def recv_into( |
|
self, |
|
buffer: Any, |
|
nbytes: int | None = None, |
|
flags: int | None = None, |
|
) -> int: |
|
""" |
|
Receive data on the connection and copy it directly into the provided |
|
buffer, rather than creating a new string. |
|
|
|
:param buffer: The buffer to copy into. |
|
:param nbytes: (optional) The maximum number of bytes to read into the |
|
buffer. If not present, defaults to the size of the buffer. If |
|
larger than the size of the buffer, is reduced to the size of the |
|
buffer. |
|
:param flags: (optional) The only supported flag is ``MSG_PEEK``, |
|
all other flags are ignored. |
|
:return: The number of bytes read into the buffer. |
|
""" |
|
if nbytes is None: |
|
nbytes = len(buffer) |
|
else: |
|
nbytes = min(nbytes, len(buffer)) |
|
|
|
|
|
|
|
|
|
buf = _no_zero_allocator("char[]", nbytes) |
|
if flags is not None and flags & socket.MSG_PEEK: |
|
result = _lib.SSL_peek(self._ssl, buf, nbytes) |
|
else: |
|
result = _lib.SSL_read(self._ssl, buf, nbytes) |
|
self._raise_ssl_error(self._ssl, result) |
|
|
|
|
|
|
|
|
|
|
|
buffer[:result] = memoryview(_ffi.buffer(buf, result)) |
|
|
|
return result |
|
|
|
def _handle_bio_errors(self, bio: Any, result: int) -> typing.NoReturn: |
|
if _lib.BIO_should_retry(bio): |
|
if _lib.BIO_should_read(bio): |
|
raise WantReadError() |
|
elif _lib.BIO_should_write(bio): |
|
|
|
raise WantWriteError() |
|
elif _lib.BIO_should_io_special(bio): |
|
|
|
|
|
raise ValueError("BIO_should_io_special") |
|
else: |
|
|
|
raise ValueError("unknown bio failure") |
|
else: |
|
|
|
_raise_current_error() |
|
|
|
def bio_read(self, bufsiz: int) -> bytes: |
|
""" |
|
If the Connection was created with a memory BIO, this method can be |
|
used to read bytes from the write end of that memory BIO. Many |
|
Connection methods will add bytes which must be read in this manner or |
|
the buffer will eventually fill up and the Connection will be able to |
|
take no further actions. |
|
|
|
:param bufsiz: The maximum number of bytes to read |
|
:return: The string read. |
|
""" |
|
if self._from_ssl is None: |
|
raise TypeError("Connection sock was not None") |
|
|
|
if not isinstance(bufsiz, int): |
|
raise TypeError("bufsiz must be an integer") |
|
|
|
buf = _no_zero_allocator("char[]", bufsiz) |
|
result = _lib.BIO_read(self._from_ssl, buf, bufsiz) |
|
if result <= 0: |
|
self._handle_bio_errors(self._from_ssl, result) |
|
|
|
return _ffi.buffer(buf, result)[:] |
|
|
|
def bio_write(self, buf: bytes) -> int: |
|
""" |
|
If the Connection was created with a memory BIO, this method can be |
|
used to add bytes to the read end of that memory BIO. The Connection |
|
can then read the bytes (for example, in response to a call to |
|
:meth:`recv`). |
|
|
|
:param buf: The string to put into the memory BIO. |
|
:return: The number of bytes written |
|
""" |
|
buf = _text_to_bytes_and_warn("buf", buf) |
|
|
|
if self._into_ssl is None: |
|
raise TypeError("Connection sock was not None") |
|
|
|
with _ffi.from_buffer(buf) as data: |
|
result = _lib.BIO_write(self._into_ssl, data, len(data)) |
|
if result <= 0: |
|
self._handle_bio_errors(self._into_ssl, result) |
|
return result |
|
|
|
def renegotiate(self) -> bool: |
|
""" |
|
Renegotiate the session. |
|
|
|
:return: True if the renegotiation can be started, False otherwise |
|
""" |
|
if not self.renegotiate_pending(): |
|
_openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1) |
|
return True |
|
return False |
|
|
|
def do_handshake(self) -> None: |
|
""" |
|
Perform an SSL handshake (usually called after :meth:`renegotiate` or |
|
one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can |
|
raise the same exceptions as :meth:`send` and :meth:`recv`. |
|
|
|
:return: None. |
|
""" |
|
result = _lib.SSL_do_handshake(self._ssl) |
|
self._raise_ssl_error(self._ssl, result) |
|
|
|
def renegotiate_pending(self) -> bool: |
|
""" |
|
Check if there's a renegotiation in progress, it will return False once |
|
a renegotiation is finished. |
|
|
|
:return: Whether there's a renegotiation in progress |
|
""" |
|
return _lib.SSL_renegotiate_pending(self._ssl) == 1 |
|
|
|
def total_renegotiations(self) -> int: |
|
""" |
|
Find out the total number of renegotiations. |
|
|
|
:return: The number of renegotiations. |
|
""" |
|
return _lib.SSL_total_renegotiations(self._ssl) |
|
|
|
def connect(self, addr: Any) -> None: |
|
""" |
|
Call the :meth:`connect` method of the underlying socket and set up SSL |
|
on the socket, using the :class:`Context` object supplied to this |
|
:class:`Connection` object at creation. |
|
|
|
:param addr: A remote address |
|
:return: What the socket's connect method returns |
|
""" |
|
_lib.SSL_set_connect_state(self._ssl) |
|
return self._socket.connect(addr) |
|
|
|
def connect_ex(self, addr: Any) -> int: |
|
""" |
|
Call the :meth:`connect_ex` method of the underlying socket and set up |
|
SSL on the socket, using the Context object supplied to this Connection |
|
object at creation. Note that if the :meth:`connect_ex` method of the |
|
socket doesn't return 0, SSL won't be initialized. |
|
|
|
:param addr: A remove address |
|
:return: What the socket's connect_ex method returns |
|
""" |
|
connect_ex = self._socket.connect_ex |
|
self.set_connect_state() |
|
return connect_ex(addr) |
|
|
|
def accept(self) -> tuple[Connection, Any]: |
|
""" |
|
Call the :meth:`accept` method of the underlying socket and set up SSL |
|
on the returned socket, using the Context object supplied to this |
|
:class:`Connection` object at creation. |
|
|
|
:return: A *(conn, addr)* pair where *conn* is the new |
|
:class:`Connection` object created, and *address* is as returned by |
|
the socket's :meth:`accept`. |
|
""" |
|
client, addr = self._socket.accept() |
|
conn = Connection(self._context, client) |
|
conn.set_accept_state() |
|
return (conn, addr) |
|
|
|
def DTLSv1_listen(self) -> None: |
|
""" |
|
Call the OpenSSL function DTLSv1_listen on this connection. See the |
|
OpenSSL manual for more details. |
|
|
|
:return: None |
|
""" |
|
|
|
bio_addr = _lib.BIO_ADDR_new() |
|
try: |
|
result = _lib.DTLSv1_listen(self._ssl, bio_addr) |
|
finally: |
|
_lib.BIO_ADDR_free(bio_addr) |
|
|
|
|
|
|
|
|
|
if self._cookie_generate_helper is not None: |
|
self._cookie_generate_helper.raise_if_problem() |
|
if self._cookie_verify_helper is not None: |
|
self._cookie_verify_helper.raise_if_problem() |
|
if result == 0: |
|
raise WantReadError() |
|
if result < 0: |
|
self._raise_ssl_error(self._ssl, result) |
|
|
|
def DTLSv1_get_timeout(self) -> int | None: |
|
""" |
|
Determine when the DTLS SSL object next needs to perform internal |
|
processing due to the passage of time. |
|
|
|
When the returned number of seconds have passed, the |
|
:meth:`DTLSv1_handle_timeout` method needs to be called. |
|
|
|
:return: The time left in seconds before the next timeout or `None` |
|
if no timeout is currently active. |
|
""" |
|
ptv_sec = _ffi.new("time_t *") |
|
ptv_usec = _ffi.new("long *") |
|
if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec): |
|
return ptv_sec[0] + (ptv_usec[0] / 1000000) |
|
else: |
|
return None |
|
|
|
def DTLSv1_handle_timeout(self) -> bool: |
|
""" |
|
Handles any timeout events which have become pending on a DTLS SSL |
|
object. |
|
|
|
:return: `True` if there was a pending timeout, `False` otherwise. |
|
""" |
|
result = _lib.DTLSv1_handle_timeout(self._ssl) |
|
if result < 0: |
|
self._raise_ssl_error(self._ssl, result) |
|
assert False, "unreachable" |
|
else: |
|
return bool(result) |
|
|
|
def bio_shutdown(self) -> None: |
|
""" |
|
If the Connection was created with a memory BIO, this method can be |
|
used to indicate that *end of file* has been reached on the read end of |
|
that memory BIO. |
|
|
|
:return: None |
|
""" |
|
if self._from_ssl is None: |
|
raise TypeError("Connection sock was not None") |
|
|
|
_lib.BIO_set_mem_eof_return(self._into_ssl, 0) |
|
|
|
def shutdown(self) -> bool: |
|
""" |
|
Send the shutdown message to the Connection. |
|
|
|
:return: True if the shutdown completed successfully (i.e. both sides |
|
have sent closure alerts), False otherwise (in which case you |
|
call :meth:`recv` or :meth:`send` when the connection becomes |
|
readable/writeable). |
|
""" |
|
result = _lib.SSL_shutdown(self._ssl) |
|
if result < 0: |
|
self._raise_ssl_error(self._ssl, result) |
|
assert False, "unreachable" |
|
elif result > 0: |
|
return True |
|
else: |
|
return False |
|
|
|
def get_cipher_list(self) -> list[str]: |
|
""" |
|
Retrieve the list of ciphers used by the Connection object. |
|
|
|
:return: A list of native cipher strings. |
|
""" |
|
ciphers = [] |
|
for i in count(): |
|
result = _lib.SSL_get_cipher_list(self._ssl, i) |
|
if result == _ffi.NULL: |
|
break |
|
ciphers.append(_ffi.string(result).decode("utf-8")) |
|
return ciphers |
|
|
|
def get_client_ca_list(self) -> list[X509Name]: |
|
""" |
|
Get CAs whose certificates are suggested for client authentication. |
|
|
|
:return: If this is a server connection, the list of certificate |
|
authorities that will be sent or has been sent to the client, as |
|
controlled by this :class:`Connection`'s :class:`Context`. |
|
|
|
If this is a client connection, the list will be empty until the |
|
connection with the server is established. |
|
|
|
.. versionadded:: 0.10 |
|
""" |
|
ca_names = _lib.SSL_get_client_CA_list(self._ssl) |
|
if ca_names == _ffi.NULL: |
|
|
|
return [] |
|
|
|
result = [] |
|
for i in range(_lib.sk_X509_NAME_num(ca_names)): |
|
name = _lib.sk_X509_NAME_value(ca_names, i) |
|
copy = _lib.X509_NAME_dup(name) |
|
_openssl_assert(copy != _ffi.NULL) |
|
|
|
pyname = X509Name.__new__(X509Name) |
|
pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) |
|
result.append(pyname) |
|
return result |
|
|
|
def makefile(self, *args: Any, **kwargs: Any) -> typing.NoReturn: |
|
""" |
|
The makefile() method is not implemented, since there is no dup |
|
semantics for SSL connections |
|
|
|
:raise: NotImplementedError |
|
""" |
|
raise NotImplementedError( |
|
"Cannot make file object of OpenSSL.SSL.Connection" |
|
) |
|
|
|
def get_app_data(self) -> Any: |
|
""" |
|
Retrieve application data as set by :meth:`set_app_data`. |
|
|
|
:return: The application data |
|
""" |
|
return self._app_data |
|
|
|
def set_app_data(self, data: Any) -> None: |
|
""" |
|
Set application data |
|
|
|
:param data: The application data |
|
:return: None |
|
""" |
|
self._app_data = data |
|
|
|
def get_shutdown(self) -> int: |
|
""" |
|
Get the shutdown state of the Connection. |
|
|
|
:return: The shutdown state, a bitvector of SENT_SHUTDOWN, |
|
RECEIVED_SHUTDOWN. |
|
""" |
|
return _lib.SSL_get_shutdown(self._ssl) |
|
|
|
def set_shutdown(self, state: int) -> None: |
|
""" |
|
Set the shutdown state of the Connection. |
|
|
|
:param state: bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. |
|
:return: None |
|
""" |
|
if not isinstance(state, int): |
|
raise TypeError("state must be an integer") |
|
|
|
_lib.SSL_set_shutdown(self._ssl, state) |
|
|
|
def get_state_string(self) -> bytes: |
|
""" |
|
Retrieve a verbose string detailing the state of the Connection. |
|
|
|
:return: A string representing the state |
|
""" |
|
return _ffi.string(_lib.SSL_state_string_long(self._ssl)) |
|
|
|
def server_random(self) -> bytes | None: |
|
""" |
|
Retrieve the random value used with the server hello message. |
|
|
|
:return: A string representing the state |
|
""" |
|
session = _lib.SSL_get_session(self._ssl) |
|
if session == _ffi.NULL: |
|
return None |
|
length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) |
|
_openssl_assert(length > 0) |
|
outp = _no_zero_allocator("unsigned char[]", length) |
|
_lib.SSL_get_server_random(self._ssl, outp, length) |
|
return _ffi.buffer(outp, length)[:] |
|
|
|
def client_random(self) -> bytes | None: |
|
""" |
|
Retrieve the random value used with the client hello message. |
|
|
|
:return: A string representing the state |
|
""" |
|
session = _lib.SSL_get_session(self._ssl) |
|
if session == _ffi.NULL: |
|
return None |
|
|
|
length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) |
|
_openssl_assert(length > 0) |
|
outp = _no_zero_allocator("unsigned char[]", length) |
|
_lib.SSL_get_client_random(self._ssl, outp, length) |
|
return _ffi.buffer(outp, length)[:] |
|
|
|
def master_key(self) -> bytes | None: |
|
""" |
|
Retrieve the value of the master key for this session. |
|
|
|
:return: A string representing the state |
|
""" |
|
session = _lib.SSL_get_session(self._ssl) |
|
if session == _ffi.NULL: |
|
return None |
|
|
|
length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) |
|
_openssl_assert(length > 0) |
|
outp = _no_zero_allocator("unsigned char[]", length) |
|
_lib.SSL_SESSION_get_master_key(session, outp, length) |
|
return _ffi.buffer(outp, length)[:] |
|
|
|
def export_keying_material( |
|
self, label: bytes, olen: int, context: bytes | None = None |
|
) -> bytes: |
|
""" |
|
Obtain keying material for application use. |
|
|
|
:param: label - a disambiguating label string as described in RFC 5705 |
|
:param: olen - the length of the exported key material in bytes |
|
:param: context - a per-association context value |
|
:return: the exported key material bytes or None |
|
""" |
|
outp = _no_zero_allocator("unsigned char[]", olen) |
|
context_buf = _ffi.NULL |
|
context_len = 0 |
|
use_context = 0 |
|
if context is not None: |
|
context_buf = context |
|
context_len = len(context) |
|
use_context = 1 |
|
success = _lib.SSL_export_keying_material( |
|
self._ssl, |
|
outp, |
|
olen, |
|
label, |
|
len(label), |
|
context_buf, |
|
context_len, |
|
use_context, |
|
) |
|
_openssl_assert(success == 1) |
|
return _ffi.buffer(outp, olen)[:] |
|
|
|
def sock_shutdown(self, *args: Any, **kwargs: Any) -> None: |
|
""" |
|
Call the :meth:`shutdown` method of the underlying socket. |
|
See :manpage:`shutdown(2)`. |
|
|
|
:return: What the socket's shutdown() method returns |
|
""" |
|
return self._socket.shutdown(*args, **kwargs) |
|
|
|
@typing.overload |
|
def get_certificate( |
|
self, *, as_cryptography: typing.Literal[True] |
|
) -> x509.Certificate | None: |
|
pass |
|
|
|
@typing.overload |
|
def get_certificate( |
|
self, *, as_cryptography: typing.Literal[False] = False |
|
) -> X509 | None: |
|
pass |
|
|
|
def get_certificate( |
|
self, |
|
*, |
|
as_cryptography: typing.Literal[True] | typing.Literal[False] = False, |
|
) -> X509 | x509.Certificate | None: |
|
""" |
|
Retrieve the local certificate (if any) |
|
|
|
:param bool as_cryptography: Controls whether a |
|
``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` |
|
object should be returned. |
|
|
|
:return: The local certificate |
|
""" |
|
cert = _lib.SSL_get_certificate(self._ssl) |
|
if cert != _ffi.NULL: |
|
_lib.X509_up_ref(cert) |
|
pycert = X509._from_raw_x509_ptr(cert) |
|
if as_cryptography: |
|
return pycert.to_cryptography() |
|
return pycert |
|
return None |
|
|
|
@typing.overload |
|
def get_peer_certificate( |
|
self, *, as_cryptography: typing.Literal[True] |
|
) -> x509.Certificate | None: |
|
pass |
|
|
|
@typing.overload |
|
def get_peer_certificate( |
|
self, *, as_cryptography: typing.Literal[False] = False |
|
) -> X509 | None: |
|
pass |
|
|
|
def get_peer_certificate( |
|
self, |
|
*, |
|
as_cryptography: typing.Literal[True] | typing.Literal[False] = False, |
|
) -> X509 | x509.Certificate | None: |
|
""" |
|
Retrieve the other side's certificate (if any) |
|
|
|
:param bool as_cryptography: Controls whether a |
|
``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` |
|
object should be returned. |
|
|
|
:return: The peer's certificate |
|
""" |
|
cert = _lib.SSL_get_peer_certificate(self._ssl) |
|
if cert != _ffi.NULL: |
|
pycert = X509._from_raw_x509_ptr(cert) |
|
if as_cryptography: |
|
return pycert.to_cryptography() |
|
return pycert |
|
return None |
|
|
|
@staticmethod |
|
def _cert_stack_to_list(cert_stack: Any) -> list[X509]: |
|
""" |
|
Internal helper to convert a STACK_OF(X509) to a list of X509 |
|
instances. |
|
""" |
|
result = [] |
|
for i in range(_lib.sk_X509_num(cert_stack)): |
|
cert = _lib.sk_X509_value(cert_stack, i) |
|
_openssl_assert(cert != _ffi.NULL) |
|
res = _lib.X509_up_ref(cert) |
|
_openssl_assert(res >= 1) |
|
pycert = X509._from_raw_x509_ptr(cert) |
|
result.append(pycert) |
|
return result |
|
|
|
@staticmethod |
|
def _cert_stack_to_cryptography_list( |
|
cert_stack: Any, |
|
) -> list[x509.Certificate]: |
|
""" |
|
Internal helper to convert a STACK_OF(X509) to a list of X509 |
|
instances. |
|
""" |
|
result = [] |
|
for i in range(_lib.sk_X509_num(cert_stack)): |
|
cert = _lib.sk_X509_value(cert_stack, i) |
|
_openssl_assert(cert != _ffi.NULL) |
|
res = _lib.X509_up_ref(cert) |
|
_openssl_assert(res >= 1) |
|
pycert = X509._from_raw_x509_ptr(cert) |
|
result.append(pycert.to_cryptography()) |
|
return result |
|
|
|
@typing.overload |
|
def get_peer_cert_chain( |
|
self, *, as_cryptography: typing.Literal[True] |
|
) -> list[x509.Certificate] | None: |
|
pass |
|
|
|
@typing.overload |
|
def get_peer_cert_chain( |
|
self, *, as_cryptography: typing.Literal[False] = False |
|
) -> list[X509] | None: |
|
pass |
|
|
|
def get_peer_cert_chain( |
|
self, |
|
*, |
|
as_cryptography: typing.Literal[True] | typing.Literal[False] = False, |
|
) -> list[X509] | list[x509.Certificate] | None: |
|
""" |
|
Retrieve the other side's certificate (if any) |
|
|
|
:param bool as_cryptography: Controls whether a list of |
|
``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` |
|
object should be returned. |
|
|
|
:return: A list of X509 instances giving the peer's certificate chain, |
|
or None if it does not have one. |
|
""" |
|
cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) |
|
if cert_stack == _ffi.NULL: |
|
return None |
|
|
|
if as_cryptography: |
|
return self._cert_stack_to_cryptography_list(cert_stack) |
|
return self._cert_stack_to_list(cert_stack) |
|
|
|
@typing.overload |
|
def get_verified_chain( |
|
self, *, as_cryptography: typing.Literal[True] |
|
) -> list[x509.Certificate] | None: |
|
pass |
|
|
|
@typing.overload |
|
def get_verified_chain( |
|
self, *, as_cryptography: typing.Literal[False] = False |
|
) -> list[X509] | None: |
|
pass |
|
|
|
def get_verified_chain( |
|
self, |
|
*, |
|
as_cryptography: typing.Literal[True] | typing.Literal[False] = False, |
|
) -> list[X509] | list[x509.Certificate] | None: |
|
""" |
|
Retrieve the verified certificate chain of the peer including the |
|
peer's end entity certificate. It must be called after a session has |
|
been successfully established. If peer verification was not successful |
|
the chain may be incomplete, invalid, or None. |
|
|
|
:param bool as_cryptography: Controls whether a list of |
|
``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` |
|
object should be returned. |
|
|
|
:return: A list of X509 instances giving the peer's verified |
|
certificate chain, or None if it does not have one. |
|
|
|
.. versionadded:: 20.0 |
|
""" |
|
|
|
cert_stack = _lib.SSL_get0_verified_chain(self._ssl) |
|
if cert_stack == _ffi.NULL: |
|
return None |
|
|
|
if as_cryptography: |
|
return self._cert_stack_to_cryptography_list(cert_stack) |
|
return self._cert_stack_to_list(cert_stack) |
|
|
|
def want_read(self) -> bool: |
|
""" |
|
Checks if more data has to be read from the transport layer to complete |
|
an operation. |
|
|
|
:return: True iff more data has to be read |
|
""" |
|
return _lib.SSL_want_read(self._ssl) |
|
|
|
def want_write(self) -> bool: |
|
""" |
|
Checks if there is data to write to the transport layer to complete an |
|
operation. |
|
|
|
:return: True iff there is data to write |
|
""" |
|
return _lib.SSL_want_write(self._ssl) |
|
|
|
def set_accept_state(self) -> None: |
|
""" |
|
Set the connection to work in server mode. The handshake will be |
|
handled automatically by read/write. |
|
|
|
:return: None |
|
""" |
|
_lib.SSL_set_accept_state(self._ssl) |
|
|
|
def set_connect_state(self) -> None: |
|
""" |
|
Set the connection to work in client mode. The handshake will be |
|
handled automatically by read/write. |
|
|
|
:return: None |
|
""" |
|
_lib.SSL_set_connect_state(self._ssl) |
|
|
|
def get_session(self) -> Session | None: |
|
""" |
|
Returns the Session currently used. |
|
|
|
:return: An instance of :class:`OpenSSL.SSL.Session` or |
|
:obj:`None` if no session exists. |
|
|
|
.. versionadded:: 0.14 |
|
""" |
|
session = _lib.SSL_get1_session(self._ssl) |
|
if session == _ffi.NULL: |
|
return None |
|
|
|
pysession = Session.__new__(Session) |
|
pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) |
|
return pysession |
|
|
|
def set_session(self, session: Session) -> None: |
|
""" |
|
Set the session to be used when the TLS/SSL connection is established. |
|
|
|
:param session: A Session instance representing the session to use. |
|
:returns: None |
|
|
|
.. versionadded:: 0.14 |
|
""" |
|
if not isinstance(session, Session): |
|
raise TypeError("session must be a Session instance") |
|
|
|
result = _lib.SSL_set_session(self._ssl, session._session) |
|
_openssl_assert(result == 1) |
|
|
|
def _get_finished_message( |
|
self, function: Callable[[Any, Any, int], int] |
|
) -> bytes | None: |
|
""" |
|
Helper to implement :meth:`get_finished` and |
|
:meth:`get_peer_finished`. |
|
|
|
:param function: Either :data:`SSL_get_finished`: or |
|
:data:`SSL_get_peer_finished`. |
|
|
|
:return: :data:`None` if the desired message has not yet been |
|
received, otherwise the contents of the message. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
empty = _ffi.new("char[]", 0) |
|
size = function(self._ssl, empty, 0) |
|
if size == 0: |
|
|
|
return None |
|
|
|
buf = _no_zero_allocator("char[]", size) |
|
function(self._ssl, buf, size) |
|
return _ffi.buffer(buf, size)[:] |
|
|
|
def get_finished(self) -> bytes | None: |
|
""" |
|
Obtain the latest TLS Finished message that we sent. |
|
|
|
:return: The contents of the message or :obj:`None` if the TLS |
|
handshake has not yet completed. |
|
|
|
.. versionadded:: 0.15 |
|
""" |
|
return self._get_finished_message(_lib.SSL_get_finished) |
|
|
|
def get_peer_finished(self) -> bytes | None: |
|
""" |
|
Obtain the latest TLS Finished message that we received from the peer. |
|
|
|
:return: The contents of the message or :obj:`None` if the TLS |
|
handshake has not yet completed. |
|
|
|
.. versionadded:: 0.15 |
|
""" |
|
return self._get_finished_message(_lib.SSL_get_peer_finished) |
|
|
|
def get_cipher_name(self) -> str | None: |
|
""" |
|
Obtain the name of the currently used cipher. |
|
|
|
:returns: The name of the currently used cipher or :obj:`None` |
|
if no connection has been established. |
|
|
|
.. versionadded:: 0.15 |
|
""" |
|
cipher = _lib.SSL_get_current_cipher(self._ssl) |
|
if cipher == _ffi.NULL: |
|
return None |
|
else: |
|
name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) |
|
return name.decode("utf-8") |
|
|
|
def get_cipher_bits(self) -> int | None: |
|
""" |
|
Obtain the number of secret bits of the currently used cipher. |
|
|
|
:returns: The number of secret bits of the currently used cipher |
|
or :obj:`None` if no connection has been established. |
|
|
|
.. versionadded:: 0.15 |
|
""" |
|
cipher = _lib.SSL_get_current_cipher(self._ssl) |
|
if cipher == _ffi.NULL: |
|
return None |
|
else: |
|
return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) |
|
|
|
def get_cipher_version(self) -> str | None: |
|
""" |
|
Obtain the protocol version of the currently used cipher. |
|
|
|
:returns: The protocol name of the currently used cipher |
|
or :obj:`None` if no connection has been established. |
|
|
|
.. versionadded:: 0.15 |
|
""" |
|
cipher = _lib.SSL_get_current_cipher(self._ssl) |
|
if cipher == _ffi.NULL: |
|
return None |
|
else: |
|
version = _ffi.string(_lib.SSL_CIPHER_get_version(cipher)) |
|
return version.decode("utf-8") |
|
|
|
def get_protocol_version_name(self) -> str: |
|
""" |
|
Retrieve the protocol version of the current connection. |
|
|
|
:returns: The TLS version of the current connection, for example |
|
the value for TLS 1.2 would be ``TLSv1.2``or ``Unknown`` |
|
for connections that were not successfully established. |
|
""" |
|
version = _ffi.string(_lib.SSL_get_version(self._ssl)) |
|
return version.decode("utf-8") |
|
|
|
def get_protocol_version(self) -> int: |
|
""" |
|
Retrieve the SSL or TLS protocol version of the current connection. |
|
|
|
:returns: The TLS version of the current connection. For example, |
|
it will return ``0x769`` for connections made over TLS version 1. |
|
""" |
|
version = _lib.SSL_version(self._ssl) |
|
return version |
|
|
|
def set_alpn_protos(self, protos: list[bytes]) -> None: |
|
""" |
|
Specify the client's ALPN protocol list. |
|
|
|
These protocols are offered to the server during protocol negotiation. |
|
|
|
:param protos: A list of the protocols to be offered to the server. |
|
This list should be a Python list of bytestrings representing the |
|
protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. |
|
""" |
|
|
|
|
|
|
|
if not protos: |
|
raise ValueError("at least one protocol must be specified") |
|
|
|
|
|
|
|
protostr = b"".join( |
|
chain.from_iterable((bytes((len(p),)), p) for p in protos) |
|
) |
|
|
|
|
|
|
|
input_str = _ffi.new("unsigned char[]", protostr) |
|
|
|
|
|
|
|
|
|
|
|
_openssl_assert( |
|
_lib.SSL_set_alpn_protos(self._ssl, input_str, len(protostr)) == 0 |
|
) |
|
|
|
def get_alpn_proto_negotiated(self) -> bytes: |
|
""" |
|
Get the protocol that was negotiated by ALPN. |
|
|
|
:returns: A bytestring of the protocol name. If no protocol has been |
|
negotiated yet, returns an empty bytestring. |
|
""" |
|
data = _ffi.new("unsigned char **") |
|
data_len = _ffi.new("unsigned int *") |
|
|
|
_lib.SSL_get0_alpn_selected(self._ssl, data, data_len) |
|
|
|
if not data_len: |
|
return b"" |
|
|
|
return _ffi.buffer(data[0], data_len[0])[:] |
|
|
|
def get_selected_srtp_profile(self) -> bytes: |
|
""" |
|
Get the SRTP protocol which was negotiated. |
|
|
|
:returns: A bytestring of the SRTP profile name. If no profile has been |
|
negotiated yet, returns an empty bytestring. |
|
""" |
|
profile = _lib.SSL_get_selected_srtp_profile(self._ssl) |
|
if not profile: |
|
return b"" |
|
|
|
return _ffi.string(profile.name) |
|
|
|
def request_ocsp(self) -> None: |
|
""" |
|
Called to request that the server sends stapled OCSP data, if |
|
available. If this is not called on the client side then the server |
|
will not send OCSP data. Should be used in conjunction with |
|
:meth:`Context.set_ocsp_client_callback`. |
|
""" |
|
rc = _lib.SSL_set_tlsext_status_type( |
|
self._ssl, _lib.TLSEXT_STATUSTYPE_ocsp |
|
) |
|
_openssl_assert(rc == 1) |
|
|