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

来自菜鸟教程
Python/docs/2.7/library/ssl
跳转至:导航、​搜索

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

2.6 版中的新功能。


源代码: :source:`Lib/ssl.py`



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

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


笔记

某些行为可能取决于平台,因为调用的是操作系统套接字 API。 已安装的 OpenSSL 版本也可能导致行为发生变化。 例如,TLSv1.1 和 TLSv1.2 带有 openssl 版本 1.0.1。


警告

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


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

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

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

17.3.1. 函数、常量和异常

exception ssl.SSLError

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

library

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

2.7.9 版中的新功能。

reason

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

2.7.9 版中的新功能。

exception ssl.SSLZeroReturnError

尝试读取或写入时引发 SSLError 的子类并且 SSL 连接已完全关闭。 请注意,这并不意味着底层传输(读取 TCP)已关闭。

2.7.9 版中的新功能。

exception ssl.SSLWantReadError

SSLError 的子类在尝试读取或写入数据时由 非阻塞 SSL 套接字 引发,但需要在底层 TCP 传输上接收更多数据才能满足请求.

2.7.9 版中的新功能。

exception ssl.SSLWantWriteError

SSLError 的子类在尝试读取或写入数据时由 非阻塞 SSL 套接字 引发,但需要在底层 TCP 传输上发送更多数据才能满足请求.

2.7.9 版中的新功能。

exception ssl.SSLSyscallError

SSLError 的子类在尝试对 SSL 套接字执行操作时遇到系统错误时引发。 不幸的是,没有简单的方法可以检查原始 errno 编号。

2.7.9 版中的新功能。

exception ssl.SSLEOFError

当 SSL 连接突然终止时,会引发 SSLError 的子类。 通常,您不应在遇到此错误时尝试重用底层传输。

2.7.9 版中的新功能。

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

17.3.1.1. 套接字创建

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

ssl.wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)

获取socket.socket的一个实例sock,并返回一个ssl.SSLSocket的实例,一个socket.socket的子类型,它包装了底层的socket在 SSL 上下文中。 sock 必须是 SOCK_STREAM 套接字; 不支持其他套接字类型。

对于客户端套接字,上下文构造是惰性的; 如果底层套接字尚未连接,则在套接字上调用 connect() 后将执行上下文构造。 对于服务器端套接字,如果该套接字没有远程对等方,则假定它是一个侦听套接字,并且服务器端 SSL 包装会在通过 accept() 方法接受的客户端连接上自动执行。 wrap_socket() 可能会引发 SSLError

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

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

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

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

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

这是一个表格,显示客户端中的哪些版本(侧面)可以连接到服务器中的哪些版本(顶部):

客户端 / 服务器

SSLv2

SSLv3

SSLv23

TLSv1

TLSv1.1

TLSv1.2

SSLv2

是的

没有

是的

没有

没有

没有

SSLv3

没有

是的

是的

没有

没有

没有

SSLv23 1

没有

是的

是的

是的

是的

是的

TLSv1

没有

没有

是的

是的

没有

没有

TLSv1.1

没有

没有

是的

没有

是的

没有

TLSv1.2

没有

没有

是的

没有

没有

是的


脚注

1

TLS 1.3 协议将在 OpenSSL >= 1.1.1 中与 PROTOCOL_SSLv23 一起使用。 仅 TLS 1.3 没有专用的 PROTOCOL 常量。

笔记

哪些连接成功取决于 OpenSSL 的版本。 例如,在 OpenSSL 1.0.0 之前,SSLv23 客户端将始终尝试 SSLv2 连接。

ciphers 参数设置此 SSL 对象的可用密码。 它应该是 OpenSSL 密码列表格式 中的字符串。

参数do_handshake_on_connect指定在做了socket.connect()后是否自动进行SSL握手,或者应用程序是否会通过调用SSLSocket.do_handshake()显式调用它方法。 显式调用 SSLSocket.do_handshake() 使程序可以控制握手中涉及的套接字 I/O 的阻塞行为。

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

2.7 版更改: 新的可选参数 ciphers


17.3.1.2. 上下文创建

一个方便的函数有助于创建 SSLContext 对象用于通用目的。

ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

返回一个新的 SSLContext 对象,其中包含给定 用途 的默认设置。 这些设置由 ssl 模块选择,通常比直接调用 SSLContext 构造函数时代表更高的安全级别。

cafilecapathcadata 代表可选的 CA 证书以信任证书验证,如 SSLContext.load_verify_locations()。 如果三个都是None,这个函数可以选择信任系统默认的CA证书。

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

笔记

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

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

笔记

如果您发现当某些较旧的客户端或服务器尝试连接由该函数创建的 SSLContext 时,它们会收到错误消息,指出“协议或密码套件不匹配”,则可能是它们仅支持 SSL3.0此函数不包括使用 OP_NO_SSLv3。 SSL3.0 被广泛认为是 完全破解 。 如果您仍希望继续使用此功能但仍允许 SSL 3.0 连接,您可以使用以下方法重新启用它们:

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

2.7.9 版中的新功能。

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

2.7.13 版变更:ChaCha20/Poly1305 加入默认密码字符串。

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

ssl._https_verify_certificates(enable=True)

指定在不指定特定 SSL 上下文的情况下创建客户端 HTTPS 连接时是否验证服务器证书。

从 Python 2.7.9 开始,httplib 和使用它的模块,例如 urllib2xmlrpclib,默认验证建立客户端 HTTPS 连接时收到的远程服务器证书. 此默认验证检查证书是否由系统信任存储中的证书颁发机构签名,以及所提供证书上的通用名称(或主题备用名称)是否与请求的主机匹配。

enable 设置为 True 可确保此默认行为生效。

enable 设置为 False 会将默认的 HTTPS 证书处理恢复为 Python 2.7.8 及更早版本,允许使用自签名证书连接到服务器,使用由证书颁发机构签名的证书的服务器不存在于系统信任库中,以及主机名与提供的服务器证书不匹配的服务器。

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

2.7.12 版中的新功能。

也可以看看

  • CVE-2014-9365 – 使用默认设置对 Python 客户端进行 HTTPS 中间人攻击

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

  • PEP 493 – Python 2.7 HTTPS 验证迁移工具



17.3.1.3. 随机生成

自 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伪随机数生成器,以增加生成的密钥的安全性。 这通常只在没有更好随机源的系统上才有必要。

有关熵收集守护进程的来源,请参阅 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() 返回的解码格式)与给定的 主机名 匹配。 应用的规则是 RFC 2818RFC 6125 中概述的用于检查 HTTPS 服务器身份的规则,除了 IP 地址是目前不支持。 除HTTPS外,此功能应适用于在各种基于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 版中的新功能。

ssl.cert_time_to_seconds(cert_time)

返回自纪元以来的时间(以秒为单位),给定 cert_time 字符串,表示 "%b %d %H:%M:%S %Y %Z" strptime 格式(C 语言环境)中证书的“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”日期必须使用 GMT (RFC 5280)。

在 2.7.9 版中更改: 将输入时间解释为 UTC 中的时间,如输入字符串中的“GMT”时区所指定。 以前使用本地时区。 返回一个整数(输入格式中没有秒的小数部分)

ssl.get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None)

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

2.7.9 版本变更:此功能现在兼容 IPv6,默认 ssl_versionPROTOCOL_SSLv3 改为 PROTOCOL_SSLv23[X1]与现代服务器的最大兼容性。

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()

返回一个命名元组,其中包含 OpenSSL 的默认 cafile 和 capath 的路径。 路径与 SSLContext.set_default_verify_paths() 使用的路径相同。 返回值是一个名为 的元组 DefaultVerifyPaths

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

  • capath - 如果目录不存在,则解析到 capath 或 None 的路径,

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

  • openssl_cafile - cafile 的硬编码路径,

  • openssl_capath_env - OpenSSL 的环境密钥,指向一个 capath,

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

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

2.7.9 版中的新功能。

ssl.enum_certificates(store_name)

从 Windows 的系统证书存储中检索证书。 store_name 可以是 CAROOTMY 之一。 Windows 也可能提供额外的证书存储。

该函数返回一个 (cert_bytes, encoding_type, trust) 元组列表。 encoding_type 指定了 cert_bytes 的编码。 X.509 ASN.1 数据为 x509_asn,PKCS#7 ASN.1 数据为 pkcs_7_asn。 Trust 将证书的用途指定为一组 OIDS 或完全 True,如果证书对于所有用途都值得信赖。

例子:

>>> 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)]

可用性:Windows。

2.7.9 版中的新功能。

ssl.enum_crls(store_name)

从 Windows 的系统证书存储中检索 CRL。 store_name 可以是 CAROOTMY 之一。 Windows 也可能提供额外的证书存储。

该函数返回一个 (cert_bytes, encoding_type, trust) 元组列表。 encoding_type 指定了 cert_bytes 的编码。 X.509 ASN.1 数据为 x509_asn,PKCS#7 ASN.1 数据为 pkcs_7_asn

可用性:Windows。

2.7.9 版中的新功能。


17.3.1.5. 常数

ssl.CERT_NONE

SSLContext.verify_mode 的可能值,或 wrap_socket()cert_reqs 参数。 在这种模式下(默认),套接字连接的另一端不需要证书。 如果从另一端收到证书,则不会尝试对其进行验证。

请参阅下面对 安全注意事项 的讨论。

ssl.CERT_OPTIONAL

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

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

ssl.CERT_REQUIRED

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

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

ssl.VERIFY_DEFAULT

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

2.7.9 版中的新功能。

ssl.VERIFY_CRL_CHECK_LEAF

SSLContext.verify_flags 的可能值。 在这种模式下,只检查对等证书,而不检查中间 CA 证书。 该模式需要由对等证书的颁发者(其直接祖先 CA)签名的有效 CRL。 如果没有正确加载 SSLContext.load_verify_locations,验证将失败。

2.7.9 版中的新功能。

ssl.VERIFY_CRL_CHECK_CHAIN

SSLContext.verify_flags 的可能值。 在此模式下,检查对等证书链中所有证书的 CRL。

2.7.9 版中的新功能。

ssl.VERIFY_X509_STRICT

SSLContext.verify_flags 的可能值以禁用损坏的 X.509 证书的变通方法。

2.7.9 版中的新功能。

ssl.VERIFY_X509_TRUSTED_FIRST

SSLContext.verify_flags 的可能值。 它指示 OpenSSL 在构建信任链以验证证书时更喜欢受信任的证书。 默认情况下启用此标志。

2.7.10 版中的新功能。

ssl.PROTOCOL_TLS

选择客户端和服务器都支持的最高协议版本。 尽管名称如此,但此选项可以选择“TLS”协议以及“SSL”。

2.7.13 版中的新功能。

ssl.PROTOCOL_SSLv23

PROTOCOL_TLS 的别名。

自 2.7.13 版起已弃用: 改用 PROTOCOL_TLS

ssl.PROTOCOL_SSLv2

选择 SSL 版本 2 作为通道加密协议。

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

警告

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

自 2.7.13 版起已弃用:OpenSSL 已删除对 SSLv2 的支持。

ssl.PROTOCOL_SSLv3

选择 SSL 版本 3 作为通道加密协议。

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

警告

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 版中的新功能。

自 2.7.13 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议。

ssl.PROTOCOL_TLSv1_2

选择 TLS 1.2 版作为通道加密协议。 这是最现代的版本,如果双方都可以说的话,这可能是最大程度保护的最佳选择。 仅适用于 openssl 版本 1.0.1+。

2.7.9 版中的新功能。

自 2.7.13 版起已弃用:OpenSSL 已弃用所有特定于版本的协议。 使用带有 OP_NO_SSLv3 等标志的默认协议。

ssl.OP_ALL

为其他 SSL 实现中存在的各种错误启用变通方法。 默认情况下设置此选项。 它不一定设置与 OpenSSL 的 SSL_OP_ALL 常量相同的标志。

2.7.9 版中的新功能。

ssl.OP_NO_SSLv2

阻止 SSLv2 连接。 此选项仅适用于与 PROTOCOL_SSLv23 结合使用。 它可以防止对等方选择 SSLv2 作为协议版本。

2.7.9 版中的新功能。

ssl.OP_NO_SSLv3

阻止 SSLv3 连接。 此选项仅适用于与 PROTOCOL_SSLv23 结合使用。 它可以防止对等方选择 SSLv3 作为协议版本。

2.7.9 版中的新功能。

ssl.OP_NO_TLSv1

阻止 TLSv1 连接。 此选项仅适用于与 PROTOCOL_SSLv23 结合使用。 它可以防止对等方选择 TLSv1 作为协议版本。

2.7.9 版中的新功能。

ssl.OP_NO_TLSv1_1

阻止 TLSv1.1 连接。 此选项仅适用于与 PROTOCOL_SSLv23 结合使用。 它阻止对等方选择 TLSv1.1 作为协议版本。 仅适用于 openssl 版本 1.0.1+。

