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. 函数,常量和异常

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

17.3.1.1. 套接字创建

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

对于 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 端服务器* SSLv2 SSLv3 SSLv23 TLSv1 TLSv1.1 TLSv1.2
SSLv2 yes no yes no no no
SSLv3 no yes yes no no no
* SSLv23 * [1] no yes yes yes yes yes
TLSv1 no no yes yes no no
TLSv1.1 no no yes no yes no
TLSv1.2 no no yes no no yes

Footnotes

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对象以用于常见目的。

设置为:具有高加密密码套件的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。

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

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

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

17.3.1.4. 证书处理

失败时引发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。

这是一个例子:

>>> 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 格式中不包括秒)

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

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

2.7.9 版中的新Function。

该函数返回(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。

该函数返回(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

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

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

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

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.10 版中的新Function。

版本 2.7.13 中的新Function。

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

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

Warning

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

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

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

Warning

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

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

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

2.7.9 版中的新Function。

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

2.7.9 版中的新Function。

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

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.15 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

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

2.7.16 版中的新Function。

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

2.7.9 版中的新Function。

2.7.10 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.15 版中的新Function。

2.7.9 版中的新Function。

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

2.7 版的新Function。

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

2.7 版的新Function。

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

2.7 版的新Function。

用作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 套接字还具有以下其他方法和属性:

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

如果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', 'hostmaster@eff.org'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

Note

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

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

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

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

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.10 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

2.7.9 版中的新Function。

17.3.3. SSL 上下文

2.7.9 版中的新Function。

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

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对象具有以下方法和属性:

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

>>> context.cert_store_stats()
{'crl': 0, 'x509_ca': 1, 'x509': 2}

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

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

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

Note

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

Note

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

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

如果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。

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

每个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

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

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

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

See also

返回的 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。

>>> 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 或更高版本。

Note

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

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 []:ops@myserver.mygroup.myorganization.com
%

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

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. 关于非阻塞套接字的注意事项

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

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

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不可用。

首页