17.3. ssl —套接字对象的 TLS/SSL 包装器

2.6 版的新Function。

源代码: Lib/ssl.py


此模块提供对 Client 端和服务器端网络套接字的传输层安全性(通常称为“安全套接字层”)加密和对等身份验证Function的访问。该模块使用 OpenSSL 库。只要在该平台上安装了 OpenSSL,它就可以在所有现代 Unix 系统,Windows,Mac OS X 以及可能的其他平台上使用。

在版本 2.7.13 中更改:已更新以支持与 OpenSSL 1.1.0 链接

Note

由于对 os 套接字 API 进行了调用,因此某些行为可能取决于平台。安装的 OpenSSL 版本也可能导致行为变化。例如,TLSv1.1 和 TLSv1.2 随附 openssl 版本 1.0.1.

Warning

请勿在未阅读Security considerations的情况下使用此模块。这样做可能会导致错误的安全感,因为 ssl 模块的默认设置不一定适合您的应用程序。

本节记录了ssl模块中的对象和Function;有关 TLS,SSL 和证书的更多常规信息,请参阅底部“另请参阅”部分中的文档。

此模块提供了一个类ssl.SSLSocket,该类是从socket.socket类型派生的,并提供了类似于套接字的包装器,该包装器还使用 SSL 对pass套接字的数据进行加密和解密。它支持其他方法,例如getpeercert()(用于检索连接另一侧的证书)和cipher()(用于检索用于安全连接的密码)。

对于更复杂的应用程序,ssl.SSLContext类有助于 Management 设置和证书,然后可以passpassSSLContext.wrap_socket()方法创建的 SSL 套接字继承这些设置和证书。

17.3.1. 函数,常量和异常

  • exception ssl. SSLError

    • 引发 signal 以指示来自基础 SSL 实现的错误(当前由 OpenSSL 库提供)。这表示在底层网络连接上叠加的更高级别的加密和身份验证层中存在一些问题。此错误是socket.error的子类型,而后者又是IOError的子类型。 SSLError实例的错误代码和消息由 OpenSSL 库提供。
  • library

    • 一个字符串助记符,指定发生错误的 OpenSSL 子模块,例如SSLPEMX509。可能值的范围取决于 OpenSSL 版本。

2.7.9 版中的新Function。

  • reason
    • 一个字符串助记符,指示发生此错误的原因,例如CERTIFICATE_VERIFY_FAILED。可能值的范围取决于 OpenSSL 版本。

2.7.9 版中的新Function。

  • exception ssl. SSLZeroReturnError
    • try读取或写入时引发了SSLError的子类,并且 SSL 连接已完全关闭。请注意,这并不意味着基础传输(读取 TCP)已关闭。

2.7.9 版中的新Function。

  • exception ssl. SSLWantReadError
    • try读取或写入数据时,由非阻塞 SSL 套接字引发的SSLError子类,但是在满足请求之前,需要在基础 TCP 传输上接收更多数据。

2.7.9 版中的新Function。

  • exception ssl. SSLWantWriteError
    • try读取或写入数据时由非阻塞 SSL 套接字引发的SSLError子类,但是在满足请求之前,需要在基础 TCP 传输上发送更多数据。

2.7.9 版中的新Function。

  • exception ssl. SSLSyscallError
    • try在 SSL 套接字上执行操作时遇到系统错误时,将引发SSLError的子类。不幸的是,没有简单的方法来检查原始的 errno 号。

2.7.9 版中的新Function。

  • exception ssl. SSLEOFError
    • SSL 连接突然终止时引发的SSLError子类。通常,遇到此错误时,您不应try重用基础传输。

2.7.9 版中的新Function。

  • exception ssl. CertificateError
    • 引发以表示证书错误(例如主机名不匹配)。但是,OpenSSL 检测到的证书错误会引发SSLError

17.3.1.1. 套接字创建

以下Function允许创建独立的套接字。从 Python 2.7.9 开始,改为使用SSLContext.wrap_socket()可以更加灵活。

  • ssl. wrap_socket(* sock keyfile = None certfile = None server_side = False cert_reqs = CERT_NONE ssl_version ={ } ca_certs = None do_handshake_on_connect = True *, * suppress_ragged_eofs = True ciphers = None *)
    • 接受socket.socket的实例sock并返回ssl.SSLSocket的实例(socket.socket的子类型),该实例将基础套接字包装在 SSL 上下文中。 sock必须是SOCK_STREAM插槽;其他套接字类型不受支持。

对于 Client 端套接字,上下文构造是延迟的。如果基础套接字尚未连接,则在套接字上调用connect()之后将执行上下文构造。对于服务器端套接字,如果套接字没有远程对等方,则假定它是侦听套接字,并且对passaccept()方法接受的 Client 端连接自动执行服务器端 SSL 包装。 wrap_socket()可以加注SSLError

keyfilecertfile参数指定了可选文件,这些文件包含用于标识连接本地的证书。有关证书如何存储在certfile中的更多信息,请参见Certificates的讨论。

参数server_side是一个布尔值,它标识此套接字是否需要服务器端或 Client 端行为。

参数cert_reqs指定是否从连接的另一端要求证书,以及如果提供的话是否将对其进行验证。它必须是以下三个值之一:CERT_NONE(忽略证书),CERT_OPTIONAL(不是必需的,但如果提供则经过验证)或CERT_REQUIRED(必需和经过验证)。如果此参数的值不是CERT_NONE,则ca_certs参数必须指向 CA 证书文件。

ca_certs文件包含一组串联的“证书颁发机构”证书,这些证书用于验证从连接另一端传递的证书。有关如何在此文件中排列证书的更多信息,请参见Certificates的讨论。

参数ssl_version指定要使用的 SSL 协议版本。通常,服务器选择特定的协议版本,并且 Client 端必须适应服务器的选择。大多数版本不能与其他版本互操作。如果未指定,则默认为PROTOCOL_SSLv23;它提供与其他版本的最大兼容性。

下表显示了 Client 端(侧面)中的哪些版本可以连接到服务器(顶部)中的哪些版本:

Note

Client 端服务器*SSLv2SSLv3SSLv23TLSv1TLSv1.1TLSv1.2
SSLv2yesnoyesnonono
SSLv3noyesyesnonono
* SSLv23 * [1]noyesyesyesyesyes
TLSv1nonoyesyesnono
TLSv1.1nonoyesnoyesno
TLSv1.2nonoyesnonoyes

Footnotes

  • [1]
    • TLS 1.3 协议将与 OpenSSL> = 1.1.1 中的PROTOCOL_SSLv23一起提供。仅 TLS 1.3 没有专用的 PROTOCOL 常数。

Note

哪些连接成功取决于 OpenSSL 的版本。例如,在 OpenSSL 1.0.0 之前,SSLv23Client 端将始终try进行 SSLv2 连接。

参数do_handshake_on_connect指定是在执行socket.connect()之后自动进行 SSL 握手,还是pass调用SSLSocket.do_handshake()方法来使应用程序显式调用它。显式调用SSLSocket.do_handshake()可使程序控制握手中涉及的套接字 I/O 的阻塞行为。

参数suppress_ragged_eofs指定SSLSocket.read()方法应如何从连接的另一端发出意外的 EOFsignal。如果指定为True(默认值),它会响应来自底层套接字的意外 EOF 错误而返回正常的 EOF(空字节对象)。如果False,它将引发异常返回给调用者。