2.7.9 版中的新功能。

ssl.OP_NO_TLSv1_2

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

2.7.9 版中的新功能。

ssl.OP_NO_TLSv1_3

阻止 TLSv1.3 连接。 此选项仅适用于与 PROTOCOL_TLS 结合使用。 它阻止对等方选择 TLSv1.3 作为协议版本。 TLS 1.3 可用于 OpenSSL 1.1.1 或更高版本。 当 Python 针对旧版本的 OpenSSL 编译时,该标志默认为 0

2.7.15 版中的新功能。

ssl.OP_CIPHER_SERVER_PREFERENCE

使用服务器的密码排序首选项,而不是客户端的。 此选项对客户端套接字和 SSLv2 服务器套接字没有影响。

2.7.9 版中的新功能。

ssl.OP_SINGLE_DH_USE

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

2.7.9 版中的新功能。

ssl.OP_SINGLE_ECDH_USE

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

2.7.9 版中的新功能。

ssl.OP_ENABLE_MIDDLEBOX_COMPAT

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

此选项仅适用于 OpenSSL 1.1.1 及更高版本。

2.7.16 版中的新功能。

ssl.OP_NO_COMPRESSION

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

此选项仅适用于 OpenSSL 1.0.0 及更高版本。

2.7.9 版中的新功能。

ssl.HAS_ALPN

OpenSSL 库是否内置了对 应用层协议协商 TLS 扩展的支持,如 RFC 7301 中所述。

2.7.10 版中的新功能。

ssl.HAS_ECDH

OpenSSL 库是否内置支持基于椭圆曲线的 Diffie-Hellman 密钥交换。 除非分发者明确禁用该功能,否则这应该是正确的。

2.7.9 版中的新功能。

ssl.HAS_SNI

OpenSSL 库是否具有对 服务器名称指示 扩展的内置支持(如 RFC 4366 中所定义)。

2.7.9 版中的新功能。

ssl.HAS_NPN

OpenSSL 库是否内置了对 Next Protocol Negotiation 的支持,如 NPN 草案规范 中所述。 当为真时,您可以使用 SSLContext.set_npn_protocols() 方法来通告您想要支持的协议。

2.7.9 版中的新功能。

ssl.HAS_TLSv1_3

OpenSSL 库是否内置了对 TLS 1.3 协议的支持。

2.7.15 版中的新功能。

ssl.CHANNEL_BINDING_TYPES

支持的 TLS 通道绑定类型列表。 此列表中的字符串可用作 SSLSocket.get_channel_binding() 的参数。

2.7.9 版中的新功能。

ssl.OPENSSL_VERSION

解释器加载的 OpenSSL 库的版本字符串:

>>> ssl.OPENSSL_VERSION
'OpenSSL 0.9.8k 25 Mar 2009'

2.7 版中的新功能。

ssl.OPENSSL_VERSION_INFO

一个由五个整数组成的元组,表示有关 OpenSSL 库的版本信息:

>>> ssl.OPENSSL_VERSION_INFO
(0, 9, 8, 11, 15)

2.7 版中的新功能。

ssl.OPENSSL_VERSION_NUMBER

OpenSSL 库的原始版本号,作为单个整数:

>>> ssl.OPENSSL_VERSION_NUMBER
9470143L
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x9080bfL'

2.7 版中的新功能。

ssl.ALERT_DESCRIPTION_HANDSHAKE_FAILURE
ssl.ALERT_DESCRIPTION_INTERNAL_ERROR
ALERT_DESCRIPTION_*

来自 RFC 5246 等的警报描述。 IANA TLS Alert Registry 包含此列表和对定义其含义的 RFC 的引用。

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

2.7.9 版中的新功能。

Purpose.SERVER_AUTH

create_default_context()SSLContext.load_default_certs() 的选项。 此值指示上下文可用于验证 Web 服务器(因此,它将用于创建客户端套接字)。

2.7.9 版中的新功能。

Purpose.CLIENT_AUTH

create_default_context()SSLContext.load_default_certs() 的选项。 此值指示上下文可用于验证 Web 客户端(因此,它将用于创建服务器端套接字)。

2.7.9 版中的新功能。


17.3.2. SSL 套接字

SSL 套接字提供了 套接字对象 的以下方法:

