| 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 |
| |
| _Buffer = typing.Union[bytes, bytearray, memoryview] |
| _T = TypeVar("_T") |
| |
| |
| class _NoOverlappingProtocols: |
| pass |
| |
| |
| NO_OVERLAPPING_PROTOCOLS = _NoOverlappingProtocols() |
| |
| # Callback types. |
| _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 |
| |
| |
| # Taken from https://golang.org/src/crypto/x509/root_linux.go |
| _CERTIFICATE_FILE_LOCATIONS = [ |
| "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc. |
| "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6 |
| "/etc/ssl/ca-bundle.pem", # OpenSUSE |
| "/etc/pki/tls/cacert.pem", # OpenELEC |
| "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7 |
| ] |
| |
| _CERTIFICATE_PATH_LOCATIONS = [ |
| "/etc/ssl/certs", # SLES10/SLES11 |
| ] |
| |
| # These values are compared to output from cffi's ffi.string so they must be |
| # byte strings. |
| _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): # type: ignore[no-untyped-def] |
| 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): # type: ignore[no-untyped-def] |
| try: |
| conn = Connection._reverse_mapping[ssl] |
| |
| # The string passed to us is made up of multiple |
| # length-prefixed bytestrings. We need to split that into a |
| # list. |
| 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 :] |
| |
| # Call the callback |
| 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." |
| ) |
| |
| # Save our callback arguments on the connection object to make |
| # sure that they don't get freed before OpenSSL can use them. |
| # Then, return them in the appropriate output parameters. |
| 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): # type: ignore[no-untyped-def] |
| try: |
| conn = Connection._reverse_mapping[ssl] |
| |
| # Extract the data if any was provided. |
| if cdata != _ffi.NULL: |
| data = _ffi.from_handle(cdata) |
| else: |
| data = None |
| |
| # Call the callback. |
| ocsp_data = callback(conn, data) |
| |
| if not isinstance(ocsp_data, bytes): |
| raise TypeError("OCSP callback must return a bytestring.") |
| |
| # If the OCSP data was provided, we will pass it to OpenSSL. |
| # However, we have an early exit here: if no OCSP data was |
| # provided we will just exit out and tell OpenSSL that there |
| # is nothing to do. |
| if not ocsp_data: |
| return 3 # SSL_TLSEXT_ERR_NOACK |
| |
| # OpenSSL takes ownership of this data and expects it to have |
| # been allocated by OPENSSL_malloc. |
| 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 # SSL_TLSEXT_ERR_ALERT_FATAL |
| |
| 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): # type: ignore[no-untyped-def] |
| try: |
| conn = Connection._reverse_mapping[ssl] |
| |
| # Extract the data if any was provided. |
| if cdata != _ffi.NULL: |
| data = _ffi.from_handle(cdata) |
| else: |
| data = None |
| |
| # Get the OCSP data. |
| ocsp_ptr = _ffi.new("unsigned char **") |
| ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) |
| if ocsp_len < 0: |
| # No OCSP data. |
| ocsp_data = b"" |
| else: |
| # Copy the OCSP data, then pass it to the callback. |
| ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] |
| |
| valid = callback(conn, ocsp_data, data) |
| |
| # Return 1 on success or 0 on error. |
| return int(bool(valid)) |
| |
| except Exception as e: |
| self._problems.append(e) |
| # Return negative value if an exception is hit. |
| 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): # type: ignore[no-untyped-def] |
| 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) |
| # "a zero return value can be used to abort the handshake" |
| 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): # type: ignore[no-untyped-def] |
| 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): # type: ignore[no-untyped-def] |
| if not flag: |
| |
| @wraps(func) |
| def explode(*args, **kwargs): # type: ignore[no-untyped-def] |
| 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" |
| ) |
| |
| _requires_ssl_get0_group_name = _make_requires( |
| getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", 0), |
| "Getting group name is not supported by the linked OpenSSL version", |
| ) |
| |
| |
| 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 |
| |
| |
| F = TypeVar("F", bound=Callable[..., Any]) |
| |
| |
| def _require_not_used(f: F) -> F: |
| @wraps(f) |
| def inner(self: Context, *args: Any, **kwargs: Any) -> Any: |
| if self._used: |
| warnings.warn( |
| ( |
| "Attempting to mutate a Context after a Connection was " |
| "created. In the future, this will raise an exception" |
| ), |
| DeprecationWarning, |
| stacklevel=2, |
| ) |
| return f(self, *args, **kwargs) |
| |
| return typing.cast(F, inner) |
| |
| |
| 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._used = False |
| 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 |
| | _lib.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
| ) |
| if version is not None: |
| self.set_min_proto_version(version) |
| self.set_max_proto_version(version) |
| |
| @_require_not_used |
| 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 |
| ) |
| |
| @_require_not_used |
| 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 |
| ) |
| |
| @_require_not_used |
| 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 |
| ) |
| |
| @_require_not_used |
| 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 |
| |
| @_require_not_used |
| 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 ``openssl@3`` `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 |
| """ |
| # SSL_CTX_set_default_verify_paths will attempt to load certs from |
| # both a cafile and capath that are set at compile time. However, |
| # it will first check environment variables and, if present, load |
| # those paths instead |
| set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) |
| _openssl_assert(set_result == 1) |
| # After attempting to set default_verify_paths we need to know whether |
| # to go down the fallback path. |
| # First we'll check to see if any env vars have been set. If so, |
| # we won't try to do anything else because the user has set the path |
| # themselves. |
| if not self._check_env_vars_set("SSL_CERT_DIR", "SSL_CERT_FILE"): |
| default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) |
| default_file = _ffi.string(_lib.X509_get_default_cert_file()) |
| # Now we check to see if the default_dir and default_file are set |
| # to the exact values we use in our manylinux builds. If they are |
| # then we know to load the fallbacks |
| if ( |
| default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR |
| and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE |
| ): |
| # This is manylinux, let's load our fallback paths |
| 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 |
| |
| @_require_not_used |
| 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() |
| |
| @_require_not_used |
| 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() |
| |
| @_require_not_used |
| def use_certificate(self, cert: X509 | x509.Certificate) -> None: |
| """ |
| Load a certificate from a X509 object |
| |
| :param cert: The X509 object |
| :return: None |
| """ |
| # Mirrored at Connection.use_certificate |
| 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() |
| |
| @_require_not_used |
| 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: |
| # TODO: This is untested. |
| _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() |
| |
| @_require_not_used |
| 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() |
| |
| @_require_not_used |
| def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: |
| """ |
| Load a private key from a PKey object |
| |
| :param pkey: The PKey object |
| :return: None |
| """ |
| # Mirrored at Connection.use_privatekey |
| 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() |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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 |
| ) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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)`). |
| |
| Note this API does not change the cipher suites used in TLS 1.3 |
| Use `set_tls13_ciphersuites` for that. |
| |
| :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 |
| ) |
| |
| @_require_not_used |
| def set_tls13_ciphersuites(self, ciphersuites: bytes) -> None: |
| """ |
| Set the list of TLS 1.3 ciphers to be used in this context. |
| OpenSSL maintains a separate list of TLS 1.3+ ciphers to |
| ciphers for TLS 1.2 and lowers. |
| |
| See the OpenSSL manual for more information (e.g. |
| :manpage:`ciphers(1)`). |
| |
| :param bytes ciphersuites: An OpenSSL cipher string containing |
| TLS 1.3+ ciphersuites. |
| :return: None |
| |
| .. versionadded:: 25.2.0 |
| """ |
| if not isinstance(ciphersuites, bytes): |
| raise TypeError("ciphersuites must be a byte string.") |
| |
| _openssl_assert( |
| _lib.SSL_CTX_set_ciphersuites(self._context, ciphersuites) == 1 |
| ) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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): # type: ignore[no-untyped-def] |
| 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 |
| @_require_not_used |
| 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): # type: ignore[no-untyped-def] |
| 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 |
| |
| @_require_not_used |
| 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: |
| # TODO: This is untested. |
| return None |
| |
| pystore = X509Store.__new__(X509Store) |
| pystore._store = store |
| return pystore |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| def clear_mode(self, mode_to_clear: int) -> int: |
| """ |
| Modes previously set cannot be overwritten without being |
| cleared first. This method should be used to clear existing modes. |
| """ |
| return _lib.SSL_CTX_clear_mode(self._context, mode_to_clear) |
| |
| @_require_not_used |
| 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): # type: ignore[no-untyped-def] |
| 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 |
| ) |
| |
| @_require_not_used |
| 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 |
| ) |
| |
| @_require_not_used |
| 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']``. |
| """ |
| # Different versions of OpenSSL are inconsistent about how they handle |
| # empty proto lists (see #1043), so we avoid the problem entirely by |
| # rejecting them ourselves. |
| if not protos: |
| raise ValueError("at least one protocol must be specified") |
| |
| # Take the list of protocols and join them together, prefixing them |
| # with their lengths. |
| protostr = b"".join( |
| chain.from_iterable((bytes((len(p),)), p) for p in protos) |
| ) |
| |
| # Build a C string from the list. We don't need to save this off |
| # because OpenSSL immediately copies the data out. |
| input_str = _ffi.new("unsigned char[]", protostr) |
| |
| # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: |
| # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() |
| # return 0 on success, and non-0 on failure. |
| # WARNING: these functions reverse the return value convention. |
| _openssl_assert( |
| _lib.SSL_CTX_set_alpn_protos( |
| self._context, input_str, len(protostr) |
| ) |
| == 0 |
| ) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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) |
| |
| @_require_not_used |
| 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, |
| ) |
| |
| @_require_not_used |
| 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") |
| |
| context._used = True |
| |
| ssl = _lib.SSL_new(context._context) |
| self._ssl = _ffi.gc(ssl, _lib.SSL_free) |
| # We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns |
| # an SSL_ERROR_WANT_READ when processing a non-application data packet |
| # even though there is still data on the underlying transport. |
| # See https://github.com/openssl/openssl/issues/6234 for more details. |
| _lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) |
| self._context = context |
| self._app_data = None |
| |
| # References to strings used for Application Layer Protocol |
| # Negotiation. These strings get copied at some point but it's well |
| # after the callback returns, so we have to hang them somewhere to |
| # avoid them getting freed. |
| self._alpn_select_callback_args: Any = None |
| |
| # Reference the verify_callback of the Context. This ensures that if |
| # set_verify is called again after the SSL object has been created we |
| # do not point to a dangling reference |
| self._verify_helper = context._verify_helper |
| self._verify_callback = context._verify_callback |
| |
| # And likewise for the cookie callbacks |
| 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 |
| # Don't set up any gc for these, SSL_free will take care of them. |
| 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: |
| # TODO: This is untested. |
| 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: |
| # TODO: This is untested, but I think twisted hits it? |
| _raise_current_error() |
| elif error == _lib.SSL_ERROR_SSL and _lib.ERR_peek_error() != 0: |
| # In 3.0.x an unexpected EOF no longer triggers syscall error |
| # but we want to maintain compatibility so we check here and |
| # raise syscall if it is an EOF. Since we're not actually sure |
| # what else could raise SSL_ERROR_SSL we check for the presence |
| # of the OpenSSL 3 constant SSL_R_UNEXPECTED_EOF_WHILE_READING |
| # and if it's not present we just raise an error, which matches |
| # the behavior before we added this elif section |
| 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 |
| self._context._used = True |
| |
| 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 |
| """ |
| # Mirrored from Context.use_certificate |
| 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 |
| """ |
| # Mirrored from Context.use_privatekey |
| 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") |
| |
| # XXX I guess this can fail sometimes? |
| _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: _Buffer, 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 |
| """ |
| # Backward compatibility |
| buf = _text_to_bytes_and_warn("buf", buf) |
| |
| with _ffi.from_buffer(buf) as data: |
| # check len(buf) instead of len(data) for testability |
| 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: _Buffer, 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: |
| # SSL_write's num arg is an int, |
| # so we cannot send more than 2**31-1 bytes at once. |
| 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, # collections.abc.Buffer once we use Python 3.12+ |
| 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)) |
| |
| # We need to create a temporary buffer. This is annoying, it would be |
| # better if we could pass memoryviews straight into the SSL_read call, |
| # but right now we can't. Revisit this if CFFI gets that ability. |
| 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) |
| |
| # This strange line is all to avoid a memory copy. The buffer protocol |
| # should allow us to assign a CFFI buffer to the LHS of this line, but |
| # on CPython 3.3+ that segfaults. As a workaround, we can temporarily |
| # wrap it in a memoryview. |
| 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): |
| # TODO: This is untested. |
| raise WantWriteError() |
| elif _lib.BIO_should_io_special(bio): |
| # TODO: This is untested. I think io_special means the socket |
| # BIO has a not-yet connected socket. |
| raise ValueError("BIO_should_io_special") |
| else: |
| # TODO: This is untested. |
| raise ValueError("unknown bio failure") |
| else: |
| # TODO: This is untested. |
| _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: _Buffer) -> 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) # type: ignore[return-value, union-attr] |
| |
| 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 # type: ignore[union-attr] |
| 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() # type: ignore[union-attr] |
| 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 |
| """ |
| # Possible future extension: return the BIO_ADDR in some form. |
| bio_addr = _lib.BIO_ADDR_new() |
| try: |
| result = _lib.DTLSv1_listen(self._ssl, bio_addr) |
| finally: |
| _lib.BIO_ADDR_free(bio_addr) |
| # DTLSv1_listen is weird. A zero return value means 'didn't find a |
| # ClientHello with valid cookie, but keep trying'. So basically |
| # WantReadError. But it doesn't work correctly with _raise_ssl_error. |
| # So we raise it manually instead. |
| 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: |
| # TODO: This is untested. |
| 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) # type: ignore[return-value, union-attr] |
| |
| @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 |
| """ |
| # OpenSSL 1.1+ |
| 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. |
| """ |
| # The OpenSSL documentation says nothing about what might happen if the |
| # count argument given is zero. Specifically, it doesn't say whether |
| # the output buffer may be NULL in that case or not. Inspection of the |
| # implementation reveals that it calls memcpy() unconditionally. |
| # Section 7.1.4, paragraph 1 of the C standard suggests that |
| # memcpy(NULL, source, 0) is not guaranteed to produce defined (let |
| # alone desirable) behavior (though it probably does on just about |
| # every implementation...) |
| # |
| # Allocate a tiny buffer to pass in (instead of just passing NULL as |
| # one might expect) for the initial call so as to be safe against this |
| # potentially undefined behavior. |
| empty = _ffi.new("char[]", 0) |
| size = function(self._ssl, empty, 0) |
| if size == 0: |
| # No Finished message so far. |
| 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']``. |
| """ |
| # Different versions of OpenSSL are inconsistent about how they handle |
| # empty proto lists (see #1043), so we avoid the problem entirely by |
| # rejecting them ourselves. |
| if not protos: |
| raise ValueError("at least one protocol must be specified") |
| |
| # Take the list of protocols and join them together, prefixing them |
| # with their lengths. |
| protostr = b"".join( |
| chain.from_iterable((bytes((len(p),)), p) for p in protos) |
| ) |
| |
| # Build a C string from the list. We don't need to save this off |
| # because OpenSSL immediately copies the data out. |
| input_str = _ffi.new("unsigned char[]", protostr) |
| |
| # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: |
| # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() |
| # return 0 on success, and non-0 on failure. |
| # WARNING: these functions reverse the return value convention. |
| _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) |
| |
| @_requires_ssl_get0_group_name |
| def get_group_name(self) -> str | None: |
| """ |
| Get the name of the negotiated group for the key exchange. |
| |
| :return: A string giving the group name or :data:`None`. |
| """ |
| # Do not remove this guard. |
| # SSL_get0_group_name crashes with a segfault if called without |
| # an established connection (should return NULL but doesn't). |
| session = _lib.SSL_get_session(self._ssl) |
| if session == _ffi.NULL: |
| return None |
| |
| group_name = _lib.SSL_get0_group_name(self._ssl) |
| if group_name == _ffi.NULL: |
| return None |
| |
| return _ffi.string(group_name).decode("utf-8") |
| |
| 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) |
| |
| 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): # type: ignore[no-untyped-def] |
| callback(Connection._reverse_mapping[ssl], where, return_code) |
| |
| self._info_callback = _ffi.callback( |
| "void (*)(const SSL *, int, int)", wrapper |
| ) |
| _lib.SSL_set_info_callback(self._ssl, self._info_callback) |