在 2.7 版中进行了更改:新的可选参数* ciphers *。

17.3.1.2. 上下文创建

便利Function可帮助创建SSLContext对象以用于常见目的。

  • ssl. create_default_context(* purpose = Purpose.SERVER_AUTH cafile = None capath = None cadata = None *)
    • 返回具有给定用途的默认设置的新SSLContext对象。这些设置由ssl模块选择,通常代表比直接调用SSLContext构造函数更高的安全级别。
  • cafile capath cadata *代表可选的 CA 证书,可信任该证书以进行证书验证,如SSLContext.load_verify_locations()。如果所有三个均为None,则此函数可以选择信任系统的默认 CA 证书。

设置为:具有高加密密码套件的PROTOCOL_SSLv23OP_NO_SSLv2OP_NO_SSLv3,而 RC4 和未经身份验证的密码套件。将SERVER_AUTH用作目的会将verify_mode设置为CERT_REQUIRED并加载 CA 证书(当至少给出* cafile capath cadata *之一时)或使用SSLContext.load_default_certs()加载默认的 CA 证书。

Note

协议,选项,密码和其他设置可以随时更改为更具限制性的值,而无需事先弃用。这些值表示兼容性和安全性之间的合理平衡。

如果您的应用程序需要特定的设置,则应创建一个SSLContext并自己应用设置。

Note

如果您发现某些较旧的 Client 端或服务器try与此Function创建的SSLContext进行连接时收到错误消息,指出“协议或密码套件不匹配”,则可能是它们仅支持 SSL3.0,但此Function不使用OP_NO_SSLv3。 SSL3.0 被广泛认为是completely broken。如果您仍然希望 continue 使用此Function但仍允许 SSL 3.0 连接,则可以使用以下方法重新启用它们:

ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
ctx.options &= ~ssl.OP_NO_SSLv3

2.7.9 版中的新Function。

在版本 2.7.10 中更改:从默认密码字符串中删除了 RC4.

在版本 2.7.13 中进行了更改:ChaCha20/Poly1305 已添加到默认密码字符串中。

从默认密码字符串中删除了 3 DES。

  • ssl. _https_verify_certificates(* enable = True *)
    • 指定在创建 Client 端 HTTPS 连接而不指定特定的 SSL 上下文时是否验证服务器证书。

从 Python 2.7.9 开始,httplib和使用它的模块(例如urllib2xmlrpclib)默认设置为在构建 Client 端 HTTPS 连接时验证收到的远程服务器证书。此默认验证将检查证书是否由系统信任库中的证书颁发机构签名,并且所提供证书上的公用名(或主题备用名)是否与请求的主机匹配。

将* enable *设置为True可确保此默认行为有效。

将* enable *设置为False会将默认的 HTTPS 证书处理恢复为 Python 2.7.8 及更早版本的 HTTPS 证书处理,从而允许使用自签名证书的服务器,使用系统信任库中不存在的由 Certicate Authority 签名的证书的服务器连接到服务器主机名与显示的服务器证书不匹配。

该函数的前划线表示该函数有意在任何 Python 3 实现中都不存在,并且可能在所有 Python 2.7 实现中都不存在。必要时可绕过证书检查或系统信任存储的可移植方法是使工具pass显式传入适当配置的 SSL 上下文(而不是还原标准库 Client 端模块的默认行为)来逐案启用该方法。

2.7.12 版中的新Function。

See also

  • CVE-2014-9365 –使用默认设置的针对 PythonClient 端的 HTTPS 中间人攻击

  • PEP 476 –默认情况下为 HTTPS 启用证书验证

  • PEP 493 –适用于 Python 2.7 的 HTTPS 验证迁移工具

17.3.1.3. 随机产生

Note

从 2.7.13 版本开始不推荐使用:OpenSSL 不推荐使用ssl.RAND_pseudo_bytes(),而请使用ssl.RAND_bytes()

  • ssl. RAND_status ( )

    • 如果已经为 SSL 伪随机数生成器提供了“足够”的随机性,则返回True,否则返回False。您可以使用ssl.RAND_egd()ssl.RAND_add()来增加伪随机数生成器的随机性。
  • ssl. RAND_egd(* path *)

    • 如果您在某个地方运行熵收集守护程序(EGD),并且* path *是向其打开的套接字连接的路径名,则它将从套接字读取 256 个字节的随机性,并将其添加到 SSL 伪随机数中生成器以增加生成的 Secret 密钥的安全性。通常只有在没有更好随机性的系统上才需要这样做。

有关熵收集守护程序的来源,请参见http://egd.sourceforge.net/http://prngd.sourceforge.net/

可用性:不适用于 LibreSSL 和 OpenSSL> 1.1.0

  • ssl. RAND_add(* bytes entropy *)
    • 将给定的* bytes 混合到 SSL 伪随机数生成器中。参数 entropy *(浮点数)是字符串中包含的熵的下限(因此您可以始终使用0.0)。有关熵来源的更多信息,请参见 RFC 1750

17.3.1.4. 证书处理

  • ssl. match_hostname(* cert hostname *)
    • 验证* cert (以SSLSocket.getpeercert()返回的解码格式)匹配给定的 hostname *。所应用的规则是 RFC 2818 RFC 6125中概述的用于检查 HTTPS 服务器身份的规则,但当前不支持 IP 地址。除 HTTPS 之外,此Function还应适用于检查各种基于 SSL 的协议(例如 FTPS,IMAPS,POPS 等)中服务器的身份。

失败时引发CertificateError。成功时,该函数不返回任何内容:

>>> cert = {'subject': ((('commonName', 'example.com'),),)}
>>> ssl.match_hostname(cert, "example.com")
>>> ssl.match_hostname(cert, "example.org")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/py3k/Lib/ssl.py", line 130, in match_hostname
ssl.CertificateError: hostname 'example.org' doesn't match 'example.com'

2.7.9 版中的新Function。

  • ssl. cert_time_to_seconds(* cert_time *)
    • "%b %d %H:%M:%S %Y %Z" strptime 格式(C 语言环境)给定的cert_time字符串表示来自证书的“ notBefore”或“ notAfter”日期,返回自大纪元以来的时间(秒)。

这是一个例子:

>>> import ssl
>>> timestamp = ssl.cert_time_to_seconds("Jan  5 09:34:43 2018 GMT")
>>> timestamp
1515144883
>>> from datetime import datetime
>>> print(datetime.utcfromtimestamp(timestamp))
2018-01-05 09:34:43

“ notBefore”或“ notAfter”日期必须使用格林尼治标准时间( RFC 5280)。

在版本 2.7.9 中更改:将 Importing 时间解释为 UTC 中 Importing 字符串中“ GMT”时区指定的时间。以前使用本地时区。返回一个整数(Importing 格式中不包括秒)

  • ssl. get_server_certificate(* addr ssl_version = PROTOCOL_SSLv23 ca_certs = None *)
    • 给定受 SSL 保护的服务器的地址addr,作为(* hostname port-number *)对,获取服务器的证书,并将其作为 PEM 编码的字符串返回。如果指定ssl_version,则使用该版本的 SSL 协议try连接到服务器。如果指定了ca_certs,则它应该是包含根证书列表的文件,该格式与wrap_socket()中的相同参数所使用的格式相同。该调用将try根据该组根证书来验证服务器证书,并且如果验证try失败将失败。

