ossl-guide-tls-introduction
名稱
ossl-guide-tls-introduction - OpenSSL 指南:OpenSSL 中 SSL/TLS 簡介
簡介
此頁面將簡介一些基本的 SSL/TLS 概念和背景,以及如何在 OpenSSL 中使用它。它假設您對 TCP/IP 和 socket 有基本的了解。
什麼是 TLS?
TLS 代表傳輸層安全性。TLS 允許應用程式透過網路安全地彼此通訊,以保護所交換資訊的機密性(即防止竊聽者竊聽通訊)。此外,它保護所交換資訊的完整性,以防止攻擊者變更它。最後,它提供驗證,以便一方或雙方都能確定他們正在與他們認為正在交談的人交談,而不是冒名頂替者。
有時 TLS 會以其前身 SSL(安全通訊層)的名稱稱呼。OpenSSL 來自 SSL 名稱仍普遍使用的時代,因此 OpenSSL 使用的許多功能和名稱都包含「SSL」縮寫。儘管如此,OpenSSL 仍包含一個功能齊全的 TLS 實作。
TLS 基於客戶端/伺服器模型。啟動通訊的應用程式稱為客戶端。回應遠端啟動通訊的應用程式是伺服器。術語「端點」是指通訊中的客戶端或伺服器。術語「對等端」是指我們目前正在討論的通訊另一端的端點。因此,如果我們目前正在討論客戶端,則對等端將是伺服器。
TLS 是標準化的協定,並且有許多不同的實作。由於標準,OpenSSL 客戶端或伺服器能夠與使用某些不同 TLS 實作的應用程式無縫通訊。TLS(及其前身 SSL)已經存在很長一段時間,並且該協定多年來經歷了各種變更。因此,有不同版本的協定可用。TLS 包含執行版本協商的能力,以便使用客戶端和伺服器共有的最高協定版本。
TLS 作為某些較低層級傳輸協定的安全性層。通常傳輸層將是 TCP。
SSL 和 TLS 版本
SSL 最初是由 Netscape Communications 開發的,其第一個公開發布的版本是 1995 年的 SSLv2。請注意,SSLv1 從未公開發布。SSLv3 在 1996 年隨後快速推出。隨後,協定的開發轉移到 IETF,該組織在 1999 年發布了第一個 TLS 版本(TLSv1.0)作為 RFC2246。TLSv1.1 於 2006 年作為 RFC4346 發布,TLSv1.2 於 2008 年作為 RFC5246 發布。最新版本的標準是 TLSv1.3,於 2018 年作為 RFC8446 發布。
今天,TLSv1.3 和 TLSv1.2 是最常部署的協定版本。IETF 已正式棄用 TLSv1.1 和 TLSv1.0,因此應避免低於 TLSv1.2 的任何版本,因為較舊的協定版本容易出現安全性問題。
OpenSSL 不支援 SSLv2(已在 OpenSSL 1.1.0 中移除)。SSLv3 支援可作為編譯時間選項,但並非預設建置。TLSv1.0、TLSv1.1、TLSv1.2 和 TLSv1.3 支援皆為 OpenSSL 標準建置的預設值。然而,若要讓 TLSv1.0 和 TLSv1.1 順利運作,需要特殊的執行時間設定。
OpenSSL 將始終嘗試協商已設定支援的最高通訊協定版本。在多數情況下,這表示將選擇 TLSv1.3 或 TLSv1.2。
憑證
為了讓用戶端建立與伺服器的連線,用戶端必須驗證該伺服器的識別,亦即需要確認伺服器確實為其聲稱的伺服器,而非冒充者。為此,伺服器將傳送數位憑證(也常稱為 X.509 憑證)給用戶端。憑證包含伺服器的各種資訊,包括其完整的 DNS 主機名稱。憑證中也包含伺服器的公開金鑰。伺服器操作員將擁有與公開金鑰連結的私密金鑰,且不得公開。
除了憑證之外,伺服器也會傳送證明其知道與憑證中的公開金鑰相關聯的私密金鑰的證明給用戶端。伺服器會使用該私密金鑰對傳送給用戶端的一則訊息進行數位簽章。用戶端可以使用憑證中的公開金鑰驗證簽章。如果簽章驗證成功,則用戶端便知道伺服器擁有正確的私密金鑰。
伺服器傳送的憑證也會由憑證授權機構簽章。憑證授權機構(通常稱為 CA)是負責驗證伺服器憑證中資訊(包括其 DNS 主機名稱)的第三方組織。CA 只有在確認伺服器操作員確實控制與其 DNS 主機名稱相關聯的伺服器,以及伺服器操作員控制私密金鑰後,才會簽署憑證。
如此一來,如果用戶端信任簽署伺服器憑證的 CA,且可以驗證伺服器擁有正確的私密金鑰,則用戶端便可以信任伺服器確實代表憑證中提供的 DNS 主機名稱。用戶端也必須驗證憑證中提供的與其最初傳送要求的相同。
完成所有這些檢查後,用戶端已成功驗證伺服器的識別。OpenSSL 可以自動執行所有這些檢查,但必須提供特定資訊才能執行,例如用戶端信任的 CA 組合,以及此用戶端嘗試連線的伺服器的 DNS 主機名稱。
請注意,證書通常會建立成一個鏈。例如,伺服器的證書可能是由中間 CA 所擁有的金鑰簽署的。該中間 CA 也有包含其公開金鑰的證書,而該金鑰又是由根 CA 所擁有的金鑰簽署的。用戶端可能只信任根 CA,但如果伺服器同時傳送它自己的證書和中間 CA 的證書,則用戶端仍可以成功驗證伺服器的識別。根 CA 和伺服器之間存在信任鏈。
預設情況下,只有用戶端使用此方法驗證伺服器。但是,也可以設定讓伺服器另外驗證用戶端。這稱為「用戶端驗證」。在此方法中,用戶端仍會以相同的方式驗證伺服器,但伺服器會向用戶端要求證書。用戶端會將其證書傳送給伺服器,而伺服器會以與用戶端相同的方式驗證它。
受信任的證書儲存
上述系統只有在端點信任的 CA 組合與對等端使用的證書之間可以建立信任鏈時才會運作。因此,在進行任何通訊之前,端點必須有一組它信任的 CA 證書。OpenSSL 本身並未提供此類證書組。因此,如果您要驗證證書(例如,如果端點是用戶端,則永遠是;如果伺服器使用用戶端驗證,則只有在這種情況下),則需要在開始之前確保擁有它們。
幸運的是,其他組織確實維護著這樣的憑證組。如果您從作業系統 (OS) 供應商(例如 Linux 發行版)取得 OpenSSL 副本,通常該副本也會附帶 CA 憑證組。
您可以透過執行 OpenSSL 命令列應用程式來檢查,如下所示
openssl version -d
這將顯示 OPENSSLDIR 的值。在 OPENSSLDIR 的 certs 子目錄中查看並檢查其內容。例如,如果 OPENSSLDIR 是「/usr/local/ssl」,則檢查「/usr/local/ssl/certs」目錄的內容。
您預期會看到檔案清單,通常附檔名為「.pem」或「.0」。如果它們存在,表示您已經有合適的受信任憑證儲存區。
如果您在 Windows 上執行 OpenSSL 版本,則 OpenSSL(從 3.2 版開始)將使用 Windows 預設的受信任 CA 組。
如果您從原始碼建置 OpenSSL 版本,或從其他位置取得,且它沒有受信任 CA 憑證組,則您必須自行取得。其中一個來源是 Curl 專案。請參閱頁面 https://curl.se/docs/caextract.html,您可以在其中下載單一檔案中的受信任憑證。將檔案重新命名為「cert.pem」,並直接儲存在 OPENSSLDIR 中。例如,如果 OPENSSLDIR 是「/usr/local/ssl」,則將其儲存為「/usr/local/ssl/cert.pem」。
您也可以使用環境變數來覆寫 OpenSSL 尋找其受信任憑證儲存區的預設位置。設定 SSL_CERT_PATH 環境變數以提供 OpenSSL 應尋找其憑證的目錄,或設定 SSL_CERT_FILE 環境變數以提供包含所有憑證的單一檔案名稱。請參閱 openssl-env(7) 以進一步了解 OpenSSL 環境變數。例如,您可以使用此功能讓多個 OpenSSL 版本全部安裝在同一個系統上,使用不同的 OPENSSLDIR 值,但全部使用相同的受信任憑證儲存區。
您可以透過透過 OpenSSL 命令列使用受信任憑證儲存區來測試其設定是否正確。使用下列命令連線到 TLS 伺服器
openssl s_client www.openssl.org:443
命令連線後,輸入字母「Q」,然後按「<enter>」以結束會話。這會在螢幕上列印許多有關連線的資訊。尋找類似下列文字的文字區塊
SSL handshake has read 4584 bytes and written 403 bytes
Verification: OK
希望如果一切正常,則「驗證」列會顯示「確定」。如果未按預期運作,您可能會看到類似下列的輸出
SSL handshake has read 4584 bytes and written 403 bytes
Verification error: unable to get local issuer certificate
「無法取得本機發行者憑證」錯誤表示 OpenSSL 無法在受信任憑證儲存區中為伺服器提供的憑證鏈找到受信任 CA。請再次檢查您的受信任憑證儲存區設定。
請注意,s_client 是測試工具,無論驗證錯誤為何,它仍會允許您連線到 TLS 伺服器。大多數應用程式不應執行此動作,且應在發生驗證錯誤時中止連線。
OPENSSL TLS 應用程式的重點物件
在基於 OpenSSL 的應用程式中,TLS 連線由 SSL 物件表示。與遠端對等端建立連線後,端點可以「寫入」資料到 SSL 物件以將資料傳送給對等端,或從中「讀取」資料以從伺服器接收資料。
從 SSL_CTX 物件建立新的 SSL 物件。將 SSL_CTX 視為建立 SSL 物件的「工廠」。您可以建立一個 SSL_CTX 物件,然後從中建立多個連線(例如 SSL 物件)。通常,您可以在 SSL_CTX 上設定常見的組態選項,以便從中建立的所有 SSL 物件繼承相同的組態選項。
請注意,在 OpenSSL 內部,多個 SSL 物件之間共用的各種項目會快取在 SSL_CTX 中,以提升效能。因此,建議為多個 SSL 物件建立一個 SSL_CTX,而不是為建立的每個 SSL 物件建立一個 SSL_CTX。
每個 SSL 物件也與兩個 BIO 物件關聯。BIO 物件用於傳送或接收來自底層傳輸層的資料。例如,您可以建立一個 BIO 來表示 TCP socket。SSL 物件使用一個 BIO 來讀取資料,並使用另一個 BIO 來寫入資料。在大部分情況下,您會對每個方向使用相同的 BIO,但在某些情況下,您可能希望它們不同。
應用程式程式設計人員負責建立所需的 BIO 物件,並將它們提供給 SSL 物件。請參閱 ossl-guide-tls-client-block(7) 以取得更多資訊。
最後,端點可以與其對等端建立「工作階段」。工作階段會保留關於用戶端與伺服器之間連線的各種 TLS 參數。然後,工作階段詳細資料可以在後續連線嘗試中重複使用,以加快連線處理。這稱為「恢復」。工作階段在 OpenSSL 中由 SSL_SESSION 物件表示。在 TLSv1.2 中,每個連線只有一個工作階段。在 TLSv1.3 中,每個連線可以有多個工作階段,包括沒有工作階段。
TLS 連線的階段
TLS 連線從初始「設定」階段開始。端點建立 SSL_CTX(如果尚未建立),並設定組態。
然後,用戶端建立一個 SSL 物件來表示新的 TLS 連線。接著套用任何連線特定的組態參數,並建立底層 socket,並透過 BIO 物件與 SSL 關聯。
伺服器將建立一個 socket 來偵聽來自用戶端的連線嘗試。一旦建立連線嘗試,伺服器將以與用戶端相同的方式建立一個 SSL 物件,並將其與新建立的輸入 socket 的 BIO 關聯。
設定完成後,TLS「握手」階段便會開始。TLS 握手包含用戶端和伺服器交換一系列 TLS 握手訊息,以建立連線。用戶端會先傳送「ClientHello」握手訊息,而伺服器則會以「ServerHello」回應。當一個端點傳送其最後一則訊息(稱為「完成」訊息)並從其對等端收到完成訊息後,握手便會完成。請注意,這可能會在每個對等端發生在略微不同的時間點。例如在 TLSv1.3 中,伺服器總是在用戶端之前傳送其完成訊息。用戶端稍後會以其完成訊息回應。此時,用戶端已完成握手,因為它已傳送和收到完成訊息。伺服器已傳送其完成訊息,但來自用戶端的完成訊息可能仍在傳輸中,因此伺服器仍處於握手階段。甚至有可能伺服器無法完成握手(如果它認為用戶端傳送的訊息存在一些問題),即使用戶端可能已進行傳送應用程式資料。在 TLSv1.2 中,這可能會以相反的方式發生,即伺服器先完成,然後用戶端再完成。
握手完成後,應用程式資料傳輸階段便會開始。嚴格來說,有些情況下,用戶端可以更早開始傳送應用程式資料(使用 TLSv1.3 的「早期資料」功能) - 但我們將在這個基本介紹中跳過這一點。
在應用程式資料傳輸期間,用戶端和伺服器可以自由地讀取和寫入資料到連線。此資料的詳細資訊通常留給一些較高層級的應用程式協定(例如 HTTP)。在此階段交換的所有資訊並非都是應用程式資料。一些協定層級訊息可能仍會交換 - 因此,僅僅因為基礎 socket 是「可讀取的」,並不一定表示應用程式資料可供讀取。
當不再需要連線時,就應該關閉它。關閉可以由用戶端或伺服器透過稱為「close_notify」警示的訊息啟動。收到 close_notify 的用戶端或伺服器可能會以一個回應,然後連線便會完全關閉,並且應用程式資料無法再傳送或接收。
關閉完成後,TLS 應用程式必須透過釋放 SSL 物件來清理。
進一步閱讀
請參閱 ossl-guide-tls-client-block(7),以查看應用這些概念來撰寫基於封鎖 socket 的簡單 TLS 用戶端的範例。請參閱 ossl-guide-quic-introduction(7),以了解 OpenSSL 中的 QUIC 簡介。
另請參閱
ossl-guide-introduction(7)、ossl-guide-libraries-introduction(7)、ossl-guide-libssl-introduction(7)、ossl-guide-tls-client-block(7)、ossl-guide-quic-introduction(7)
版權
版權所有 2023 The OpenSSL Project Authors。保留所有權利。
根據 Apache 許可證 2.0 版(「許可證」)授權。您不得使用此檔案,除非符合許可證。您可以在原始程式碼散佈中的 LICENSE 檔案中取得副本,或至 https://www.openssl.org/source/license.html 取得。