TLS 路徑上的三個立足點
當你既不掌控客戶端、也不掌控伺服器,卻要把一條連線升級到後量子密碼學時,你得先設法進入那條路徑。可以站的位置有三個,而每一個都會犧牲掉某樣東西—平台適用範圍、一行設定,或是最終由誰來掌握流量控制。三者之中,只有一個對兩端都是隱形的,而那正是最值得先弄懂的那一個。
Inline—在封包層
代理程式透過一段掛在核心 TC(流量控制)掛鉤上的 eBPF 程式,附著在網路介面上,支援 Linux 4.9 以上。它就地讀取、改寫、再送出封包,對被接合的流量從不開啟任何 socket。兩端都不需要設定、改路由或搬移—沒有 proxy 變數、沒有 CONNECT、不必讓出任何連接埠。兩端的 TLS 端點根本不知道它在那裡;它們只是進行協商,而得到的,是把中間密碼學替換掉的雙交握接合。這是唯一能同時對兩端都隱形的傳輸方式,這也正是我們把它擺在最前面的原因。它的代價是適用範圍:僅限 Linux、代理程式必須位於主機介面的路徑上,而且因為它運作在核心 TCP 堆疊之下,它必須自己掌管流量控制—這是一個有其後果的刻意取捨。
Redirect—以 netfilter 導入真正的 socket
往堆疊上爬一層。一條 iptables nat 規則—對主機自身對外的客戶端,是 OUTPUT 鏈上的 REDIRECT;對抵達閘道的流量,則是 PREROUTING 的 DNAT—把目的埠為 443 的連線轉向一個本地監聽埠;代理程式再用 SO_ORIGINAL_DST 取回原始目的地,並以 443 連向它。客戶端依然什麼都不必改,伺服器也保有它的連接埠。和 inline 不同的,在底下:被轉向的連線落在一個真正的核心 socket 上,於是代理程式免費繼承了核心的 TCP 堆疊—以及它的流量控制。代價是它需要 root 權限,以及在它所在的主機(或閘道)上的一條 netfilter 規則。一樣是 Linux,只是落在 socket 層,而非封包層。
Proxy—一個讓流量指向的端點
可攜的選項,也是唯一一個在 macOS 與 Windows 上原封不動就能跑的家族—因為它對核心的封包路徑一無所求,它就是一個普通的 TCP 服務。它有兩種型態。顯式(explicit)代理監聽在自己的連接埠上,由客戶端以標準方式指向它(一個 HTTPS_PROXY 設定,加上 CONNECT 方法);伺服器毫髮無傷。反向(reverse)代理自己綁定 443、轉發到你指定的上游,於是真正的服務讓出 443、搬到它後面;這次換成客戶端毫髮無傷。所以「proxy」從不需要同時改動客戶端與伺服器—它只需要其中一邊,而你選哪一邊。
標籤底下的那條軸線
把名字拆掉,其實只有兩個引擎和一個問題。兩個引擎:一個改寫封包的引擎(inline),以及一個轉送 socket 的引擎(其餘全部—redirect 不過是同一個引擎,只是改由 netfilter 餵給它,而非由客戶端的 proxy 設定)。那個問題是:由誰掌握流量控制。任何建立在核心 socket 上的—redirect、兩種 proxy—都免費得到核心那套久經沙場的 TCP 行為。inline 放棄了這個,以換取留在封包層;而它換回來的,是其他三者都沒有的那個特性:它對兩端什麼都不碰。這一個取捨,就是這整件事的性格。當你能部署在 Linux、又想對端點零足跡時,選 inline;當你需要另一個作業系統、無法安裝核心掛鉤,或單純想讓核心去扛那些位元組時,選一個 socket 傳輸。
同一個升級,三道門
這一切都沒有改變的,是密碼學本身。透過這三者中的任何一個,一個傳統客戶端與一個傳統伺服器,都能被接合到一次 X25519MLKEM768 交握(draft-ietf-tls-ecdhe-mlkem 所定義的金鑰交換)之上,而兩者都不必學會任何新的密碼套件—後量子的那一部分,由接合在中間完成。傳輸方式只決定那道門:你有多顯眼、你跑在哪個作業系統上,以及由誰(如果有的話)去改一行設定。為一個特定的網路選對那道門,正是遷移工作的大半—而它,是我們賴以為生的工作。