但是,由于 SSL(和 TLS)协议在 TCP 之上有自己的框架,因此 SSL 套接字抽象在某些方面可能与正常的操作系统级套接字的规范不同。 请特别参阅有关非阻塞套接字 注释。

SSL 套接字还具有以下附加方法和属性:

SSLSocket.do_handshake()

执行 SSL 设置握手。

2.7.9版本变更:当socket的contextcheck_hostname属性为true时,握手方法也会执行match_hostname()

SSLSocket.getpeercert(binary_form=False)

如果连接另一端的 peer 没有证书,则返回 None。 如果 SSL 握手尚未完成,请提高 ValueError

如果 binary_form 参数为 False,并且从对等方收到证书,则此方法返回 dict 实例。 如果证书未经过验证,则 dict 为空。 如果证书被验证,它会返回一个带有几个键的字典,其中包括 subject(颁发证书的主体)和 issuer(颁发证书的主体)。 如果证书包含 主题备用名称 扩展的实例(请参阅 RFC 3280),则在字典。

subjectissuer 字段是包含证书数据结构中为各个字段提供的相对可分辨名称 (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', 'hostmaster@eff.org'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

笔记

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

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

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

  • 对于服务器 SSL 套接字,客户端只会在服务器请求时提供证书; 因此,如果您使用 CERT_NONE(而不是 CERT_OPTIONAL 或 CERT_24X],则 getpeercert() 将返回 None

2.7.9 版本更改: 返回的字典包含额外的项,例如 issuernotBefore。 另外,握手未完成时,ValueError 会升高。 返回的字典包括额外的 X509v3 扩展项,例如 crlDistributionPointscaIssuersOCSP URI。

SSLSocket.cipher()
返回一个三值元组,其中包含正在使用的密码的名称、定义其使用的 SSL 协议的版本以及正在使用的秘密位数。 如果没有建立连接,则返回 None
SSLSocket.compression()

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

如果上层协议支持自己的压缩机制,可以使用 OP_NO_COMPRESSION 来禁用 SSL 级压缩。

2.7.9 版中的新功能。

SSLSocket.get_channel_binding(cb_type='tls-unique')

获取当前连接的通道绑定数据,作为字节对象。 如果未连接或握手尚未完成,则返回 None

cb_type 参数允许选择所需的通道绑定类型。 CHANNEL_BINDING_TYPES 列表中列出了有效的频道绑定类型。 目前仅支持由 RFC 5929 定义的“tls-unique”通道绑定。 ValueError 如果请求了不受支持的通道绑定类型,则将引发。

2.7.9 版中的新功能。

SSLSocket.selected_alpn_protocol()

返回在 TLS 握手期间选择的协议。 如果 SSLContext.set_alpn_protocols() 没有被调用,如果对方不支持 ALPN,如果这个 socket 不支持任何客户端提出的协议,或者如果握手还没有发生,None 返回。

2.7.10 版中的新功能。

SSLSocket.selected_npn_protocol()

返回在 TLS/SSL 握手期间选择的更高级别的协议。 如果 SSLContext.set_npn_protocols() 没有被调用,或者对方不支持 NPN,或者握手还没有发生,这将返回 None

2.7.9 版中的新功能。

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

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

2.7.9 版中的新功能。

SSLSocket.context

此 SSL 套接字绑定到的 SSLContext 对象。 如果 SSL 套接字是使用顶级 wrap_socket() 函数(而不是 SSLContext.wrap_socket())创建的,则这是为此 SSL 套接字创建的自定义上下文对象。

2.7.9 版中的新功能。


17.3.3. SSL 上下文

2.7.9 版中的新功能。


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

class ssl.SSLContext(protocol)

创建新的 SSL 上下文。 您必须传递 protocol,它必须是此模块中定义的 PROTOCOL_* 常量之一。 PROTOCOL_SSLv23 目前被推荐用于实现最大的互操作性。

也可以看看

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

在 2.7.16 版更改:使用安全默认值创建上下文。 选项 OP_NO_COMPRESSIONOP_CIPHER_SERVER_PREFERENCEOP_SINGLE_DH_USE、OP_SINGLE_ECDH_USE[X127X1X18X1200X100X100X100X100X100X1000X100X1000X1000X1000X1000X1000X1000X5 ]) 和 OP_NO_SSLv3PROTOCOL_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 字符串必须是 PEM 格式的单个文件的路径,该文件包含证书以及建立证书真实性所需的任意数量的 CA 证书。 keyfile 字符串(如果存在)必须指向包含私钥的文件。 否则,私钥也将从 certfile 中获取。 有关证书如何存储在 certfile 中的更多信息,请参阅 Certificates 的讨论。

