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;它与其他版本的兼容性最好。

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

client / server

SSLv2

SSLv3

TLS 3

TLSv1

TLSv1.1

TLSv1.2

SSLv2

yes

no

no 1

no

no

no

SSLv3

no

yes

no 2

no

no

no

TLS (SSLv233

no 1

no 2

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

 

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

 

posted @ 2022-02-08 22:27  我用python写Bug  阅读(5195)  评论(0编辑  收藏  举报