碼了2000多行代碼就是爲了講清楚TLS握手流程

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來自公衆號:新世界雜貨鋪","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"呼,這篇文章的準備週期可謂是相當的長了!原本是想直接通過源碼進行分析的,但是發現TLS握手流程調試起來非常不方便,筆者怒了,於是實現了一個極簡的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"net.Conn","attrs":{}}],"attrs":{}},{"type":"text","text":"接口以方便調試。碼着碼着,筆者哭了,因爲現在這個調試Demo已經達到2000多行代碼了!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/be/be39ad48832a087d5fa3438b7403d43f.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然碼了兩千多行代碼,但是目前只能夠解析TLS1.3握手流程中發送的消息,因此本篇主要分析TLS1.3的握手流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特別提醒:有想在本地調試一番的小夥伴請至文末獲取本篇源碼。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"結論先行","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鑑於本文篇幅較長,筆者決定結論先行,以助各位讀者理解後文詳細的分析內容。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTPS單向認證","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單向認證客戶端不需要證書,客戶端只要驗證服務端證書合法即可訪問。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是筆者運行Demo打印的調試信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8cd8c2688e51b3f497477830ca2b88f2.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據調試信息知,在TLS1.3單向認證中,總共收發數據","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三次","attrs":{}},{"type":"text","text":",Client和Server從這三次數據中分別讀取不同的信息以達到握手的目的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意","attrs":{}},{"type":"text","text":":TLS1.3不處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"類型的數據,而該數據在TLS1.2中是需要處理的。因本篇主要分析TLS1.3握手流程,故後續不會再提及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":",同時","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"時序圖中也會忽略此消息","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"筆者將調試信息轉換爲下述時序圖,以方便各位讀者理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3cfe4fd7fce54a79e2a5e1882b1b995a.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTPS雙向認證","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向認證不僅服務端要有證書,客戶端也需要證書,只有客戶端和服務端證書均合法纔可繼續訪問。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"筆者在這裏特別提醒,開啓雙向認證很簡單,在筆者的Demo中取消下面代碼的註釋即可。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// sconf.ClientAuth = tls.RequireAndVerifyClientCert","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,筆者在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"main.go","attrs":{}}],"attrs":{}},{"type":"text","text":"同目錄下留有測試用的根證書、服務端證書和客戶端證書,爲了保證雙向認證的順利運行請將根證書安裝爲受用戶信任的證書。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是筆者運行Demo打印的調試信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/53/5365946d1ccbdb044546fb3e8074ef50.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同單向認證一樣,筆者將調試信息轉換爲下述時序圖。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ea/eaa29576d95429f7be4e0d74756acf63.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向認證和單向認證相比,Server發消息給Client時會額外發送一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,Client收到此消息後會將證書信息(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":")和簽名信息(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":")發送給Server。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向認證中,Client和Server發送消息變多了,但是總的數據收發仍然只有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"三次","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、TLS1.3和TLS1.2握手流程是有區別的,這一點需要注意。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、單向認證和雙向認證中,總的數據收發僅三次,單次發送的數據中包含一個或者多個消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"未經過加密,之後發送的消息均做了加密處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、Client和Server會各自計算兩次密鑰,計算時機分別是讀取到對方的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之後。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注","attrs":{}},{"type":"text","text":":上述第3點和第4點分析過程詳見後文。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Client發送HelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在TLS握手過程中的第一步是Client發送HelloMsg,所以針對TLS握手流程的分析也從這一步開始。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server對於Client的基本信息瞭解完全依賴於Client主動告知Server,而其中比較關鍵的信息分別是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客戶端支持的TLS版本","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客戶端支持的加密套件(cipherSuites)","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客戶端支持的簽名算法","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"客戶端支持的密鑰交換協議以及其對應的公鑰","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端支持的TLS版本:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端支持的TLS版本主要通過tls包中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).supportedVersions","attrs":{}}],"attrs":{}},{"type":"text","text":"方法計算。對TLS1.3來說默認支持的TLS版本如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var supportedVersions = []uint16{\n\tVersionTLS13,\n\tVersionTLS12,\n\tVersionTLS11,\n\tVersionTLS10,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在發起請求時如果用戶手動設置了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MaxVersion","attrs":{}}],"attrs":{}},{"type":"text","text":"或者","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"MinVersion","attrs":{}}],"attrs":{}},{"type":"text","text":",則客戶端支持的TLS版本會發生變化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如發起請求時,設置了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"conf.MaxVersion = tls.VersionTLS12","attrs":{}}],"attrs":{}},{"type":"text","text":",此時","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).supportedVersions","attrs":{}}],"attrs":{}},{"type":"text","text":"返回的版本爲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"[]uint16{\n\tVersionTLS12,\n\tVersionTLS11,\n\tVersionTLS10,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps: 如果有興趣的小夥伴可以在克隆筆者的demo後手動設置Config.MaxVersion,設置後可以調試TLS1.2的握手流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端支持的加密套件(cipherSuites):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說實話,加密套件已經進入筆者的知識盲區了,其作用筆者會在下一小節講明白,故本小節筆者直接貼出計算後的結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/eb7d21a5652e847b40bfc868cc0c7d3d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖中籃框部分爲當前Client支持加密套件Id,紅框部分爲計算邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端支持的簽名算法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端支持的簽名算法,僅在客戶端支持的最大TLS版本大於等於TLS1.2時生效。此時客戶端支持的簽名算法如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var supportedSignatureAlgorithms = []SignatureScheme{\n\tPSSWithSHA256,\n\tECDSAWithP256AndSHA256,\n\tEd25519,\n\tPSSWithSHA384,\n\tPSSWithSHA512,\n\tPKCS1WithSHA256,\n\tPKCS1WithSHA384,\n\tPKCS1WithSHA512,\n\tECDSAWithP384AndSHA384,\n\tECDSAWithP521AndSHA512,\n\tPKCS1WithSHA1,\n\tECDSAWithSHA1,\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端支持的密鑰交換協議及其對應的公鑰:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一塊兒邏輯僅在客戶端支持的最大TLS版本是TLS1.3時生效。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if hello.supportedVersions[0] == VersionTLS13 {\n\thello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13()...)\n\n\tcurveID := config.curvePreferences()[0]\n\tif _, ok := curveForCurveID(curveID); curveID != X25519 && !ok {\n\t\treturn nil, nil, errors.New(\"tls: CurvePreferences includes unsupported curve\")\n\t}\n\tparams, err = generateECDHEParameters(config.rand(), curveID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\thello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}}\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中,方法","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"config.curvePreferences","attrs":{}}],"attrs":{}},{"type":"text","text":"的邏輯爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521}\nfunc (c *Config) curvePreferences() []CurveID {\n\tif c == nil || len(c.CurvePreferences) == 0 {\n\t\treturn defaultCurvePreferences\n\t}\n\treturn c.CurvePreferences\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本篇中,筆者未手動設置優先可供選擇的曲線,故","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"curveID","attrs":{}}],"attrs":{}},{"type":"text","text":"的值爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"X25519","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函數的作用是根據曲線Id生成一種橢圓曲線密鑰交換協議的實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果客戶端支持的最大TLS版本是TLS1.3時,會爲Client支持的加密套件增加TLS1.3默認的加密套件,同時還會選擇","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Curve25519","attrs":{}},{"type":"text","text":"密鑰交換協議生成","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"keyShare","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節介紹了在TLS1.3中Client需要告知Server客戶端支持的TLS版本號、客戶端支持的加密套件、客戶端支持的簽名算法和客戶端支持的密鑰交換協議。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Server讀HelloMsg&發送消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server讀到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之後會根據客戶端支持的TLS版本和本地支持的TLS版本做對比,得到Client和Server均支持的TLS版本最大值,該值作爲後續繼續通信的標準。本篇中Client和Server都支持TLS1.3,因此Server進入TLS1.3的握手流程。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"處理clientHelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server進入TLS1.3握手流程之後,還需要繼續處理clientHelloMsg,同時構建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server支持的TLS版本","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進入TLS1.3握手流程之前,Server已經計算出兩端均支持的TLS版本,但是Client還無法得知Server支持的TLS版本,因此開始繼續處理clientHelloMsg時,Server將已經計算得到的TLS版本賦值給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"supportedVersion","attrs":{}}],"attrs":{}},{"type":"text","text":"以告知客戶端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// client讀取到serverHelloMsg後,通過讀取此字段計算兩端均支持的TLS版本\nhs.hello.supportedVersion = c.vers","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server計算兩端均支持的加密套件","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"中含有Client支持的加密套件信息,Server讀取該信息並和本地支持的加密套件做對比計算出兩端均支持的加密套件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏需要注意的是,如果Server的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config.PreferServerCipherSuites","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":"則選擇Server第一個在兩端均支持的加密套件,否則選擇Client第一個在兩端均支持的加密套件。筆者通過Debug得到兩端均支持的加密套件id爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"4865","attrs":{}}],"attrs":{}},{"type":"text","text":"(其常量爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.TLS","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"AES","attrs":{}},{"type":"text","text":"128_GCM_SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"),詳情見下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/31924f987ea466b2cac89138ec90e483.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mutualCipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"函數會從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuitesTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"變量中選擇匹配的加密套件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var cipherSuitesTLS13 = []*cipherSuiteTLS13{\n\t{TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256},\n\t{TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256},\n\t{TLS_AES_256_GCM_SHA384, 32, aeadAESGCMTLS13, crypto.SHA384},\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結合前面的Debug信息知,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"結構體的變量且其值爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuitesTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"切片的第一個。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuiteTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"結構體定義如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type cipherSuiteTLS13 struct {\n\tid uint16\n\tkeyLen int\n\taead func(key, fixedNonce []byte) aead\n\thash crypto.Hash\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server已經計算出雙端均支持的加密套件,Server通過設置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cipherSuite","attrs":{}}],"attrs":{}},{"type":"text","text":"將雙端均支持的加密套件告知Client:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.hello.cipherSuite = hs.suite.id\nhs.transcript = hs.suite.hash.New()","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在後續計算密鑰時需要對Client和Server之間的所有消息計算Hash摘要。根據前面計算出的加密套件知,本篇中計算消息摘要的Hash算法爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":",此算法的實現賦值給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript","attrs":{}}],"attrs":{}},{"type":"text","text":"變量,後續計算消息摘要時均通過該變量實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Server計算雙端均支持的密鑰交換協議以及對應的公鑰","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.keyShares","attrs":{}}],"attrs":{}},{"type":"text","text":"變量記錄着Client支持的曲線Id以及對應的公鑰。Server通過對比本地支持的曲線Id計算出雙端均支持的密鑰交換協議。根據前面","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Client發送HelloMsg","attrs":{}},{"type":"text","text":"這一小節的內容以及筆者實際調試的結果,雙端均支持的曲線爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Curve25519","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Server計算出雙端均支持的曲線後,調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"方法得到對應密鑰交換協議的實現,即Curve25519密鑰交換協議。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Curve25519","attrs":{}}],"attrs":{}},{"type":"text","text":"是橢圓曲線迪菲-赫爾曼(Elliptic-curve Diffie–Hellman ,縮寫爲ECDH)密鑰交換方案之一,同時也是最快的ECC(Elliptic-curve cryptography)曲線之一。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"可以爲Client和Server在不安全的通道上爲雙方建立共享密鑰,並且Client和Server需要各自持有一組橢圓曲線公私密鑰對。當Client和Server需要建立共享密鑰時僅需要公佈各自的公鑰,Client和Server通過對方的公鑰以及自己的私鑰即可計算出相等的密鑰。如果公鑰被第三方截獲也無關緊要,因爲第三方沒有私鑰無法計算出共享密鑰除非第三方能夠解決橢圓曲線Diffie–Hellman問題。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDHE","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"的一個變種,其區別僅僅是私鑰和公鑰在每次建立共享密鑰時均需重新生成(以上爲筆者對維基百科中ECDH的理解)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDHE","attrs":{}}],"attrs":{}},{"type":"text","text":"有了一定的理解後,我們現在看一下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函數中的部分源碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func generateECDHEParameters(rand io.Reader, curveID CurveID) (ecdheParameters, error) {\n\tif curveID == X25519 {\n\t\tprivateKey := make([]byte, curve25519.ScalarSize)\n\t\tif _, err := io.ReadFull(rand, privateKey); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpublicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &x25519Parameters{privateKey: privateKey, publicKey: publicKey}, nil\n\t}\n // 此處省略代碼\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每次調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函數時均會生成一組新的橢圓曲線公私密鑰對。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.keyShares","attrs":{}}],"attrs":{}},{"type":"text","text":"變量存有Client的公鑰,因此Server已經可以計算共享密鑰:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"params, err := generateECDHEParameters(c.config.rand(), selectedGroup)\nif err != nil {\n c.sendAlert(alertInternalError)\n return err\n}\nhs.hello.serverShare = keyShare{group: selectedGroup, data: params.PublicKey()}\nhs.sharedKey = params.SharedKey(clientKeyShare.data) // 共享密鑰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中Server已經計算出共享密鑰,之後可以通過此密鑰派生出其他密鑰爲數據加密。Client因爲無Server的公鑰還無法計算出共享密鑰,所以Server通過設置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverShare","attrs":{}}],"attrs":{}},{"type":"text","text":"變量告知Client服務端的公鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server對Client發來的helloMsg已經處理完畢。筆者在這裏額外提醒一句,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"中仍然有Client和Server生成的隨機數,但是在TLS1.3中這兩個隨機數已經和密鑰交換無關了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節介紹了Server讀取","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"後會計算雙端支持的TLS版本以及雙端支持的加密套件和密鑰交換協議,同時還介紹了共享密鑰的生成以及ECDH的概念。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"選擇合適的證書以及簽名算法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Server選擇和當前Client匹配的證書前其實還有關於預共享密鑰模式的處理,該模式需要實現","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientSessionCache","attrs":{}}],"attrs":{}},{"type":"text","text":"接口,鑑於其不影響握手流程的分析,故本篇不討論預共享密鑰模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個Server可能給多個Host提供服務,因此Server可能持有多個證書,那麼選擇一個和當前Client匹配的證書是十分必要的,其實現邏輯參見","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(*Config).getCertificate","attrs":{}}],"attrs":{}},{"type":"text","text":"方法。本篇中的Demo只有一個證書,故該方法會直接返回此證書。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"證書中是包含公鑰的,不同的公鑰支持的簽名算法是不同的,在本例中Server支持的簽名算法和最終雙端均支持的簽名算法見下面的Debug結果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bb/bbd67e99ae3eff7fd050fc3d02e696ec.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖中紅框部分爲Server支持的簽名算法,藍框爲選定的雙端均支持的簽名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節主要介紹了Server選擇匹配當前Client的證書和簽名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"計算握手階段的密鑰以及發送Server的參數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個階段Server會將","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"寫入緩衝區,寫完之後再寫入一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"(TLS1.3不會處理此消息)消息,需要注意的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"未進行加密發送。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"計算握手階段的密鑰","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面提到過計算密鑰需要計算消息摘要:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.transcript.Write(hs.clientHello.marshal())\nhs.transcript.Write(hs.hello.marshal()) // hs.hello爲serverHelloMsg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript","attrs":{}}],"attrs":{}},{"type":"text","text":"在前面已經提到過是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"Hash算法的一種實現。下面我們逐步分析源碼中Server第一次計算密鑰的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"earlySecret := hs.earlySecret \nif earlySecret == nil {\n earlySecret = hs.suite.extract(nil, nil)\n}\nhs.handshakeSecret = hs.suite.extract(hs.sharedKey, \nhs.suite.deriveSecret(earlySecret, \"derived\", nil))","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和預共享密鑰有關,因本篇不涉及預共享密鑰,故","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nil","attrs":{}}],"attrs":{}},{"type":"text","text":"。此時,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"會通過加密套件派生出一個密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// extract implements HKDF-Extract with the cipher suite hash.\nfunc (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte {\n\tif newSecret == nil {\n\t\tnewSecret = make([]byte, c.hash.Size())\n\t}\n\treturn hkdf.Extract(c.hash.New, newSecret, currentSecret)\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HDKF","attrs":{}}],"attrs":{}},{"type":"text","text":"是一種基於哈希消息身份驗證的密鑰派生算法,其兩個主要用途分別爲:一、從較大的隨機源中提取更加均勻和隨機的密鑰;二、將已經合理的隨機輸入(例如共享密鑰)擴展爲更大的密碼獨立輸出,從而將共享密鑰派生出多個密鑰(以上爲筆者對維基百科中HKDF的理解)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite.deriveSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法筆者就不列出其源碼了,該方法最終會調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hkdf.Expand","attrs":{}}],"attrs":{}},{"type":"text","text":"方法進行密鑰派生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時再次回顧","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成正是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法基於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sharedKey","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"earlySecret","attrs":{}}],"attrs":{}},{"type":"text","text":"計算的結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後,通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和消息摘要派生出一組密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,\n\tclientHandshakeTrafficLabel, hs.transcript)\nc.in.setTrafficSecret(hs.suite, clientSecret)\nserverSecret := hs.suite.deriveSecret(hs.handshakeSecret,\n\tserverHandshakeTrafficLabel, hs.transcript)\nc.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHandshakeTrafficLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHandshakeTrafficLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"爲常量,其值分別爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c hs traffic","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s hs traffic","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.suite.deriveSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法會在內部調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript.Sum(nil)","attrs":{}}],"attrs":{}},{"type":"text","text":"計算出消息的摘要信息,所以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法基於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和兩個常量以及Server和Client已經發送的消息的摘要派生出的密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服務端用於對收到的數據進行解密,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服務端對要發送的數據進行加密。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.in","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.out","attrs":{}}],"attrs":{}},{"type":"text","text":"同其語義一樣,分別用於處理收到的數據和要發送的數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面看看筆者對","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setTrafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"方法的Debug結果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d7/d7599620f1308ba7ab3d087cdc79a6b8.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficKey","attrs":{}}],"attrs":{}},{"type":"text","text":"方法使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HKDF","attrs":{}}],"attrs":{}},{"type":"text","text":"算法對密鑰進行了再次派生,筆者就不再對其展開。這裏需要關注的是紅框部分,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"aes-gcm","attrs":{}}],"attrs":{}},{"type":"text","text":"是一種","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單純的對稱加密算法,其解密步驟是無法確認密鑰是否正確的。也就是說,加密後的數據可以用任何密鑰執行解密運算,得到一組疑似原始數據,然而並不知道密鑰是否是正確,也不知道解密出來的原始數據是否正確。因此,需要在單純的加密算法之上,加上一層驗證手段,來確認解密步驟是否正確,這就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server在握手階段的密鑰生成結束,此階段之後發送的消息(即","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ChangeCipherSpec","attrs":{}}],"attrs":{}},{"type":"text","text":"之後的消息),均通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"aes-gcm","attrs":{}}],"attrs":{}},{"type":"text","text":"算法加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後回顧一下加密套件的作用:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、提供消息摘要的Hash算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、提供加解密的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後再順便提一嘴,筆者Demo中parse.go文件的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"processMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"方法在處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"時有計算握手階段密鑰的極簡實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"支持的HTTP協議","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientHelloMsg.alpnProtocols","attrs":{}}],"attrs":{}},{"type":"text","text":"告知Server客戶端支持的HTTP協議,Server通過對比本地支持的HTTP協議,最終選擇雙端均支持的協議並構建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息告知Client","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"encryptedExtensions := new(encryptedExtensionsMsg)\nif len(hs.clientHello.alpnProtocols) > 0 {\n if selectedProto, fallback := mutualProtocol(hs.clientHello.alpnProtocols, c.config.NextProtos); !fallback {\n encryptedExtensions.alpnProtocol = selectedProto\n c.clientProtocol = selectedProto\n }\n}\nhs.transcript.Write(encryptedExtensions.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"hs.clientHello.alpnProtocols","attrs":{}}],"attrs":{}},{"type":"text","text":"的數據來源爲客戶端的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config.NextProtos","attrs":{}}],"attrs":{}},{"type":"text","text":"。在筆者的Demo中,Client和Server均支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"h2","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http1.1","attrs":{}}],"attrs":{}},{"type":"text","text":"這兩種協議。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏順便強調一下,Client或者Server在獲取到對方的helloMsg之後接受/發送的消息均會調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.transcript.Write","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,以便計算密鑰時可以快速計算消息摘要。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、本節討論了握手階段的密鑰生成流程:對消息摘要,然後用HKDF算法對共享密鑰和消息摘要派生密鑰,最後通過加密套件返回AEAD算法的實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、確認了加密套件的作用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、計算兩端均支持的HTTP協議。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"發送Server證書以及簽名","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此階段主要涉及三個消息,分別是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"僅在雙向認證時才發送給Client,單向認證時Server不發送此消息。這裏也再次印證了前面單向認證和雙向認證時序圖中Server發送的消息數量不一致的原因。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息的主體是Server的證書這個沒什麼好說的,下面着重分析一下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"私鑰簽名","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,構建","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"並設置其簽名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"certVerifyMsg := new(certificateVerifyMsg)\ncertVerifyMsg.hasSignatureAlgorithm = true // 沒有簽名算法無法簽名,所以直接寫true沒毛病\ncertVerifyMsg.signatureAlgorithm = hs.sigAlg","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.sigAlg","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"選擇合適的證書以及簽名算法","attrs":{}},{"type":"text","text":"小節選擇的簽名算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後,通過簽名算法計算簽名類型以及簽名hash,並構建簽名選項。以下爲筆者Debug結果:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/498ab0908e3c2978543cefe1a1ed4a2d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由上圖知,簽名類型爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signatureRSAPSS","attrs":{}}],"attrs":{}},{"type":"text","text":",簽名哈希算法爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SHA256","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signedMessage","attrs":{}}],"attrs":{}},{"type":"text","text":"的作用是將消息的摘要和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSignatureContext","attrs":{}}],"attrs":{}},{"type":"text","text":"(值爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TLS 1.3, server CertificateVerify\\x00","attrs":{}}],"attrs":{}},{"type":"text","text":")常量按照固定格式構建爲待簽名數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,計算簽名併發送消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"sig, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)\nif err != nil {\n // 省略代碼\n return errors.New(\"tls: failed to sign handshake: \" + err.Error())\n}\ncertVerifyMsg.signature = sig\nhs.transcript.Write(certVerifyMsg.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特別提醒,私鑰加密公鑰解密稱之爲簽名。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節主要介紹了此階段會發送的三種消息,以及Server簽名的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"發送finishedMsg並再次計算密鑰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"發送finishedMsg","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的內容非常簡單,僅一個字段:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished := &finishedMsg{\n verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"通過加密套件的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"計算得出,下面我們看看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"的內容:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {\n\tfinishedKey := c.expandLabel(baseKey, \"finished\", nil, c.hash.Size())\n\tverifyData := hmac.New(c.hash.New, finishedKey)\n\tverifyData.Write(transcript.Sum(nil))\n\treturn verifyData.Sum(nil)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"是一種利用密碼學中的散列函數來進行消息認證的一種機制,所能提供的消息認證包括兩方面內容(此內容摘自百度百科):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"消息完整性認證:能夠證明消息內容在傳送過程沒有被修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信源身份認證:因爲通信雙方共享了認證的密鑰,接收方能夠認證發送該數據的信源與所宣稱的一致,即能夠可靠地確認接收的消息與發送的一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.expandLabel","attrs":{}}],"attrs":{}},{"type":"text","text":"最種會調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hkdf.Expand","attrs":{}}],"attrs":{}},{"type":"text","text":"派生出新的密鑰。最後用新的密鑰以及消息摘要通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法計算出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"收到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"一方通過同樣的方式在本地計算出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData'","attrs":{}}],"attrs":{}},{"type":"text","text":",如果","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData'","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"相等,則證明此消息未被修改且來源可信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"再次計算密鑰","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本次計算密鑰的過程和前面計算密鑰的流程相似,所以直接上代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.masterSecret = hs.suite.extract(nil,\n\ths.suite.deriveSecret(hs.handshakeSecret, \"derived\", nil))\n\nhs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,\n\tclientApplicationTrafficLabel, hs.transcript)\nserverSecret := hs.suite.deriveSecret(hs.masterSecret,\n\tserverApplicationTrafficLabel, hs.transcript)\nc.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,利用前文已經生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"handshakeSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"\t再次派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":",然後再從","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":",最後調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.out.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}],"attrs":{}},{"type":"text","text":"計算出Server發送數據時的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,此時利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法會用於握手結束後對要發送的業務數據進行加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此階段結束後,Server會調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.flush()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,將前面提到的消息一次性發送給Client。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、本節介紹了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成過程,其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg.verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法計算得出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的作用是確保握手過程中發送的消息未被篡改,且數據來源可信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、計算Server發送業務數據時的加密密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Client讀消息&發送消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client讀到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之後會讀取服務端支持的TLS版本並和本地支持的版本做對比,前文已經提到過服務端支持的TLS版本是TLS1.3,因此Client也進入TLS1.3握手流程。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"讀取serverHelloMsg並計算密鑰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client進入TLS1.3握手流程後,有一系列的檢查邏輯,這些邏輯比較長而且筆者也不需要考慮這些異常,因此筆者化繁爲簡,在下面列出關鍵邏輯:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites,\n\ths.serverHello.cipherSuite) // 結合Server支持的加密套件選擇雙端均支持的加密套件\nhs.suite = selectedSuite\nhs.transcript = hs.suite.hash.New()\nhs.transcript.Write(hs.hello.marshal()) // hs.hello爲clientHelloMsg\nhs.transcript.Write(hs.serverHello.marshal())","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這一段代碼邏輯和Server處理加密套件以及通過加密套件構建消息摘要算法的實現邏輯相對應,因此筆者不再過多贅述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們看一下計算握手階段的密鑰以及","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"的生成:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"sharedKey := hs.ecdheParams.SharedKey(hs.serverHello.serverShare.data)\nearlySecret := hs.earlySecret\nif !hs.usingPSK {\n earlySecret = hs.suite.extract(nil, nil)\n}\nhandshakeSecret := hs.suite.extract(sharedKey,\n\ths.suite.deriveSecret(earlySecret, \"derived\", nil)) // 通過共享密鑰派生出handshakeSecret\n\nclientSecret := hs.suite.deriveSecret(handshakeSecret,\n\tclientHandshakeTrafficLabel, hs.transcript) // 通過handshakeSecret派生出clientSecret\nc.out.setTrafficSecret(hs.suite, clientSecret)\nserverSecret := hs.suite.deriveSecret(handshakeSecret,\n\tserverHandshakeTrafficLabel, hs.transcript) // 通過handshakeSecret派生出serverSecret\nc.in.setTrafficSecret(hs.suite, serverSecret)\nhs.masterSecret = hs.suite.extract(nil,\n\ths.suite.deriveSecret(handshakeSecret, \"derived\", nil)) // 通過handshakeSecret派生出masterSecret","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏需要提一嘴的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.ecdheParams","attrs":{}}],"attrs":{}},{"type":"text","text":",該值爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Client發送HelloMsg","attrs":{}},{"type":"text","text":"這一小節調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"generateECDHEParameters","attrs":{}}],"attrs":{}},{"type":"text","text":"函數生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"params","attrs":{}}],"attrs":{}},{"type":"text","text":"。其他邏輯和Server生成握手階段的密鑰保持一致,硬要說不同的話也就只有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"masterSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的階段不同。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"clientSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在客戶端用於對要發送的數據進行加密,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在客戶端對收到的數據進行解密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節梳理了客戶端處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverHelloMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"的邏輯和生成握手階段密鑰的邏輯。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"處理Server發送的參數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在客戶端需要處理的Server參數只有一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。而且處理邏輯也十分簡單:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"msg, err := c.readHandshake()\nencryptedExtensions, ok := msg.(*encryptedExtensionsMsg)\nhs.transcript.Write(encryptedExtensions.marshal())\nc.clientProtocol = encryptedExtensions.alpnProtocol","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果客戶端讀取到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"encryptedExtensionsMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,則直接將Server支持的HTTP協議賦值給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"c.clientProtocol","attrs":{}}],"attrs":{}},{"type":"text","text":"。在之後的HTTP請求中會根據TLS握手狀態以及服務端是否支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"h2","attrs":{}}],"attrs":{}},{"type":"text","text":"決定是否將本次請求升級爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http2","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"驗證證書和簽名","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本小節仍然繼續處理Server發送的消息,主要包含","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":",這三個消息均和證書相關。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateRequestMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,僅在雙向認證時,服務端才發送此消息。在本階段的處理邏輯也很簡單,讀取該消息並記錄。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"msg, err := c.readHandshake()\ncertReq, ok := msg.(*certificateRequestMsgTLS13)\nif ok {\n hs.transcript.Write(certReq.marshal())\n hs.certReq = certReq\n msg, err = c.readHandshake()\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"消息,該消息中主要包含證書信息,Client在獲取到證書信息後要校驗證書是否過期以及是否可信任。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if err := c.verifyServerCertificate(certMsg.certificate.Certificate); err != nil {\n return err\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"c.verifyServerCertificate","attrs":{}}],"attrs":{}},{"type":"text","text":"的內部邏輯如果各位讀者有興趣可以下載Demo調試一番,筆者在這裏就不對該方法做深入的展開和分析了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。前面在處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"時已經驗證了證書可信任或者Client可以忽略不受信任的證書,但是Client仍無法確信提供這個證書的服務器是否持有該證書,而驗證簽名的意義就在於確保該服務確實持有該證書。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Server發送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息時已經使用了證書對應的私鑰對需要簽名的數據進行簽名,客戶端利用證書的公鑰解密該簽名並和本地的待簽名數據做對比以確保服務端確實持有該證書。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// 根據簽名算法返回對應的算法類型和hash算法\nsigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)\nsigned := signedMessage(sigHash, serverSignatureContext, hs.transcript)\nif err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,\n\tsigHash, signed, certVerify.signature); err != nil {\n c.sendAlert(alertDecryptError)\n return errors.New(\"tls: invalid signature by the server certificate: \" + err.Error())\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"typeAndHashFromSignatureScheme","attrs":{}}],"attrs":{}},{"type":"text","text":"函數和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"signedMessage","attrs":{}}],"attrs":{}},{"type":"text","text":"函數在前文已經提到過,因此不再做重複敘述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"verifyHandshakeSignature","attrs":{}}],"attrs":{}},{"type":"text","text":"函數的內部實現涉及到非對稱加密算法的加解密,因筆者的知識有限,確實無法做更進一步的分析,在這裏給各位讀者道個歉~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":在這一小節簡單介紹了客戶端證書的驗證以及簽名的驗證。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"處理finishedMsg並再次計算密鑰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端對證書籤名驗證通過後,接下來還需要驗證消息的完整性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished, ok := msg.(*finishedMsg)\nexpectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)\nif !hmac.Equal(expectedMAC, finished.verifyData) {\n c.sendAlert(alertDecryptError)\n return errors.New(\"tls: invalid server finished hash\")\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"finishedHash","attrs":{}}],"attrs":{}},{"type":"text","text":"方法說明請參考","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"發送finishedMsg並再次計算密鑰","attrs":{}},{"type":"text","text":"這一小節。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只有當客戶端計算的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"expectedMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg.verifyData","attrs":{}}],"attrs":{}},{"type":"text","text":"一致時纔可繼續後續操作,即客戶端二次計算密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,\n\tclientApplicationTrafficLabel, hs.transcript)\nserverSecret := hs.suite.deriveSecret(hs.masterSecret,\n\tserverApplicationTrafficLabel, hs.transcript)\nc.in.setTrafficSecret(hs.suite, serverSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二次計算密鑰時分別派生出","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"兩個密鑰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,此時利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"serverSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法會用於握手結束後對收到的業務數據進行解密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,Server發送給客戶端的消息已經全部處理完畢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":本節主要介紹了客戶端通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HMAC","attrs":{}}],"attrs":{}},{"type":"text","text":"算法確保收到的消息未被篡改以及二次計算密鑰。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Client發送最後的消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端已經驗證了服務端消息的完整性,但是服務端還未驗證客戶端消息的完整性,因此客戶端還需要發送最後一次數據給服務端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先判斷是否需要發送證書給Server:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"if hs.certReq == nil {\n return nil\n}\ncertMsg := new(certificateMsgTLS13)\n// 此處省略代碼\ncertVerifyMsg := new(certificateVerifyMsg)\ncertVerifyMsg.hasSignatureAlgorithm = true\n// 此處省略代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"驗證證書和簽名","attrs":{}},{"type":"text","text":"這一小節的描述,如果服務端要求客戶端發送證書則","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.certReq","attrs":{}}],"attrs":{}},{"type":"text","text":"不爲nil。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"的主體也是證書,該證書的來源爲客戶端","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"tls.Config","attrs":{}}],"attrs":{}},{"type":"text","text":"配置的證書,在本例中客戶端配置證書邏輯如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"tlsConf.NextProtos = append(tlsConf.NextProtos, \"h2\", \"http/1.1\")\ntlsConf.Certificates = make([]tls.Certificate, 1)\nif len(certFile) > 0 && len(keyFile) > 0 {\n var err error\n tlsConf.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)\n if err != nil {\n return nil, err\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然要發送證書給服務端,那麼同服務端邏輯一樣也需要發送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"提供消息簽名的信息。客戶端簽名邏輯和服務端簽名邏輯一致,因此筆者不再贅述。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,客戶端需要發送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"給服務端:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"finished := &finishedMsg{\n verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),\n}\nhs.transcript.Write(finished.marshal())\nc.out.setTrafficSecret(hs.suite, hs.trafficSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在第二次計算密鑰時就已經被賦值,當","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"發送後,利用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"生成的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AEAD","attrs":{}}],"attrs":{}},{"type":"text","text":"加密算法會對客戶端要發送的業務數據進行加密。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,客戶端的握手流程全部完成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"小結","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、如果服務端要求客戶端發送證書,則客戶端會發送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、發送","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息並設置發送業務數據時的密鑰信息。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Server讀Client最後的消息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,服務端在TLS握手的最後階段,會先判斷是否要求客戶端發送證書,如果要求客戶端發送證書則處理客戶端發送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息。服務端處理","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateMsgTLS13","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"certificateVerifyMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"消息的邏輯和客戶端處理這兩個消息的邏輯類似。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,讀取客戶端發送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":", 並驗證消息的完整性,驗證邏輯和客戶端驗證","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"邏輯一致。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,設置服務端讀取業務數據時的加密信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"c.in.setTrafficSecret(hs.suite, hs.trafficSecret)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"hs.trafficSecret","attrs":{}}],"attrs":{}},{"type":"text","text":"在服務端第二次計算加密信息時就已經賦值,當讀完客戶端發送的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"finishedMsg","attrs":{}}],"attrs":{}},{"type":"text","text":"之後再執行此步驟是爲了避免無法解密客戶端發送的握手信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,服務端的握手流程全部完成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"握手完成之後","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成上述流程後,筆者還想試試看能不能從握手過程獲取的密鑰信息對業務數據進行解密。說幹就幹,下面是筆者在TLS握手完成之後用Client連接發送了一條消息的代碼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// main.go 握手完成之後,client發送了一條數據\nclient.Write([]byte(\"點贊關注:新世界雜貨鋪\"))","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是運行Demo後的輸出截圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/88/883a1a9dd9a6cad0ecf7517924963124.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖中紅色箭頭部分爲在Internet中真實傳輸的數據,藍色箭頭部分爲其解密結果。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一點感慨","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於TLS握手流程的文章筆者想寫很久了,現在總算得償所願。筆者不敢保證把TLS握手過程的每一個細節都描述清楚,所以如果中間有什麼問題還請各位讀者及時指出,大家相互學習。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫到這裏時筆者的內心也略有忐忑,畢竟這中間涉及了很多密碼學相關的知識,而在筆者各種瘋狂查資料期間發現國內具有權威性的文章還是太少。像","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ECDH","attrs":{}}],"attrs":{}},{"type":"text","text":"之類的關鍵詞在百度百科都沒有收錄,果然維基百科纔是爸爸呀。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後一點感概是關於Go中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"io.Reader ","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"io.Writer","attrs":{}}],"attrs":{}},{"type":"text","text":"這兩個接口的,不得不說這兩個接口的設計真的很簡單但是真的非常通用。筆者的Demo正是基於這兩個接口實現,否則筆者的心願很難完成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"挖坑","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上一篇文章中,筆者給了一條彩蛋——“下一期TLS/SSL握手流程敬請期待”。哇,這可真的是自己坑自己了,本篇文章未完成之前,筆者愣是斷更了也沒敢發別的文章。果然自己作的死,哭着也要作完。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了前車之鑑,筆者決定以後不再放彩蛋,而是挖坑(填坑時間待定😊):本篇中主要介紹了TLS1.3的握手流程,那麼TLS1.2也快了~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,衷心希望本文能夠對各位讀者有一定的幫助。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 寫本文時, 筆者所用go版本爲: go1.15.2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/http2/tls/main.go","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章