Python-开启ssl证书校验
Python- SSL文档:https://docs.python.org/dev/library/ssl.html
有啥不懂,看文档呗
一、介绍
1、主要方法介绍
SSLContext.load_cert_chain(certfile, keyfile=None, password=None): """ 加载私钥和相应的证书。证书文件字符串必须是PEM格式的单个文件的路径,其中包含证书以及建立证书真实性所需的任何数量的CA证书。 如果存在keyfile字符串,则必须指向包含中私钥的文件。否则,私钥也将从证书文件中获取。 password参数可以是一个要调用的函数,以获取用于解密私钥的密码。只有在私钥已加密并且需要密码时,才会调用它。 它将在没有参数的情况下调用,并且它应该返回字符串、字节或字节数组。如果返回值是字符串,在使用它解密密钥之前,它将被编码为UTF-8。或者,字符串、字节或字节数组值可以直接作为密码参数提供。 如果私钥未加密,且不需要密码,则将忽略此密钥。 如果未指定password参数,并且需要密码,则将使用OpenSSL内置的密码提示机制以交互方式提示用户输入密码。 如果私钥与证书不匹配,将引发SSLError。 """ SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None): """ 加载一组“证书颁发机构”(CA)证书,用于在验证模式不是CERT_NONE时验证其他对等体的证书。cafile和capath必须至少指定一个。 此方法还可以加载PEM或DER格式的证书撤销列表(CRL)。为了使用CRL,必须正确配置SSLContext.verify_flags。 cafile字符串(如果存在)是PEM格式的级联CA证书文件的路径。有关如何在此文件中安排证书的更多信息,请参阅证书的讨论。 capath字符串(如果存在)是指向包含多个PEM格式CA证书的目录的路径,遵循OpenSSL特定布局。 cadata对象(如果存在)是一个或多个PEM编码证书的ASCII字符串,或DER编码证书的类似字节的对象。与capath一样,PEM编码证书周围的额外行将被忽略,但必须至少存在一个证书。 """ SSLContext.get_ca_certs(binary_form=False): """ 获取加载的“证书颁发机构”(CA)证书的列表。 如果二进制形式参数为False,则每个列表条目都是一个dict,就像SSLSocket.getpeercert()的输出一样。 否则,该方法返回DER编码的证书列表。返回的列表不包含来自capath的证书,除非证书是由SSL连接请求和加载的。 """ SSLContext.set_ciphers(ciphers): """ 为使用此上下文创建的套接字设置可用密码。它应该是OpenSSL密码列表格式的字符串。 如果无法选择密码(因为编译时选项或其他配置禁止使用所有指定的密码),将引发SSLError。 """
2、主要模式介绍
SSL上下文
class ssl.
SSLContext
(protocol=None)
创建新的SSL上下文。您可以传递协议,该协议必须是本模块中定义的协议常数之一。该参数指定要使用的SSL协议版本。通常,服务器选择特定的协议版本,客户端必须适应服务器的选择。大多数版本都无法与其他版本互操作。如果未指定,则默认为PROTOCOL_TLS;它与其他版本的兼容性最好。
下面的表格显示了客户端(下方)中的哪些版本可以连接到服务器(顶部)中的哪些版本:
SSLContext.verify_mode:认证模式
ssl.CERT_NONE
SSLContext.verify_mode的可能值,或包装_socket()的cert_reqs参数。除PROTOCOL_TLS_CLENT外,它是默认模式。
对于客户端套接字,几乎接受任何证书。验证错误,如不受信任或过期的证书,将被忽略,并且不会中止TLS/SSL握手。
在服务器模式下,没有向客户端请求证书,因此客户端不会发送任何客户端证书身份验证。
ssl.CERT_OPTIONAL
SSLContext.verify_mode的可能值,或包装_socket()的cert_reqs参数。
在客户端模式下,CERT_OPTIONAL与CERT_REQUIRED的含义相同。建议对客户端套接字使用CERT_REQUIRED。
在服务器模式下,向客户端发送客户端证书请求。客户端可以忽略请求,也可以发送证书,以便执行TLS客户端证书身份验证。
如果客户端选择发送证书,则会对其进行验证。任何验证错误都会立即中止TLS握手。
使用此设置需要将一组有效的CA证书传递给SSLContext.load_verify_locations(),或作为ca_certs参数的值传递给包装_socket()。
ssl.CERT_REQUIRED
SSLContext.verify_mode的可能值,或包装_socket()的cert_reqs参数。
在此模式下,需要从套接字连接的另一端获得证书;如果未提供证书或其验证失败,将引发SSL错误。
此模式不足以在客户端模式下验证证书,因为它与主机名不匹配。还必须启用check_hostname才能验证证书的真实性。PROTOCOL_TLS_CLENT使用CERT_REQUIRED,默认情况下启用check_hostname。
对于服务器套接字,此模式提供强制性的TLS客户端证书身份验证。服务端向客户端发起客户端证书请求发,客户端必须提供有效的可信证书。
使用此设置需要将一组有效的CA证书传递给SSLContext.load_verify_locations(),或作为ca_certs参数的值传递给包装_socket()。
SSLContext.verify_flags:吊销列表校验
ssl.VERIFY_DEFAULT
SSLContext.verify_flags的可能值。在此模式下,不检查证书吊销列表(CRL)。默认情况下,OpenSSL既不要求也不验证CRL。
ssl.VERIFY_CRL_CHECK_LEAF
SSLContext.verify_flags的可能值。在这种模式下,只检查对端证书,不检查中间CA证书。
该模式需要由对等证书颁发者(其直接祖先CA)签名的有效CRL。如果没有正确的CRL加载SSLContext.load_verify_locations,则验证将失败。
ssl.VERIFY_CRL_CHECK_CHAIN
SSLContext.verify_flags的可能值,在这种模式下,会检查对端证书链中所有证书的CRL。
二、示例
1、服务端
import ssl from flask import Flask app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def hello_world(): return "hello World!" def get_ssl_context(): # ca根证书 ca_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\root.crt" # 吊销列表 server_crl_path = r"E:\MyData\TestProjects\TestPython36\ca\server.crl" # 服务端证书和秘钥 server_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\server.crt" server_key_path = r"E:\MyData\TestProjects\TestPython36\ca\server.key" # 创建ssl上下文 ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) # 选择认证模式:作为服务端,此选项为服务端必须校验客户端的证书,双向认证 ssl_context.verify_mode = ssl.CERT_REQUIRED # 不校验域名 ssl_context.check_hostname = False # 吊销列表校验:只检查对端证书,不检查中间CA证书 # ssl_context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF # ssl_context.load_verify_locations(server_crl_path) # 加密套件 ssl_context.set_ciphers = ("HIGH:!SSLv3:!TLSv1:!aNULL:@STRENGTH") # 加载ca根证书 ssl_context.load_verify_locations(ca_crt_path) # 加载服务端证书和秘钥,用于通信时携带 ssl_context.load_cert_chain(certfile=server_crt_path, keyfile=server_key_path) return ssl_context if __name__ == '__main__': ssl_context = get_ssl_context() app.run(host="127.0.0.1", port=5000, ssl_context=ssl_context)
2、客户端
import ssl import json from urllib import request from urllib import parse def get_ssl_context(): # ca根证书 ca_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\root.crt" # 吊销列表 client_crl_path = r"E:\MyData\TestProjects\TestPython36\ca\client.crl" # 客户端证书和秘钥 client_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\client.crt" client_key_path = r"E:\MyData\TestProjects\TestPython36\ca\client.key" # 创建ssl上下文 ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) # 选择认证模式:作为客户端,此选项为客户端必须校验服务端的证书 ssl_context.verify_mode = ssl.CERT_REQUIRED # 不校验域名 ssl_context.check_hostname = False # 吊销列表校验:只检查对端证书,不检查中间CA证书 # ssl_context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF # ssl_context.load_verify_locations(client_crl_path) # 加载ca根证书 ssl_context.load_verify_locations(ca_crt_path) # 加载客户端证书和秘钥,用于通信时携带 ssl_context.load_cert_chain(certfile=client_crt_path, keyfile=client_key_path) return ssl_context if __name__ == '__main__': """当我们需要加载吊销列表,ca根证书等等的时候,需要使用urlopen的方式""" ssl_context = get_ssl_context() url = "https://127.0.0.1:5000/" method = "GET" headers = {} body = {"test": "aaa"} # 是否以json格式的请求体发送数据 json_type = True if method == 'GET': req = request.Request(url, headers=headers, method='GET') else: if json_type: data = json.dumps(body).encode('utf-8') else: data = bytes(parse.urlencode(body), encoding='utf-8') req = request.Request(url, headers=headers, data=data, method='POST') with request.urlopen(req, context=ssl_context) as response: print(response.msg) print(response.read().decode("utf-8")) print(response.getcode())
还能使用 urllib3.HTTPSConnectionPool
import json from urllib3 import HTTPSConnectionPool def creat_request(): method = 'POST' host = "127.0.0.1" # 域名 port = "5000" # 端口 url = "/" # 路由 # ca根证书 ca_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\root.crt" # 吊销列表 -- HTTPSConnectionPool 似乎不支持 client_crl_path = r"E:\MyData\TestProjects\TestPython36\ca\client.crl" # 客户端证书和秘钥 client_crt_path = r"E:\MyData\TestProjects\TestPython36\ca\client.crt" client_key_path = r"E:\MyData\TestProjects\TestPython36\ca\client.key" # 密钥解密密码(如果没有,则不填) key_pwd = "abc123" headers = {} body = {"id": "666"} pool = HTTPSConnectionPool(host=host, port=port, cert_file=client_crt_path, key_file=client_key_path, ca_certs=ca_crt_path, key_password=key_pwd, assert_hostname=False) request_body = json.dumps(body) res = pool.request(method=method, url=url, body=request_body, headers=headers) print(res.status) print(res.data) if __name__ == '__main__': creat_request()