在版本 2.7.9 中更改:此Function现在与 IPv6 兼容,默认的* ssl_version *从PROTOCOL_SSLv3更改为PROTOCOL_SSLv23,以最大程度地与现代服务器兼容。

  • ssl. DER_cert_to_PEM_cert(* DER_cert_bytes *)

    • 给定证书作为 DER 编码的字节块,则返回同一证书的 PEM 编码的字符串版本。
  • ssl. PEM_cert_to_DER_cert(* PEM_cert_string *)

    • 给定证书为 ASCII PEM 字符串,则返回该证书的 DER 编码字节序列。
  • ssl. get_default_verify_paths ( )

  • cafile-解析到 cafile 的路径,或者None(如果文件不存在),

  • capath-已解析为 capath 的路径;如果目录不存在,则为None

  • openssl_cafile_env-指向 cafile 的 OpenSSL 环境密钥,

  • openssl_cafile-cafile 的硬编码路径,

  • openssl_capath_env-OpenSSL 的环境密钥,它指向一个安全范围,

  • openssl_capath-Capath 目录的硬编码路径

可用性:LibreSSL 忽略环境变量openssl_cafile_envopenssl_capath_env

2.7.9 版中的新Function。

  • ssl. enum_certificates(* store_name *)
    • 从 Windows 的系统证书库中检索证书。 * store_name *可以是CAROOTMY之一。 Windows 也可能提供其他证书存储。

该函数返回(cert_bytes,encoding_type,trust)Tuples 的列表。 encoding_type 指定 cert_bytes 的编码。对于 X.509 ASN.1 数据,它是x509_asn;对于 PKCS#7 ASN.1 数据,它是pkcs_7_asn。信任将证书的目的指定为一组 OIDS,或者如果证书对所有目的都是可信任的,则将其确切指定为True

Example:

>>> ssl.enum_certificates("CA")
[(b'data...', 'x509_asn', {'1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2'}),
 (b'data...', 'x509_asn', True)]

Availability: Windows.

2.7.9 版中的新Function。

  • ssl. enum_crls(* store_name *)
    • 从 Windows 的系统证书库中检索 CRL。 * store_name *可以是CAROOTMY之一。 Windows 也可能提供其他证书存储。

该函数返回(cert_bytes,encoding_type,trust)Tuples 的列表。 encoding_type 指定 cert_bytes 的编码。对于 X.509 ASN.1 数据,它是x509_asn;对于 PKCS#7 ASN.1 数据,它是pkcs_7_asn

Availability: Windows.

2.7.9 版中的新Function。

17.3.1.5. Constants

  • ssl. CERT_NONE
    • SSLContext.verify_mode的可能值,或wrap_socket()cert_reqs参数。在此模式(默认)下,套接字连接的另一端将不需要证书。如果从另一端收到证书,则不会try验证它。

请参阅下面对Security considerations的讨论。

  • ssl. CERT_OPTIONAL
    • SSLContext.verify_mode的可能值,或wrap_socket()cert_reqs参数。在这种模式下,套接字连接的另一端将不需要证书。但是如果提供了它们,将try进行验证,并且在失败时将引发SSLError

使用此设置要求将一组有效的 CA 证书传递给SSLContext.load_verify_locations()或作为ca_certs参数的值传递给wrap_socket()

  • ssl. CERT_REQUIRED
    • SSLContext.verify_mode的可能值,或wrap_socket()cert_reqs参数。在这种模式下,需要从套接字连接的另一端来获得证书。如果没有提供证书或验证失败,则会引发SSLError

使用此设置要求将一组有效的 CA 证书传递给SSLContext.load_verify_locations()或作为ca_certs参数的值传递给wrap_socket()

  • ssl. VERIFY_DEFAULT
    • SSLContext.verify_flags的可能值。在这种模式下,不检查证书吊销列表(CRL)。默认情况下,OpenSSL 既不需要也不验证 CRL。

2.7.9 版中的新Function。

  • ssl. VERIFY_CRL_CHECK_LEAF
    • SSLContext.verify_flags的可能值。在此模式下,仅对等证书是受检查的,而不是中间 CA 证书。该模式需要由对等证书的颁发者(其直接祖先 CA)签名的有效 CRL。如果尚未加载SSLContext.load_verify_locations,则验证将失败。

2.7.9 版中的新Function。

  • ssl. VERIFY_CRL_CHECK_CHAIN

2.7.9 版中的新Function。

2.7.9 版中的新Function。

  • ssl. VERIFY_X509_TRUSTED_FIRST
    • SSLContext.verify_flags的可能值。它指示 OpenSSL 在构建用于验证证书的信任链时首选可信任证书。默认情况下启用此标志。

2.7.10 版中的新Function。

  • ssl. PROTOCOL_TLS
    • 选择 Client 端和服务器都支持的最高协议版本。尽管名称,此选项可以选择“ TLS”协议以及“ SSL”。

版本 2.7.13 中的新Function。

  • ssl. PROTOCOL_SSLv23
    • PROTOCOL_TLS的别名。

从 2.7.13 版开始不推荐使用:改为使用PROTOCOL_TLS

  • ssl. PROTOCOL_SSLv2
    • 选择 SSL 版本 2 作为通道加密协议。

如果 OpenSSL 是使用OPENSSL_NO_SSL2标志编译的,则此协议不可用。

Warning

SSL 版本 2 不安全。不鼓励使用它。

从 2.7.13 版本开始不推荐使用:OpenSSL 删除了对 SSLv2 的支持。

  • ssl. PROTOCOL_SSLv3
    • 选择 SSL 版本 3 作为通道加密协议。

如果 OpenSSL 是使用OPENSSL_NO_SSLv3标志编译的,则此协议不可用。

Warning

SSL 版本 3 不安全。不鼓励使用它。

从 2.7.13 版本开始不推荐使用:OpenSSL 不推荐所有特定于版本的协议。使用带有OP_NO_SSLv3等标志的默认协议。

  • ssl. PROTOCOL_TLSv1
    • 选择 TLS 版本 1.0 作为通道加密协议。

从 2.7.13 版本开始不推荐使用:OpenSSL 不推荐所有特定于版本的协议。使用带有OP_NO_SSLv3等标志的默认协议。

  • ssl. PROTOCOL_TLSv1_1
    • 选择 TLS 版本 1.1 作为通道加密协议。仅适用于 openssl 版本 1.0.1.

2.7.9 版中的新Function。

从 2.7.13 版本开始不推荐使用:OpenSSL 不推荐所有特定于版本的协议。使用带有OP_NO_SSLv3等标志的默认协议。

  • ssl. PROTOCOL_TLSv1_2
    • 选择 TLS 版本 1.2 作为通道加密协议。这是最现代的版本,而且如果双方都能说的话,可能是获得最大保护的最佳选择。仅适用于 openssl 版本 1.0.1.

2.7.9 版中的新Function。

