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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM