Tls

使用自簽名 PKI 和 Python 的 asyncio 模組進行 TLS 加密

  • October 28, 2016

我想為使用 Python 3 的 asyncio 建構的自包含分佈式系統啟用自定義/自簽名 PKI 的 TLS 加密。

我已經閱讀了 Python 的ssl 文件以及一些教程:

從所有這些中,我提取了以下步驟,以便為具有自簽名 PKI 的簡單客戶端和伺服器創建一個最小範例。

似乎按預期工作,但我不確定我是否遺漏了任何內容或創建了可能降低或取消系統安全性的非明顯錯誤。

這是我到目前為止所做的:

我將創建一個根 CA,它為每台機器簽署一個 CSR。每台機器都有自己的證書和密鑰。

根 CA 的注意事項:

  • 密鑰長度應該是 2048 位,或者更好的是 4096 位。
  • 應使用強密碼。
  • 密鑰不應離開機器(除非您將其儲存在 SD 卡上並將其隱藏在某個地方保存)。偏執程度應取決於安全需求。
  • 證書不應使用與任何生產系統相同的 FQDN。

生成密鑰和自簽名證書:

$ openssl genrsa -aes256 -out ca.key 4096
$ openssl req -new -x509 -nodes -key ca.key -out ca.pem -days 1000

在機器上創建密鑰和證書籤名請求 (CSR):

$ openssl genrsa -out server.key 4096
$ openssl genrsa -out client.key 4096
$ openssl req -new -key server.key -out server.csr
$ openssl req -new -key client.key -out client.csr

現在您可以使用您的 CA 證書籤署設備密鑰:

$ openssl x509 -CA ca.pem -CAkey ca.key -CAcreateserial -req -in server.csr -out server.pem -days 365
$ openssl x509 -CA ca.pem -CAkey ca.key -CAcreateserial -req -in client.csr -out client.pem -days 365

最小的 Python 範例。我們需要為客戶端和伺服器使用不同的 SSLContext,因為它們做一些不同的事情。客戶端和伺服器都將使用 TLS 1.2 和 ECDH-AESGCM。他們將根據根 CA 證書驗證彼此的證書。客戶端還檢查伺服器的主機名。每個 SSL 會話僅使用一次 ECDH 密鑰,並且禁用壓縮:

import asyncio
import ssl


@asyncio.coroutine
def handle_client(reader, writer):
   data = yield from reader.read(100)
   print(data)
   writer.write(b'cya')
   yield from writer.drain()
   writer.close()


@asyncio.coroutine
def client(addr, ssl_ctx):
   reader, writer = yield from asyncio.open_connection(*addr, ssl=ssl_ctx)
   writer.write(b'ohai')
   data = yield from reader.read(100)
   print(data)
   writer.close()


addr = ('127.0.0.1', 5555)
loop = asyncio.get_event_loop()

# Setup server
server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
server_ctx.verify_mode = ssl.CERT_REQUIRED
server_ctx.options |= ssl.OP_SINGLE_ECDH_USE
server_ctx.options |= ssl.OP_NO_COMPRESSION
server_ctx.load_cert_chain(certfile='server.pem', keyfile='server.key')
server_ctx.load_verify_locations(cafile='ca.pem')
server_ctx.set_ciphers('ECDH+AESGCM')

coro = asyncio.start_server(handle_client, *addr, ssl=server_ctx)
server = loop.run_until_complete(coro)

# Run client
client_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
client_ctx.verify_mode = ssl.CERT_REQUIRED
client_ctx.check_hostname = True
client_ctx.load_cert_chain(certfile='client.pem', keyfile='client.key')
client_ctx.load_verify_locations(cafile='ca.pem')
client_ctx.set_ciphers('ECDH+AESGCM')

loop.run_until_complete(client(addr, client_ctx))

# Shutdown
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

腳本將輸出

b'ohai'
b'cya'

正如預期的那樣,TLS似乎有效。是否有任何問題或可以做得更好的事情?

感謝您的回饋意見。

因為我偶然發現了這個檢查別的東西,所以有一些小問題

  • CA 不簽署 CSR;它簽署(頒發)一個證書,其中包含一些從 CSR 中獲取的重要數據,但有很多不同的數據。
  • CA 證書及其頒發的所有伺服器和客戶端證書的主題名稱應該是唯一的,並且最好是助記符。CA 和客戶端名稱根本不需要是 FQDN 或域名,儘管如果它們是 FQDN 有助於使它們獨一無二,並且通常是助記符。對於伺服器,Subject 的 CommonName部分或 SubjectAlternativeName 擴展(如果使用)應該- 並且對於許多但不是所有客戶端必須- 匹配客戶端用於連​​接到伺服器的“主機名”。通常這是 FQDN,但在測試和 Intranet 設置中可能不是;您的範例顯示連接到環回或“本地主機”地址127.0.0.1。
  • ECDH-AESGCM 不是密碼套件,甚至不是 SSL/TLS 中的密鑰交換;ECDH+AESGM 是 8 種不同密碼套件的 OpenSSL 簡寫——其中只有 2 種使用 RSA 密鑰+證書,即 ECDHE-RSA-AES256-GCM-SHA384 和 ECDHE-RSA-AES128-GCM-SHA256。
  • 您沒有明顯指定臨時(短暫)ecdh 參數(曲線),因此您可能從 Python 獲得了預設值;OpenSSL 本身沒有預設設置,儘管 1.0.2 以上版本有一個非預設選項來自動使用客戶端首選項。既然你選擇了其他相當強的東西,你應該確保 ECDHE 曲線不是弱到無法接受的程度。我希望它可以在 Python 原始碼中找到;如果沒有,請監視與 Wireshark 或類似工具的實際連接以進行檢查。

引用自:https://crypto.stackexchange.com/questions/26591