从 2.7.13 版本开始不推荐使用:OpenSSL 不推荐所有特定于版本的协议。使用带有OP_NO_SSLv3等标志的默认协议。

  • ssl. OP_ALL
    • 为其他 SSL 实现中存在的各种错误启用解决方法。默认情况下设置此选项。它不必设置与 OpenSSL 的SSL_OP_ALL常量相同的标志。

2.7.9 版中的新Function。

  • ssl. OP_NO_SSLv2
    • 阻止 SSLv2 连接。此选项仅与PROTOCOL_SSLv23结合使用。这样可以防止对等方选择 SSLv2 作为协议版本。

2.7.9 版中的新Function。

  • ssl. OP_NO_SSLv3
    • 阻止 SSLv3 连接。此选项仅与PROTOCOL_SSLv23结合使用。这样可以防止对等方选择 SSLv3 作为协议版本。

2.7.9 版中的新Function。

  • ssl. OP_NO_TLSv1
    • 阻止 TLSv1 连接。此选项仅与PROTOCOL_SSLv23结合使用。这样可以防止对等方选择 TLSv1 作为协议版本。

2.7.9 版中的新Function。

  • ssl. OP_NO_TLSv1_1
    • 阻止 TLSv1.1 连接。此选项仅与PROTOCOL_SSLv23结合使用。这样可以防止对等方选择 TLSv1.1 作为协议版本。仅适用于 openssl 版本 1.0.1.

2.7.9 版中的新Function。

  • ssl. OP_NO_TLSv1_2
    • 阻止 TLSv1.2 连接。此选项仅与PROTOCOL_SSLv23结合使用。这样可以防止对等方选择 TLSv1.2 作为协议版本。仅适用于 openssl 版本 1.0.1.

2.7.9 版中的新Function。

  • ssl. OP_NO_TLSv1_3
    • 阻止 TLSv1.3 连接。此选项仅与PROTOCOL_TLS结合使用。这样可以防止对等方选择 TLSv1.3 作为协议版本。 TLS 1.3 可用于 OpenSSL 1.1.1 或更高版本。针对旧版本的 OpenSSL 编译 Python 后,该标志默认为* 0 *。

2.7.15 版中的新Function。

  • ssl. OP_CIPHER_SERVER_PREFERENCE
    • 使用服务器的密码排序首选项,而不是 Client 端的密码。此选项对 Client 端套接字和 SSLv2 服务器套接字无效。

2.7.9 版中的新Function。

  • ssl. OP_SINGLE_DH_USE
    • 防止针对不同的 SSL 会话重复使用相同的 DH 密钥。这提高了前向保密性,但是需要更多的计算资源。此选项仅适用于服务器套接字。

2.7.9 版中的新Function。

  • ssl. OP_SINGLE_ECDH_USE
    • 防止针对不同的 SSL 会话重复使用相同的 ECDH 密钥。这提高了前向保密性,但是需要更多的计算资源。此选项仅适用于服务器套接字。

2.7.9 版中的新Function。

  • ssl. OP_ENABLE_MIDDLEBOX_COMPAT
    • 在 TLS 1.3 握手中发送虚拟的更改密码规范(CCS)消息,以使 TLS 1.3 连接看起来更像是 TLS 1.2 连接。

此选项仅在 OpenSSL 1.1.1 和更高版本中可用。

2.7.16 版中的新Function。

  • ssl. OP_NO_COMPRESSION
    • 禁用 SSL 通道上的压缩。如果应用程序协议支持其自己的压缩方案,这将很有用。

此选项仅在 OpenSSL 1.0.0 和更高版本中可用。

2.7.9 版中的新Function。

  • ssl. HAS_ALPN
    • RFC 7301中所述,OpenSSL 库是否具有对* Application-Layer Protocol Negotiation * TLS 扩展的内置支持。

2.7.10 版中的新Function。

  • ssl. HAS_ECDH
    • OpenSSL 库是否内置支持基于椭圆曲线的 Diffie-Hellman 密钥交换。除非发布者明确禁用了此Function,否则应该为 true。

2.7.9 版中的新Function。

  • ssl. HAS_SNI
    • OpenSSL 库是否对* Server Name Indication *扩展(在 RFC 4366中定义)具有内置支持。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

  • ssl. HAS_TLSv1_3
    • OpenSSL 库是否具有对 TLS 1.3 协议的内置支持。

2.7.15 版中的新Function。

2.7.9 版中的新Function。

  • ssl. OPENSSL_VERSION
    • 解释器加载的 OpenSSL 库的版本字符串:
>>> ssl.OPENSSL_VERSION
'OpenSSL 0.9.8k 25 Mar 2009'

2.7 版的新Function。

  • ssl. OPENSSL_VERSION_INFO
    • 五个整数的 Tuples,表示有关 OpenSSL 库的版本信息:
>>> ssl.OPENSSL_VERSION_INFO
(0, 9, 8, 11, 15)

2.7 版的新Function。

  • ssl. OPENSSL_VERSION_NUMBER
    • OpenSSL 库的原始版本号,以单个整数表示:
>>> ssl.OPENSSL_VERSION_NUMBER
9470143L
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x9080bfL'

2.7 版的新Function。

  • ssl. ALERT_DESCRIPTION_HANDSHAKE_FAILURE

  • ssl. ALERT_DESCRIPTION_INTERNAL_ERROR

  • ALERT_DESCRIPTION_*

用作SSLContext.set_servername_callback()中的回调函数的返回值。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

17.3.2. SSL 套接字

SSL 套接字提供以下Socket Objects方法:

但是,由于 SSL(和 TLS)协议在 TCP 之上有其自己的框架,因此 SSL 套接字抽象在某些方面可能会偏离正常的 OS 级别套接字的规范。尤其参见非阻塞 socket 的注意事项

SSL 套接字还具有以下其他方法和属性:

  • SSLSocket. do_handshake ( )
    • 执行 SSL 设置握手。

在版本 2.7.9 中更改:套接字的contextcheck_hostname属性为 true 时,握手方法还将执行match_hostname()

  • SSLSocket. getpeercert(* binary_form = False *)
    • 如果连接另一端没有对等方的证书,则返回None。如果尚未完成 SSL 握手,请引发ValueError

如果binary_form参数是False,并且从对等方接收到证书,则此方法返回dict实例。如果证书未pass验证,则字典为空。如果证书已pass验证,它将返回包含几个密钥的字典,其中包括subject(颁发证书的主体)和issuer(颁发证书的主体)。如果证书包含* Subject Alternative Name *extensions 的实例(请参见 RFC 3280),则词典中还将有一个subjectAltName密钥。

subjectissuer字段是 Tuples,包含在证书的数据结构中为各个字段提供的相对专有名称(RDN)的序列,每个 RDN 是一系列名称/值对。这是一个真实的示例:

{'issuer': ((('countryName', 'IL'),),
            (('organizationName', 'StartCom Ltd.'),),
            (('organizationalUnitName',
              'Secure Digital Certificate Signing'),),
            (('commonName',
              'StartCom Class 2 Primary Intermediate Server CA'),)),
 'notAfter': 'Nov 22 08:15:19 2013 GMT',
 'notBefore': 'Nov 21 03:09:52 2011 GMT',
 'serialNumber': '95F0',
 'subject': ((('description', '571208-SLe257oHY9fVQ07Z'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'Electronic Frontier Foundation, Inc.'),),
             (('commonName', '*.eff.org'),),
             (('emailAddress', '[email protected]'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

Note

要验证特定服务的证书,可以使用match_hostname()Function。

如果binary_form参数是True,并且提供了证书,则此方法以字节序列的形式返回整个证书的 DER 编码形式,如果对等方未提供证书,则返回None。对等方是否提供证书取决于 SSL 套接字的角色:

  • 对于 Client 端 SSL 套接字,无论是否需要验证,服务器将始终提供证书;

  • 对于服务器 SSL 套接字,Client 端仅在服务器请求时才提供证书;因此,如果您使用CERT_NONE(而不是CERT_OPTIONALCERT_REQUIRED),则getpeercert()将返回None

在版本 2.7.9 中进行了更改:返回的字典包含其他项,例如issuernotBefore。未完成握手时,将引发 Extral ValueError。返回的字典包含其他 X509v3 扩展项,例如crlDistributionPointscaIssuersOCSP URI。

  • SSLSocket. cipher ( )

    • 返回一个三值 Tuples,其中包含正在使用的密码的名称,定义其使用的 SSL 协议的版本以及正在使用的 Secret 位数。如果未构建连接,则返回None
  • SSLSocket. compression ( )

    • 返回用作字符串的压缩算法,如果未压缩连接,则返回None

如果更高级别的协议支持其自己的压缩机制,则可以使用OP_NO_COMPRESSION禁用 SSL 级别的压缩。

2.7.9 版中的新Function。

  • SSLSocket. get_channel_binding(* cb_type =“ tls-unique” *)
    • 获取当前连接的通道绑定数据(作为字节对象)。如果未连接或握手尚未完成,则返回None
  • cb_type *参数允许选择所需的通道绑定类型。有效的 Channels 绑定类型在CHANNEL_BINDING_TYPES列表中列出。当前仅支持由 RFC 5929定义的“ tls-unique”通道绑定。如果请求的通道绑定类型不受支持,则会引发ValueError

2.7.9 版中的新Function。

  • SSLSocket. selected_alpn_protocol ( )
    • 返回在 TLS 握手期间选择的协议。如果未调用SSLContext.set_alpn_protocols(),则另一方不支持 ALPN,如果此套接字不支持 Client 端提出的任何协议,或者尚未进行握手,则返回None

2.7.10 版中的新Function。

  • SSLSocket. selected_npn_protocol ( )
    • 返回在 TLS/SSL 握手期间选择的高级协议。如果未调用SSLContext.set_npn_protocols(),或者另一方不支持 NPN,或者握手尚未发生,则将返回None

2.7.9 版中的新Function。

  • SSLSocket. unwrap ( )

    • 执行 SSL 关闭握手,这将从基础套接字中删除 TLS 层,并返回基础套接字对象。这可用于从pass连接的加密操作到未加密。返回的套接字应始终用于与连接的另一端进行进一步的通信,而不是原始套接字。
  • SSLSocket. version ( )

    • 以字符串形式返回由连接协商的实际 SSL 协议版本,或者None未构建安全连接。在撰写本文时,可能的返回值包括"SSLv2""SSLv3""TLSv1""TLSv1.1""TLSv1.2"。最新的 OpenSSL 版本可能会定义更多的返回值。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

17.3.3. SSL 上下文

2.7.9 版中的新Function。

SSL 上下文包含比单个 SSL 连接更长寿的各种数据,例如 SSL 配置选项,证书和私钥。它还可以为服务器端套接字 ManagementSSL 会话的缓存,以加快来自同一 Client 端的重复连接。

  • 类别 ssl. SSLContext(协议)
    • 创建一个新的 SSL 上下文。您必须传递* protocol *,它必须是此模块中定义的PROTOCOL_*常量之一。当前建议使用PROTOCOL_SSLv23以实现最大的互操作性。

See also

create_default_context()使ssl模块为给定目的选择安全设置。

在 2.7.16 版中更改:使用安全的默认值创建上下文。默认设置选项OP_NO_COMPRESSIONOP_CIPHER_SERVER_PREFERENCEOP_SINGLE_DH_USEOP_SINGLE_ECDH_USEOP_NO_SSLv2(PROTOCOL_SSLv2除外)和OP_NO_SSLv3(PROTOCOL_SSLv3除外)。初始密码套件列表仅包含HIGH个密码,没有NULL个密码,也没有MD5个密码(PROTOCOL_SSLv2除外)。

SSLContext对象具有以下方法和属性:

  • SSLContext. cert_store_stats ( )
    • 获取有关已加载的 X.509 证书的数量,标记为 CA 证书的 X.509 证书的数量以及字典的证书吊销列表的统计信息。

具有一个 CA 证书和另一个证书的上下文示例:

>>> context.cert_store_stats()
{'crl': 0, 'x509_ca': 1, 'x509': 2}
  • SSLContext. load_cert_chain(* certfile keyfile = None password = None *)
    • 加载私钥和相应的证书。 * certfile *字符串必须是包含证书以及构建证书真实性所需的任意数量的 CA 证书的 PEM 格式的单个文件的路径。 * keyfile 字符串(如果存在)必须指向包含私钥的文件。否则,私钥也将从 certfile 获取。有关如何将证书存储在 certfile *中的更多信息,请参见Certificates的讨论。
  • password 参数可以是一个函数,用于获取用于解密私钥的密码。仅当私钥已加密并且需要密码时,才会调用该函数。它将不带任何参数地被调用,并且它应该返回一个字符串,字节或字节数组。如果返回值为字符串,则在使用它解密密钥之前,它将被编码为 UTF-8.另外,字符串,字节或字节数组值可以直接作为 password *参数提供。如果私钥未加密并且不需要密码,它将被忽略。

如果未指定* password *参数并且需要密码,则将使用 OpenSSL 的内置密码提示机制以交互方式提示用户 Importing 密码。

如果私钥与证书不匹配,则会引发SSLError

  • SSLContext. load_default_certs(* purpose = Purpose.SERVER_AUTH *)
    • 从默认位置加载一组默认的“证书颁发机构”(CA)证书。在 Windows 上,它将从CAROOT系统存储中加载 CA 证书。在其他系统上,它称为SSLContext.set_default_verify_paths()。将来,该方法也可能会从其他位置加载 CA 证书。
  • purpose *标志指定加载哪种 CA 证书。默认设置Purpose.SERVER_AUTH加载为 TLS Web 服务器身份验证(Client 端套接字)标记并受信任的证书。 Purpose.CLIENT_AUTH在服务器端加载 CA 证书以进行 Client 端证书验证。
  • SSLContext. load_verify_locations(* cafile = None capath = None cadata = None *)
    • verify_mode不是CERT_NONE时,加载一组用于验证其他对等方证书的“证书颁发机构”(CA)证书。必须指定* cafile capath *至少之一。

此方法还可以加载 PEM 或 DER 格式的证书吊销列表(CRL)。为了使用 CRL,必须正确配置SSLContext.verify_flags

  • cafile *字符串(如果存在)是 PEM 格式的级联 CA 证书文件的路径。有关如何在此文件中排列证书的更多信息,请参见Certificates的讨论。

  • capath *字符串(如果存在)是指向目录的路径,该目录包含多个OpenSSL 特定的布局之后的 PEM 格式的 CA 证书。

  • cadata 对象(如果存在)是一个或多个 PEM 编码证书的 ASCII 字符串,或者是 DER 编码证书的类似字节的对象。与 capath *一样,PEM 编码证书周围的多余行将被忽略,但至少必须存在一个证书。

  • SSLContext. get_ca_certs(* binary_form = False *)
    • 获取已加载的“证书颁发机构”(CA)证书的列表。如果binary_form参数是False,则每个列表条目都是 dict,就像SSLSocket.getpeercert()的输出一样。否则,该方法将返回 DER 编码证书的列表。除非 SSL 连接请求并加载了证书,否则返回的列表不包含来自* capath *的证书。

Note

除非至少使用过一次,否则不会加载 capath 目录中的证书。

  • SSLContext. set_default_verify_paths ( )

    • 从构建 OpenSSL 库时定义的文件系统路径中加载一组默认的“证书颁发机构”(CA)证书。不幸的是,没有简单的方法可以知道该方法是否成功:如果找不到证书,则不会返回错误。但是,当 OpenSSL 库作为 os 的一部分提供时,它可能已正确配置。
  • SSLContext. set_ciphers(密码)

    • 设置使用此上下文创建的套接字的可用密码。它应该是OpenSSL 密码列表格式中的字符串。如果无法选择密码(因为编译时选项或其他配置禁止使用所有指定的密码),则将引发SSLError

Note

连接后,SSL 套接字的SSLSocket.cipher()方法将提供当前选择的密码。

默认情况下,OpenSSL 1.1.1 启用了 TLS 1.3 密码套件。无法使用set_ciphers()禁用套件。

  • SSLContext. set_alpn_protocols(协议)
    • 指定套接字在 SSL/TLS 握手期间应通告的协议。它应该是按喜好排序的 ASCII 字符串列表,例如['http/1.1', 'spdy/2']。协议的选择将在握手期间进行,并根据 RFC 7301进行播放。握手成功后,SSLSocket.selected_alpn_protocol()方法将返回约定的协议。

如果HAS_ALPN为 False,则此方法将引发NotImplementedError

当双方都支持 ALPN 但无法在协议上达成共识时,OpenSSL 1.1.0 到 1.1.0e 将中止握手并引发SSLError。 1.1.0f 的行为类似于 1.0.2,SSLSocket.selected_alpn_protocol()返回 None。

2.7.10 版中的新Function。

  • SSLContext. set_npn_protocols(协议)
    • 指定套接字在 SSL/TLS 握手期间应通告的协议。它应该是按喜好排序的字符串列表,例如['http/1.1', 'spdy/2']。协议的选择将在握手期间进行,并根据NPN 规范草案进行播放。握手成功后,SSLSocket.selected_npn_protocol()方法将返回约定的协议。

如果HAS_NPN为 False,则此方法将引发NotImplementedError

  • SSLContext. set_servername_callback(* server_name_callback *)
    • 注册一个回调函数,当 TLSClient 端指定服务器名称指示时,SSL/TLS 服务器接收到 TLSClient 端 Hello 握手消息后将调用该回调函数。服务器名称指示机制在 RFC 6066第 3 部分-服务器名称指示中指定。

每个SSLContext只能设置一个回调。如果* server_name_callback *为None,则禁用回调。随后调用此函数将禁用以前注册的回调。

回调函数* server_name_callback *,将使用三个参数来调用;第一个是ssl.SSLSocket,第二个是表示 Client 端打算通信的服务器名称的字符串(如果 TLSClient 端 Hello 不包含服务器名称,则为None),第三个参数是原始的SSLContext。服务器名称参数是 IDNA 解码的服务器名称。

此回调的典型用法是将ssl.SSLSocketSSLSocket.context属性更改为类型SSLContext的新对象,该对象表示与服务器名称匹配的证书链。

由于 TLS 连接处于早期协商阶段,因此只能使用有限的方法和属性,例如SSLSocket.selected_alpn_protocol()SSLSocket.contextSSLSocket.getpeercert()SSLSocket.getpeercert()SSLSocket.cipher()SSLSocket.compress()方法要求 TLS 连接已超出 TLSClient 端 Hello 的范围,因此将不包含返回有意义的值,也无法对其进行安全调用。

如果服务器名称上存在 IDNA 解码错误,则 TLS 连接将以向 Client 端发送ALERT_DESCRIPTION_INTERNAL_ERROR致命 TLS 警报消息的方式终止。

如果* server_name_callback *函数引发异常,则 TLS 连接将以致命的 TLS 警报消息ALERT_DESCRIPTION_HANDSHAKE_FAILURE终止。

如果 OpenSSL 库在构建时定义了 OPENSSL_NO_TLSEXT,则此方法将引发NotImplementedError

  • SSLContext. load_dh_params(* dhfile *)
    • 加载用于 Diffie-Helman(DH)密钥交换的密钥生成参数。使用 DH 密钥交换可以提高前向保密性,但会浪费计算资源(包括服务器和 Client 端上的计算资源)。 * dhfile *参数应该是包含 PEM 格式的 DH 参数的文件的路径。

此设置不适用于 Client 端套接字。您也可以使用OP_SINGLE_DH_USE选项进一步提高安全性。

  • SSLContext. set_ecdh_curve(曲线名称)
    • 设置基于椭圆曲线的 Diffie-Hellman(ECDH)密钥交换的曲线名称。 ECDH 明显快于常规 DH,同时可以说是安全的。 * curve_name *参数应该是描述众所周知的椭圆曲线的字符串,例如prime256v1表示广泛支持的曲线。

此设置不适用于 Client 端套接字。您也可以使用OP_SINGLE_ECDH_USE选项进一步提高安全性。

如果HAS_ECDHFalse,则此方法不可用。

See also

  • SSLContext. wrap_socket(袜子,* server_side = False do_handshake_on_connect = True suppress_ragged_eofs = True server_hostname = None *)
    • 包装现有的 Python 套接字* sock *并返回SSLSocket对象。 * sock *必须是SOCK_STREAM套接字;其他套接字类型不受支持。

返回的 SSL 套接字与上下文,其设置和证书相关联。参数* server_side do_handshake_on_connect suppress_ragged_eofs *具有与顶层wrap_socket()函数相同的含义。

在 Client 端连接上,可选参数* server_hostname 指定我们要连接到的服务的主机名。这使得单个服务器可以使用不同的证书托管多个基于 SSL 的服务,这与 HTTP 虚拟主机非常相似。如果 server_side 为 true,则指定 server_hostname *将引发ValueError

在版本 2.7.9 中更改:即使 OpenSSL 没有 SNI,也始终允许传递 server_hostname。

  • SSLContext. session_stats ( )
    • 获取有关此上下文创建或 Management 的 SSL 会话的统计信息。返回一个字典,该字典将每个一点信息的名称 Map 为其数值。例如,以下是自创建上下文以来会话缓存中的命中总数和未命中总数:
>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)

Example:

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_default_certs()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(s, server_hostname='www.verisign.com')
ssl_sock.connect(('www.verisign.com', 443))

Note

此Function需要 OpenSSL 0.9.8f 或更高版本。

  • SSLContext. options
    • 一个整数,表示在此上下文中启用的 SSL 选项集。默认值为OP_ALL,但是您可以pass对它们进行“或”运算来指定其他选项,例如OP_NO_SSLv2

Note

对于低于 0.9.8m 的 OpenSSL 版本,只能设置选项,而不能清除它们。try清除选项(pass重置相应的位)将引发ValueError

  • SSLContext. protocol

    • 构造上下文时选择的协议版本。此属性是只读的。
  • SSLContext. verify_flags

    • 证书验证操作的标志。您可以pass对它们进行“或”操作来设置VERIFY_CRL_CHECK_LEAF之类的标志。默认情况下,OpenSSL 既不需要也不验证证书吊销列表(CRL)。仅适用于 openssl 0.9.8 版。
  • SSLContext. verify_mode

17.3.4. Certificates

证书通常是公钥/私钥系统的一部分。在这个系统中,每个* principal (可能是一台机器,一个人或一个组织)都分配有一个唯一的两部分式加密密钥。密钥的一部分是公共的,称为“公共密钥”;另一部分被保密,称为私钥*。这两个部分是相关的,如果您用其中一个部分对消息进行加密,则可以使用另一部分对消息进行解密,而使用另一部分来“仅**”进行解密。

证书包含有关两个主体的信息。它包含* subject 的名称和主题的公钥。它还包含第二个负责人 issuer *的语句,主题是他们声称的身份,而这确实是主题的公钥。发行人的语句使用发行人的私钥签名,只有发行人才知道。但是,任何人都可以pass找到发行者的公钥,解密该语句并将其与证书中的其他信息进行比较来验证发行者的语句。该证书还包含有关其有效期限的信息。这表示为两个字段,称为“ notBefore”和“ notAfter”。

在使用证书的 Python 中,Client 端或服务器可以使用证书来证明其身份。还可能需要网络连接的另一端来生成证书,并且可以对证书进行验证,以使需要这种验证的 Client 端或服务器满意。如果验证失败,可以将连接try设置为引发异常。验证由底层的 OpenSSL 框架自动完成;该应用程序无需关注其机制。但是应用程序通常确实需要提供证书集以允许此过程发生。

Python 使用文件包含证书。它们的格式应为“ PEM”(请参见 RFC 1422),这是一种以 64 位编码的形式,其中包含标题行和页脚行:

-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

17.3.4.1. 证书链

包含证书的 Python 文件可以包含一系列证书,有时也称为证书链。该链应从“是”Client 端或服务器的委托人的特定证书开始,然后是该证书的颁发者的证书,然后是“那个”证书的颁发者的证书,依此类推。直到您获得自签名的证书,即具有相同主题和颁发者的证书,有时也称为* root 证书*。证书应仅在证书文件中串联在一起。例如,假设我们具有三个证书链,从我们的服务器证书到签署我们的服务器证书的证书颁发机构的证书,再到颁发证书颁发机构的证书的代理机构的根证书:

-----BEGIN CERTIFICATE-----
... (certificate for your server)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the certificate for the CA)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the root certificate for the CA's issuer)...
-----END CERTIFICATE-----

17.3.4.2. CA 证书

如果您需要验证连接证书的另一端,则需要提供一个“ CA certs”文件,其中包含您愿意信任的每个颁发者的证书链。同样,此文件仅包含这些串联在一起的链。为了进行验证,Python 将使用它在文件中找到的匹配的第一条链。可以pass调用SSLContext.load_default_certs()使用平台的证书文件,该操作passcreate_default_context()自动完成。

17.3.4.3. 组合密钥和证书

通常,私钥与证书存储在同一文件中。在这种情况下,仅需要传递SSLContext.load_cert_chain()wrap_socket()certfile参数。如果私钥与证书一起存储,则它应位于证书链中的第一个证书之前:

-----BEGIN RSA PRIVATE KEY-----
... (private key in base64 encoding) ...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

17.3.4.4. 自签名证书

如果要创建提供 SSL 加密连接服务的服务器,则需要获取该服务的证书。获取适当证书的方法有很多,例如从证书颁发机构购买证书。另一种常见的做法是生成自签名证书。最简单的方法是使用 OpenSSL 软件包,使用如下所示的内容:

% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
.......++++++
.............................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:MyState
Locality Name (eg, city) []:Some City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc.
Organizational Unit Name (eg, section) []:My Group
Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com
Email Address []:[email protected]
%

自签名证书的缺点是它是自己的根证书,没有其他人将其保存在已知(和受信任)根证书的缓存中。

17.3.5. Examples

17.3.5.1. 测试 SSL 支持

要测试 Python 安装中是否存在 SSL 支持,用户代码应使用以下习惯用法:

try:
    import ssl
except ImportError:
    pass
else:
    ...  # do something that requires SSL support

17.3.5.2. Client 端操作

本示例使用建议的 Client 端套接字安全设置(包括自动证书验证)创建 SSL 上下文:

>>> context = ssl.create_default_context()

如果您希望自己调整安全设置,则可以从头开始创建上下文(但是请注意,您可能无法正确获得设置):

>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS)
>>> context.verify_mode = ssl.CERT_REQUIRED
>>> context.check_hostname = True
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")

(此代码段假定您的 os 将所有 CA 证书 Binding 在/etc/ssl/certs/ca-bundle.crt中;否则,您将得到一个错误,必须调整位置)

当您使用上下文连接到服务器时,CERT_REQUIRED验证服务器证书:它确保使用 CA 证书之Pair服务器证书进行签名,并检查签名的正确性:

>>> conn = context.wrap_socket(socket.socket(socket.AF_INET),
...                            server_hostname="www.python.org")
>>> conn.connect(("www.python.org", 443))

然后,您可以获取证书:

>>> cert = conn.getpeercert()

外观检查显示证书确实标识了所需的服务(即 HTTPS 主机www.python.org):

>>> pprint.pprint(cert)
{'OCSP': ('http://ocsp.digicert.com',),
 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt',),
 'crlDistributionPoints': ('http://crl3.digicert.com/sha2-ev-server-g1.crl',
                           'http://crl4.digicert.com/sha2-ev-server-g1.crl'),
 'issuer': ((('countryName', 'US'),),
            (('organizationName', 'DigiCert Inc'),),
            (('organizationalUnitName', 'www.digicert.com'),),
            (('commonName', 'DigiCert SHA2 Extended Validation Server CA'),)),
 'notAfter': 'Sep  9 12:00:00 2016 GMT',
 'notBefore': 'Sep  5 00:00:00 2014 GMT',
 'serialNumber': '01BB6F00122B177F36CAB49CEA8B6B26',
 'subject': ((('businessCategory', 'Private Organization'),),
             (('1.3.6.1.4.1.311.60.2.1.3', 'US'),),
             (('1.3.6.1.4.1.311.60.2.1.2', 'Delaware'),),
             (('serialNumber', '3359300'),),
             (('streetAddress', '16 Allen Rd'),),
             (('postalCode', '03894-4801'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'NH'),),
             (('localityName', 'Wolfeboro,'),),
             (('organizationName', 'Python Software Foundation'),),
             (('commonName', 'www.python.org'),)),
 'subjectAltName': (('DNS', 'www.python.org'),
                    ('DNS', 'python.org'),
                    ('DNS', 'pypi.org'),
                    ('DNS', 'docs.python.org'),
                    ('DNS', 'testpypi.python.org'),
                    ('DNS', 'bugs.python.org'),
                    ('DNS', 'wiki.python.org'),
                    ('DNS', 'hg.python.org'),
                    ('DNS', 'mail.python.org'),
                    ('DNS', 'packaging.python.org'),
                    ('DNS', 'pythonhosted.org'),
                    ('DNS', 'www.pythonhosted.org'),
                    ('DNS', 'test.pythonhosted.org'),
                    ('DNS', 'us.pycon.org'),
                    ('DNS', 'id.python.org')),
 'version': 3}

现在已构建 SSL 通道并验证了证书,您可以 continue 与服务器对话:

>>> conn.sendall(b"HEAD / HTTP/1.0\r\nHost: linuxfr.org\r\n\r\n")
>>> pprint.pprint(conn.recv(1024).split(b"\r\n"))
[b'HTTP/1.1 200 OK',
 b'Date: Sat, 18 Oct 2014 18:27:20 GMT',
 b'Server: nginx',
 b'Content-Type: text/html; charset=utf-8',
 b'X-Frame-Options: SAMEORIGIN',
 b'Content-Length: 45679',
 b'Accept-Ranges: bytes',
 b'Via: 1.1 varnish',
 b'Age: 2188',
 b'X-Served-By: cache-lcy1134-LCY',
 b'X-Cache: HIT',
 b'X-Cache-Hits: 11',
 b'Vary: Cookie',
 b'Strict-Transport-Security: max-age=63072000; includeSubDomains',
 b'Connection: close',
 b'',
 b'']

请参阅下面对Security considerations的讨论。

17.3.5.3. 服务器端操作

对于服务器操作,通常您需要在文件中具有服务器证书和私钥。首先,您将创建一个包含密钥和证书的上下文,以便 Client 端可以检查您的真实性。然后,您将打开一个套接字,将其绑定到端口,在其上调用listen(),然后开始 awaitClient 端连接:

import socket, ssl

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="mycertfile", keyfile="mykeyfile")

