Python第 19 期、自動化第 8 期正在火熱招生中
小夥伴們可以點擊諮詢
通常,SSL / TLS 客戶端會驗證伺服器的證書。伺服器也可能需要客戶端的籤名證書。這些被稱為客戶證書。這確保了客戶端不僅可以信任伺服器,而且伺服器也可以信任客戶端。
傳統上在 Python 中,您需要將 ca_certs 參數傳遞給 ssl.wrap_socket() 伺服器上的函數以啟用客戶端證書:
#客戶端
ssl.wrap_socket(s,ca_certs =「ssl / server.crt」,cert_reqs = ssl.CERT_REQUIRED,
certfile =「ssl / client.crt」,keyfile =「ssl / client.key」)
#伺服器
ssl.wrap_socket(connection,server_side = True,certfile =「ssl / server.crt」,
keyfile =「ssl / server.key」,ca_certs =「ssl / client.crt」)
自從 Python v3.4 以來,在 SSL / TLS 層中封裝套接字的更安全且更 優選的方法是創建SSLContext實例和調用SSLContext.wrap_socket()。但是,該SSLContext.wrap_socket()方法沒有 ca_certs參數。如何在伺服器端啟用客戶端證書的要求也不是很明顯。
文檔 SSLContext.load_default_certs() 中提到了客戶端證書:
Purpose.CLIENT_AUTH 在伺服器端加載用於客戶端證書驗證的 CA 證書。
但 SSLContext.load_default_certs() 加載系統的默認可信證書授權鏈,以便 客戶端 可以驗證 伺服器的證書。您通常不希望將這些用於客戶端證書。
在驗證證書部分,它提到您需要指定 CERT_REQUIRED:
在伺服器模式下,如果要使用SSL層對客戶端進行身份驗證(而不是使用更高級別的身份驗證機制),則還必須指定 CERT_REQUIRED 並同樣檢查客戶端證書。
我沒有發現如何 CERT_REQUIRED 在 SSLContext 構造函數或 wrap_socket() 方法中指定。事實證明,您必須 SSLContext 在伺服器上手動設置屬性以啟用客戶端證書驗證,如下所示:
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile = server_cert,keyfile = server_key)
context.load_verify_locations(憑證檔案錯誤= client_certs)
以下是驗證對方證書的客戶端和伺服器的完整示例:
在這個例子中,我們將創建自籤名伺服器和客戶端證書。通常情況下,您需要使用來自證書頒發機構(如 Let's Encrypt )的伺服器證書,並且將設置您自己的證書頒發機構,以便您可以籤署和吊銷客戶機證書。
創建伺服器證書:
openssl req -new -newkey rsa:2048-days 365 -nodes -x509 -keyout server.key -out server.crt
確保輸入 'example.com' 作為通用名稱。
接下來,生成一個客戶端證書:
openssl req -new -newkey rsa:2048-days 365 -nodes -x509 -keyout client.key -out client.crt
客戶端證書的通用名稱並不重要。
客戶代碼:
#!的/ usr / bin中/ python3
導入套接字
導入ssl
host_addr ='127.0.0.1'
host_port = 8082
server_sni_hostname ='example.com'
server_cert ='server.crt'
client_cert ='client.crt'
client_key ='client.key'
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH,cafile = server_cert)
context.load_cert_chain(certfile = client_cert,keyfile = client_key)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
conn = context.wrap_socket(s,server_side = False,server_hostname = server_sni_hostname)
conn.connect((host_addr,host_port))
print(「SSL established。Peer:{}」。format(conn.getpeercert()))
列印(「發送:'你好,世界!')
conn.send(b「Hello,world!」)
列印(「關閉連接」)
conn.close()
伺服器代碼:
#!的/ usr / bin中/ python3
導入套接字
從套接字導入AF_INET,SOCK_STREAM,SO_REUSEADDR,SOL_SOCKET,SHUT_RDWR
導入ssl
listen_addr ='127.0.0.1'
listen_port = 8082
server_cert ='server.crt'
server_key ='server.key'
client_certs ='client.crt'
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile = server_cert,keyfile = server_key)
context.load_verify_locations(憑證檔案錯誤= client_certs)
bindsocket = socket.socket()
bindsocket.bind((listen_addr,listen_port))
bindsocket.listen(5)
而真:
列印(「等待客戶」)
newsocket,fromaddr = bindsocket.accept()
print(「客戶端連接:{}:{}」。format(fromaddr [0],fromaddr [1]))
conn = context.wrap_socket(newsocket,server_side = True)
print(「SSL established。Peer:{}」。format(conn.getpeercert()))
buf = b''#緩衝區保存接收到的客戶端數據
嘗試:
而真:
data = conn.recv(4096)
如果數據:
#客戶向我們發送了數據。追加到緩衝區
buf + =數據
其他:
#沒有更多的客戶數據。顯示緩衝區並關閉連接。
print(「Received:」,buf)
打破
最後:
列印(「關閉連接」)
conn.shutdown(socket.SHUT_RDWR)
conn.close()
伺服器的輸出如下所示:
$ python3 ./server.py
等待客戶
已連接客戶端:127.0.0.1:51372
建立SSL。Peer:{'subject':((('countryName','AU'),),
(('stateOrProvinceName','Some-State'),),(('organizationName','Internet
Widgits Pty Ltd'),),(('commonName','someclient'),)),'issuer':
((''countryName','AU'),),(('stateOrProvinceName','Some-State'),),
(('organizationName','Internet Widgits Pty Ltd'),),(('commonName',
'someclient'),)),'notBefore':'Jun 1 08:05:39 2018 GMT','version':3,
'serialNumber':'A564F9767931F3BC','notAfter':'Jun 1 08:05:39 2019 GMT'}
收到:b'Hello,世界!'
關閉連接
等待客戶
客戶輸出:
$ python3 ./client.py
建立SSL。Peer:{'notBefore':'May 30 20:47:38 2018 GMT','notAfter':
'May 30 20:47:38 2019 GMT','subject':((('countryName','NL'),),
(('stateOrProvinceName','GLD'),),(('localityName','Ede'),),
(('organizationName','Electricmonk'),),(('commonName','example.com'),)),
'(發行人):((('countryName','NL'),),(('stateOrProvinceName','GLD'),),
(('localityName','Ede'),),(('organizationName','Electricmonk'),),
(('commonName','example.com'),)),'version':3,'serialNumber':
'CAEC89334941FD9F'}
發送:'你好,世界!
關閉連接
一些注意事項:
您可以將多個客戶端證書連接成一個 PEM 文件,以驗證不同的客戶端。
您可以在伺服器和客戶端上重複使用相同的證書和密鑰。這樣,您不需要生成特定的客戶端證書。但是,使用該證書的任何客戶端都需要密鑰,並且能夠模擬伺服器。再也無法區分客戶。
您不需要設置您自己的證書頒發機構並籤署客戶端證書。您可以使用上述 openssl 命令生成它們,並將它們添加到受信任的證書文件中。如果您不再信任客戶端,只需從文件中刪除證書即可。
我不確定伺服器是否驗證客戶端證書的到期日期。