password 参数可以是一个函数,用于获取解密私钥的密码。 只有在私钥被加密并且需要密码时才会调用它。 它将在不带参数的情况下被调用,它应该返回一个字符串、字节或字节数组。 如果返回值是一个字符串,它将在使用它解密密钥之前被编码为 UTF-8。 或者,字符串、字节或字节数组值可以直接作为 password 参数提供。 如果私钥未加密且不需要密码,它将被忽略。

如果未指定 password 参数并且需要密码,则 OpenSSL 的内置密码提示机制将用于交互式提示用户输入密码。

如果私钥与证书不匹配,则会引发 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 服务器身份验证(客户端套接字)标记和信任。 Purpose.CLIENT_AUTH加载CA证书,用于服务器端客户端证书验证。

SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None)

verify_mode 不是 CERT_NONE 时,加载一组用于验证其他对等方证书的“证书颁发机构”(CA)证书。 必须至少指定 cafilecapath 之一。

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

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

capath 字符串(如果存在)是包含 PEM 格式的多个 CA 证书的目录的路径,遵循 OpenSSL 特定布局

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

SSLContext.get_ca_certs(binary_form=False)

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

笔记

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

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

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

笔记

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

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

SSLContext.set_alpn_protocols(protocols)

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

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

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

2.7.10 版中的新功能。

SSLContext.set_npn_protocols(protocols)

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

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

SSLContext.set_servername_callback(server_name_callback)

注册一个回调函数,当 TLS 客户端指定服务器名称指示时,SSL/TLS 服务器收到 TLS 客户端 Hello 握手消息后将调用该回调函数。 服务器名称指示机制在 RFC 6066 第 3 节 - 服务器名称指示中指定。

每个SSLContext只能设置一个回调。 如果 server_name_callbackNone,则回调被禁用。 后续调用此函数将禁用先前注册的回调。

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

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

由于 TLS 连接的早期协商阶段,只有有限的方法和属性可用,如 SSLSocket.selected_alpn_protocol()SSLSocket.contextSSLSocket.getpeercert(), SSLSocket.getpeercert(), SSLSocket.cipher()SSLSocket.compress() 方法要求 TLS 连接已取得进展超出 TLS 客户端 Hello,因此不会包含返回有意义的值,也不能安全地调用它们。

server_name_callback 函数必须返回 None 以允许 TLS 协商继续。 如果需要 TLS 失败,则可以返回常量 ALERT_DESCRIPTION_*。 其他返回值将导致 TLS 致命错误 ALERT_DESCRIPTION_INTERNAL_ERROR

如果服务器名称存在 IDNA 解码错误,TLS 连接将终止,并向客户端发送 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 密钥交换以牺牲计算资源(在服务器和客户端上)为代价提高了前向保密性。 dhfile 参数应该是包含 PEM 格式的 DH 参数的文件的路径。

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

SSLContext.set_ecdh_curve(curve_name)

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

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

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

也可以看看

SSL/TLS 和完美的前向保密

文森特·伯纳特。


SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None)

包装一个现有的 Python 套接字 sock 并返回一个 SSLSocket 对象。 sock 必须是 SOCK_STREAM 套接字; 不支持其他套接字类型。

返回的 SSL 套接字与上下文、其设置和证书相关联。 参数 server_sidedo_handshake_on_connectsuppress_ragged_eofs 与顶层 wrap_socket() 函数的含义相同。

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

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

SSLContext.session_stats()

获取有关由此上下文创建或管理的 SSL 会话的统计信息。 返回一个字典,它将每个 条信息 的名称映射到它们的数值。 例如,这是自上下文创建以来会话缓存中的命中和未命中总数:

>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)
SSLContext.check_hostname

是否将对等证书的主机名与 SSLSocket.do_handshake() 中的 match_hostname() 匹配。 上下文的 verify_mode 必须设置为 CERT_OPTIONALCERT_REQUIRED,并且必须将 server_hostname 传递给 Xwrap_socket3(X146X]Xwrap_socket3(X76X)为了匹配主机名。

例子:

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))

笔记

此功能需要 OpenSSL 0.9.8f 或更新版本。

SSLContext.options

一个整数,表示在此上下文中启用的 SSL 选项集。 默认值为 OP_ALL,但您可以通过将它们组合在一起来指定其他选项,例如 OP_NO_SSLv2

笔记

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

SSLContext.protocol
构建上下文时选择的协议版本。 该属性是只读的。
SSLContext.verify_flags
证书验证操作的标志。 您可以通过将它们组合在一起来设置像 VERIFY_CRL_CHECK_LEAF 这样的标志。 默认情况下,OpenSSL 既不需要也不需要验证证书吊销列表 (CRL)。 仅适用于 openssl 版本 0.9.8+。
SSLContext.verify_mode
是否尝试验证其他对等方的证书以及验证失败时的行为。 此属性必须是 CERT_NONECERT_OPTIONALCERT_REQUIRED 之一。

17.3.4. 证书

证书通常是公钥/私钥系统的一部分。 在这个系统中,每个principal(可能是一台机器、一个人或一个组织)都被分配了一个唯一的两部分加密密钥。 密钥的一部分是公开的,称为公钥; 另一部分保密,称为私钥。 这两个部分是相关的,因为如果您用其中一个部分加密消息,您可以用另一部分解密,而 与另一部分。

一个证书包含有关两个主体的信息。 它包含 主题 的名称和主题的公钥。 它还包含第二个委托人 发行人 的声明,该主体是他们声称的身份,并且这确实是该主体的公钥。 发行人的声明是用发行人的私钥签名的,只有发行人知道。 但是,任何人都可以通过找到发行者的公钥、用它解密声明并将其与证书中的其他信息进行比较来验证发行者的声明。 该证书还包含有关其有效时间段的信息。 这表示为两个字段,称为“notBefore”和“notAfter”。

在 Python 中使用证书时,客户端或服务器可以使用证书来证明他们是谁。 网络连接的另一端也可能需要生成证书,并且可以验证该证书以使需要此类验证的客户端或服务器满意。 如果验证失败,连接尝试可以设置为引发异常。 验证由底层 OpenSSL 框架自动完成; 应用程序不需要关心它的机制。 但是应用程序通常需要提供证书集来允许这个过程发生。

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

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

17.3.4.1. 证书链

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

-----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 证书”文件,其中包含您愿意信任的每个颁发者的证书链。 同样,这个文件只包含这些连接在一起的链。 为了验证,Python 将使用它在文件中找到的匹配的第一个链。 可以通过调用 SSLContext.load_default_certs() 来使用平台的证书文件,这是通过 create_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 []:ops@myserver.mygroup.myorganization.com
%

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


17.3.5. 例子

17.3.5.1. 测试 SSL 支持

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

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

17.3.5.2. 客户端操作

此示例使用客户端套接字的推荐安全设置创建 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")

(此代码段假定您的操作系统将所有 CA 证书的捆绑包放置在 /etc/ssl/certs/ca-bundle.crt 中;否则,您将收到错误并必须调整位置)

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

>>> 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 通道已建立并验证了证书,您可以继续与服务器对话:

>>> 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'']

请参阅下面对 安全注意事项 的讨论。


17.3.5.3. 服务端操作

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

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)

当客户端连接时,您将在套接字上调用 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 读取数据并对其进行处理,直到您完成对客户端的处理(或客户端对您完成处理):

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

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


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

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

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

  • 相反,由于 SSL 层有自己的框架,因此 SSL 套接字可能仍然具有可供读取的数据,而 select() 不知道它。 因此,您应该首先调用 SSLSocket.recv() 以排出任何可能可用的数据,然后仅在必要时阻止 select() 调用。

    (当然,类似的规定适用于使用其他原语如 poll()selectors 模块中的原语)

  • SSL 握手本身将是非阻塞的:必须重试 SSLSocket.do_handshake() 方法,直到它成功返回。 这是使用 select() 等待套接字准备就绪的概要:

    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. 最佳默认值

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

如果连接需要客户端证书,可以添加 SSLContext.load_cert_chain()

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


17.3.7.2. 手动设置

17.3.7.2.1。 验证证书

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

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

笔记

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


17.3.7.2.2. 协议版本

SSL 版本 2 和 3 被认为是不安全的,因此使用起来很危险。 如果您希望客户端和服务器之间具有最大的兼容性,建议使用 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。 密码选择

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


17.3.7.3. 多处理

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


17.3.8. LibreSSL 支持

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