bindsocket = socket.socket()
bindsocket.bind(('myaddr.mydomain.com', 10023))
bindsocket.listen(5)

Client 端连接后,您将在套接字上调用accept()来从另一端获取新的套接字,并使用上下文的SSLContext.wrap_socket()方法为该连接创建服务器端 SSL 套接字:

while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()

然后,您将从connstream中读取数据并对其进行处理,直到完成 Client 端操作(或 Client 端操作完成)为止:

def deal_with_client(connstream):
    data = connstream.read()
    # null data means the client is finished with us
    while data:
        if not do_something(connstream, data):
            # we'll assume do_something returns False
            # when we're finished with client
            break
        data = connstream.read()
    # finished with client

然后返回侦听新的 Client 端连接(当然,true 的服务器可能会在单独的线程中处理每个 Client 端连接,或者将套接字置于非阻塞模式并使用事件循环)。

17.3.6. 关于非阻塞套接字的注意事项

使用非阻塞套接字时,需要注意以下几点:

  • 调用select()可以告诉您可以从 OS 套接字读取(或写入),但这并不意味着在 SSL 上层有足够的数据。例如,可能只有一部分 SSL 帧到达。因此,您必须准备处理SSLSocket.recv()SSLSocket.send()故障,并在再次调用select()之后重试。

  • 相反,由于 SSL 层具有其自己的框架,因此 SSL 套接字可能仍具有可供读取的数据,而无需select()意识到。因此,您应该首先调用SSLSocket.recv()以清空所有潜在的可用数据,然后仅在必要时才阻塞select()调用。

(当然,在使用其他 Primitives(例如poll()selectors模块中的 Primitives)时,也适用类似的规定)

  • SSL 握手本身将是非阻塞的:必须重试SSLSocket.do_handshake()方法,直到它成功返回。这是使用select()await 套接字准备就绪的摘要:
while True:
    try:
        sock.do_handshake()
        break
    except ssl.SSLWantReadError:
        select.select([sock], [], [])
    except ssl.SSLWantWriteError:
        select.select([], [sock], [])

17.3.7. 安全注意事项

17.3.7.1. 最佳默认值

对于 Client 端使用 ,如果您对安全策略没有任何特殊要求,则强烈建议您使用create_default_context()函数来创建 SSL 上下文。它将加载系统的受信任 CA 证书,启用证书验证和主机名检查,并try选择合理的安全协议和密码设置。

如果连接需要 Client 端证书,则可以使用SSLContext.load_cert_chain()添加。

相反,如果您pass自己调用SSLContext构造函数来创建 SSL 上下文,则默认情况下不会启用证书验证或主机名检查。如果这样做,请阅读以下段落以达到良好的安全级别。

17.3.7.2. 手动设定

17.3.7.2.1. 验证证书

直接调用SSLContext构造函数时,默认为CERT_NONE。由于它不对另一个对等方进行身份验证,因此它可能是不安全的,尤其是在 Client 端模式下,在大多数情况下,您希望确保与之对话的服务器的真实性。因此,在 Client 端模式下,强烈建议使用CERT_REQUIRED。但是,这本身是不够的。您还必须检查可以pass调用SSLSocket.getpeercert()获得的服务器证书是否与所需服务匹配。对于许多协议和应用程序,可以pass主机名来标识服务;在这种情况下,可以使用match_hostname()Function。启用SSLContext.check_hostname时,将自动执行此通用检查。

在服务器模式下,如果要使用 SSL 层(而不是使用更高级别的身份验证机制)对 Client 端进行身份验证,则还必须指定CERT_REQUIRED并类似地检查 Client 端证书。

Note

Note

在 Client 端模式下,除非启用了匿名密码(默认情况下它们被禁用),否则CERT_OPTIONALCERT_REQUIRED是等效的。

17.3.7.2.2. 协议版本

SSL 版本 2 和 3 被认为是不安全的,因此有使用危险。如果要在 Client 端和服务器之间实现最大兼容性,建议使用PROTOCOL_SSLv23作为协议版本,然后使用SSLContext.options属性显式禁用 SSLv2 和 SSLv3:

context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3

上面创建的 SSL 上下文仅允许 TLSv1 和更高版本(如果系统支持)连接。

17.3.7.2.3. 密码选择

如果您有高级安全性要求,则可以passSSLContext.set_ciphers()方法对在 SSL 会话进行协商时启用的密码进行微调。从 Python 2.7.9 开始,ssl 模块默认情况下禁用某些弱密码,但是您可能希望进一步限制密码选择。确保阅读有关密码列表格式的 OpenSSL 文档。如果要检查给定密码列表启用了哪些密码,请在系统上使用openssl ciphers命令。

17.3.7.3. Multi-processing

如果将此模块用作多进程应用程序的一部分(例如使用multiprocessingconcurrent.futures模块),请注意 OpenSSL 的内部随机数生成器无法正确处理派生的进程。如果应用程序对os.fork()使用任何 SSL Function,则它们必须更改父进程的 PRNG 状态。 RAND_add()RAND_bytes()RAND_pseudo_bytes()的任何成功调用就足够了。

17.3.8. LibreSSL 支持

LibreSSL 是 OpenSSL 1.0.1 的分支。 ssl 模块对 LibreSSL 的支持有限。使用 LibreSSL 编译 ssl 模块时,某些Function不可用。