TLS源碼解析-golang


概述

以golang 1.8.1版本爲準。
源碼目錄:src/crypto/tls

RECORD協議

record類型

const (
   recordTypeChangeCipherSpec recordType = 20  // changecipherspec表明發送端已取得用以生成連接參數的足夠信息。內容隨密碼套件不同
   recordTypeAlert            recordType = 21  // alert協議類型
   recordTypeHandshake        recordType = 22  // 握手協議類型
   recordTypeApplicationData  recordType = 23  // 應用數據協議類型
)

 

record head長5個字節

|--record type--|--version--|--data length--|


record type: 1個字節

version:        2個字節

data length:  2個字節  最大值16KB

一條record的長度是可變的, 參考: https://blog.csdn.net/liujiyong7/article/details/65632819


record是TLS 加解密的最小單元,至少要收到一個完整的record,才能加解密。

record 協議會執行以下動作

1. 分片,將應用層的數據進行分片

2. 生成序列號,防重放

3. 壓縮, 可選,握手協議會進行協商,但是不建議壓縮

4. 算HMAC, 計算數據HMAC,防止篡改,僞造

5. 發給tcp/ip,    下層協議,也可以是udp

HANDSHAKE協議

第一次Read或Write方法會自動調用Handshake()方法

完整的握手

 

ClientHello

type clientHelloMsg struct {
   raw                          []byte      // 原始數據
   vers                         uint16      // 協議版本,指示客戶端支持的最大協議版本
   random                       []byte      // 隨機數 32字節
   sessionId                    []byte      // 會話ID,第一次爲空,服務端藉助會話ID恢復會話,需要服務器端緩存。
   cipherSuites                 []uint16    // 客戶端支持的加密套件列表
   compressionMethods           []uint8     // 客戶端支持的壓縮方法,默認爲null
   nextProtoNeg                 bool        // 擴展NPN 是否支持次協議協商
   serverName                   string      // 擴展SNI 服務器名稱,通常爲域名,默認爲目標地址主機名。支持SNI擴展需要的字段。
   ocspStapling                 bool        // 擴展status_request 是否支持ocsp staping。全稱在線證書狀態檢查協議 (rfc6960),用來向 CA 站點查詢證書狀態
   scts                         bool        // 擴展SCT。是否支持SCT
   supportedCurves              []CurveID   // 擴展ellipic curve 列出支持的橢圓曲線名稱  https://tools.ietf.org/html/rfc4492#section-5.5.1
   supportedPoints              []uint8     // 擴展Point Formats 對橢圓曲線頂點進行可選壓縮  https://tools.ietf.org/html/rfc4492#section-5.5.2 默認不壓縮
   ticketSupported              bool        // 擴展Sessionticket.是否支持會話ticket
   sessionTicket                []uint8     // 擴展Sessionticket 會話ticket,區別於sessionId的新的會話恢復機制,這種機制不需要服務器端緩存。
   signatureAndHashes           []signatureAndHash  // 擴展SignatureAlgorithms 簽名和散列算法
   secureRenegotiation          []byte      // 擴展RenegotiationInfo 如果請求重新協商,就會發起一次新的握手。
   secureRenegotiationSupported bool        // 擴展RenegotiationInfo 是否支持renegotiation_info擴展 安全重新協商
   alpnProtocols                []string    // 擴展ALPN 應用層協議協商。
}

常用的擴展

支持ECC需要兩個擴展 ellipic curve,point formats

 

SNI擴展用來實現安全虛擬主機。單個服務器可能配有多個證書,服務端使用SNI來區分請求使用的是哪個證書。

 

SPDY使用NPN擴展協商使用何種應用層協議

HTTP2的協議協商過程使用ALPN擴展

ALPN是由客戶端給服務器發送一個協議清單,由服務器來選擇一個。NPN正好相反

加密套件

使用openssl ciphers -V | column -t 查看本機支持的cipher suite列表。如下所示:

0xC0,0x30  -  ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH        Au=RSA    Enc=AESGCM(256)    Mac=AEAD
0xC0,0x2C  -  ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH        Au=ECDSA  Enc=AESGCM(256)    Mac=AEAD
0xC0,0x28  -  ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH        Au=RSA    Enc=AES(256)       Mac=SHA384
0xC0,0x24  -  ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH        Au=ECDSA  Enc=AES(256)       Mac=SHA384

Kx表示密鑰交換算法 Au表示認證算法 Enc表示加密算法 Mac表示消息認證碼算法

每一種cipher suite由一個uint16整數標示

golang的默認cipher suites (如果監測到AES-GSM硬件,會優先提供aes-gcm的加密套件)

topCipherSuites = []uint16{
   TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
   TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
   TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}


EC參數填充

supportedCurves的值默認填充以下參數:
var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521} // 23 24 25 29

https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8 從中可查到是secp256r1 secp384r1 secp521r1  x25519

 

supportedPoints 默認爲0 不壓縮

ServerHello

消息的意義是將服務器選擇的參數傳回客戶端,結構與clientHello相似,每個字段只包含一個選項

type serverHelloMsg struct {
   raw                          []byte      // 原始數據
   vers                         uint16      // 服務端選擇的版本號 通常爲client server都支持的版本中最高的。 common.go:mutualVersion()
   random                       []byte      // 服務端生成的隨機數  32字節
   sessionId                    []byte      //
   cipherSuite                  uint16      // 服務端選擇的加密套件  通常爲client server都支持的套件中,最靠前的。所以套件的順序是有講究的。
   compressionMethod            uint8       // 選擇的壓縮方法 只會選擇不壓縮。如果客戶端不支持不壓縮,會報錯
   nextProtoNeg                 bool        // client支持,則server支持
   nextProtos                   []string    // 服務端支持的應用層協議
   ocspStapling                 bool        //
   scts                         [][]byte    // 簽名的證書時間戳?
   ticketSupported              bool        //
   secureRenegotiation          []byte      //
   secureRenegotiationSupported bool        // 與client一致
   alpnProtocol                 string      // 服務端選擇的應用層協議,client server都支持的協議中,最靠前的。如果client端爲空,則填充爲服務器支持的協議
}

 

根據ClientHello,填充ServerHello消息,主要的代碼在 handshake_server.go:readClientHello()

EC參數填充

preferredCurves := c.config.curvePreferences()
for _, curve := range hs.clientHello.supportedCurves {
   for _, supported := range preferredCurves { // 默認X25519, CurveP256, CurveP384, CurveP521,
      if supported == curve {
         supportedCurve = true
         break Curves
      }
   }
}
......
 
var preferenceList, supportedList []uint16
// c.config.PreferServerCipherSuites本字段控制服務端是選擇客戶端最期望的密碼組合還是服務端最期望的密碼組合。
// 如果本字段爲真,服務端會優先選擇CipherSuites字段中靠前的密碼組合使用。
if c.config.PreferServerCipherSuites {
   preferenceList = c.config.cipherSuites()
   supportedList = hs.clientHello.cipherSuites
else {
   preferenceList = hs.clientHello.cipherSuites
   supportedList = c.config.cipherSuites()
}
 
for _, id := range preferenceList {
   if hs.setCipherSuite(id, supportedList, c.vers) {//選擇一個優先的ciphersuite
      break
   }
}
 

 

Certificate

可選。攜帶X.509證書鏈,證書鏈以ASN.1 DSR編碼的一系列證書。主證書必須第一個發送,中間證書按照正確的順序跟在後面,根證書需省略。

證書需與選擇的算法套件一致。

type certificateMsg struct {
   raw          []byte
   certificates [][]byte
}

ServerKeyExchange

攜帶密鑰交換的額外數據,消息內容對不同的協商算法套件存在差異。某些場景下不需要發送

type serverKeyExchangeMsg struct {
   raw []byte                     // 對於ECDHE, 填充serverECDHParams
   key []byte
}

 

 

func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
 
    if ka.curveid == X25519 {                         // 選擇的橢圓曲線是X25519
        var scalar, public [32]byte
        if _, err := io.ReadFull(config.rand(), scalar[:]); err != nil {
            return nil, err
        }
 
        curve25519.ScalarBaseMult(&public, &scalar)   // 生成公鑰 私鑰
        ka.privateKey = scalar[:]                     // 私鑰自己保存
        ecdhePublic = public[:]                       // 公鑰填充進ECDHParams參數
    else {           // 選擇其他的橢圓曲線
        curve, ok := curveForCurveID(ka.curveid)
        if !ok {
            return nil, errors.New("tls: preferredCurves includes unsupported curve")
        }
 
        var x, y *big.Int
        var err error
        ka.privateKey, x, y, err = elliptic.GenerateKey(curve, config.rand())  // 生成私鑰 公鑰對
        if err != nil {
            return nil, err
        }
        ecdhePublic = elliptic.Marshal(curve, x, y)
    }
 
    serverECDHParams := make([]byte, 1+2+1+len(ecdhePublic))   // 計算長度,分配內存
    serverECDHParams[0] = 3 // named curve                     // 選擇的橢圓曲線
    serverECDHParams[1] = byte(ka.curveid >> 8)                // 保存橢圓曲線高8位
    serverECDHParams[2] = byte(ka.curveid)                     // 保存橢圓曲線低8位
    serverECDHParams[3] = byte(len(ecdhePublic))               // 公鑰 ecdhePublic 長度
    copy(serverECDHParams[4:], ecdhePublic)                    // 公鑰
...
    skx := new(serverKeyExchangeMsg)
    sigAndHashLen := 0
    if ka.version >= VersionTLS12 {
        sigAndHashLen = 2
    }
    skx.key = make([]byte, len(serverECDHParams)+sigAndHashLen+2+len(sig))
    copy(skx.key, serverECDHParams)
    k := skx.key[len(serverECDHParams):]
    if ka.version >= VersionTLS12 {
       k[0] = sigAndHash.hash                                  // hash
      k[1] = sigAndHash.signature                              // 簽名
     k = k[2:]
    }
    k[0] = byte(len(sig) >> 8)                                 // 簽名長度高8位
    k[1] = byte(len(sig))                                      // 簽名長度低8位
    copy(k[2:], sig)                                           // 簽名
}

 

ServerHelloDone

表明服務器已經將所有握手消息發送完畢

type serverHelloDoneMsg struct{}


ClientKeyExchange

攜帶客戶端爲密鑰交換提供的所有信息。內容隨不同的密碼套件會不同。

type clientKeyExchangeMsg struct {
   raw        []byte
   ciphertext []byte
}

Finished

type finishedMsg struct {
   raw        []byte
   verifyData []byte
}

 

客戶端身份驗證

CertificateRequest

服務器使用本消息對客戶端進行身份認證

type certificateRequestMsg struct {
   raw []byte
   // hasSignatureAndHash indicates whether this message includes a list
   // of signature and hash functions. This change was introduced with TLS
   // 1.2.
   hasSignatureAndHash bool
 
   certificateTypes       []byte
   signatureAndHashes     []signatureAndHash
   certificateAuthorities [][]byte
}

 

CertificateVerify

客戶端使用本消息證明自己的私鑰與之前發送的客戶端證書中的公鑰對應

type certificateVerifyMsg struct {
   raw                 []byte
   hasSignatureAndHash bool
   signatureAndHash    signatureAndHash
   signature           []byte
}

 

密鑰交換

ECDHE

Elliptic curve Diffie-Hellman 橢圓曲線密鑰交換,如名稱所示,基於橢圓曲線加密。

數學原理

安全性建立在離散對數計算很困難的基礎上

Diffie-Hellman

兩個基本概念:

  • 本原根:如果整數 a 是素數 p 的本原根,則 a, a^2, …, a^(p-1) 在 mod p 下都不相同。
  • 離散對數:對任意整數 b 和素數 p 的本原根 a,存在唯一的指數 i 滿足: b ≡ a^i mod p (0≤i≤p-1)

則稱 i 是 b 的以 a 爲底的模 p 的離散對數

a,p是公開的,已知i,求b比較容易。已知b,求i非常困難

 

示例:

假設 client 和 server 需要協商密鑰,p=2579,則本原根 a = 2。

1,  Client 選擇隨機數 Kc = 123 做爲自己的私鑰,計算 Yc = a^Kc  mod p = 2^123 mod 2579 = 2400,把 Yc 作爲公鑰發送給 server。

2,  Server 選擇隨機數 Ks = 293 作爲私鑰,計算 Ys = a^Ks  mod p = s^293 mod 2579 = 968,把 Ys 作爲公鑰發送給 client。

3,  Client 計算共享密鑰:secrect =  Ys^Kc mod (p) = 968^123  mod(2579) = 434

4,  Server 計算共享密鑰:secrect = Yc^Ks mod(p) =2400^293 mod(2579) =434

上述公式中的 Ys,Yc,P, a, 都是公開信息,可以被中間者查看,只有 Ks,Kc 作爲私鑰沒有公開,當私鑰較小時,通過窮舉攻擊能夠計算出共享密鑰,但是當私鑰非常大時,窮舉攻擊肯定是不可行的。

 

ECC

http://www.8btc.com/introduction

橢圓曲線簡化定義爲 y^2 = x^3+ax+b 

觀察以上橢圓曲線圖中的四個點,定義一個阿貝爾羣

• 羣中的元素是一條橢圓曲線上的點;

• 單位元爲無窮遠點O;

• 點P的逆元是其關於x-軸的對稱點;

• 加法,滿足以下規則: 對於3個處在同一直線上的非零點 P, Q 和 R, 它們的和 P + Q + R = 0.

 

 

考慮上圖,在切線的情況下, P+Q+R =0,  切點P=Q, 所以P+P=-R

定義乘法

nP = P+P+P...+P n個P相加

簡寫爲Q=nP

已知n,P求解Q,比較容易

已知Q,P,求解n,非常困難

 

加密使用的橢圓曲線公式 y^2 mod p = (x^3+ax +b) mod p  

1.挑選基點 G = (x, y),G 的階爲 n。n 爲滿足 nG = 0 的最小正整數。

2.Client 選擇私鑰 Kc (0 <Kc<n ),產生公鑰 Yc =Kc *G

3.server 選擇私鑰 Ks 併產生公鑰 Ys =Ks*G

4.client 計算共享密鑰 K = Kc*Ys   ,server 端計算共享密鑰 Ks*Yc ,這兩者的結果是一樣的

Kc*Ys = Kc*(Ks*G) = Ks*(Kc*G) = Ks*Yc


協商過程

tls利用兩個擴展elliptic_curves, point_formats 實現EC功能

elliptic_curves在clientHello消息中列出支持的橢圓曲線名稱

 

1,  瀏覽器發送 client_hello,包含一個隨機數 random1,同時需要有 2 個擴展:

a)      Elliptic_curves:客戶端支持的曲線類型和有限域參數。現在使用最多的是 256 位的素數域,參數定義如上節所述。

b)      Ec_point_formats:支持的曲線點格式,默認都是 uncompressed。

2,  服務端回覆 server_hello,包含一個隨機數 random2 及 ECC 擴展。

3,  服務端回覆 certificate,攜帶了證書公鑰。

4,  服務端生成 ECDH 臨時公鑰,同時回覆 server_key_exchange,包含三部分重要內容:

a)       ECC 相關的參數。

b)       ECDH 臨時公鑰。

c)       ECC 參數和公鑰生成的簽名值,用於客戶端校驗。

5,  瀏覽器接收 server_key_exchange 之後,使用證書公鑰進行簽名解密和校驗,獲取服務器端的 ECDH 臨時公鑰,生成會話所需要的共享密鑰。

至此,瀏覽器端完成了密鑰協商。

6,  瀏覽器生成 ECDH 臨時公鑰和 client_key_exchange 消息,跟 RSA 密鑰協商不同的是,這個消息不需要加密了。

7,  服務器處理 client_key_exchang 消息,獲取客戶端 ECDH 臨時公鑰。

8,  服務器生成會話所需要的共享密鑰。

9,  Server 端密鑰協商過程結束。

 

GOLANG實現

func (hs *serverHandshakeState) establishKeys() error {
   c := hs.c
   // 生成密鑰協商需要的密鑰,這6個值,在加密過程中會使用
   clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
      keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen)
 
   var clientCipher, serverCipher interface{}
   var clientHash, serverHash macFunction
 
   if hs.suite.aead == nil {
      clientCipher = hs.suite.cipher(clientKey, clientIV, true /* for reading */)
      clientHash = hs.suite.mac(c.vers, clientMAC)
      serverCipher = hs.suite.cipher(serverKey, serverIV, false /* not for reading */)
      serverHash = hs.suite.mac(c.vers, serverMAC)
   else {
      clientCipher = hs.suite.aead(clientKey, clientIV)
      serverCipher = hs.suite.aead(serverKey, serverIV)
   }
 
   c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
   c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
 
   return nil
}

c.vers                                                     tls version  handshake_server.go readClientHello() 142 協商最大的版本號

hs.suite                                                  加密套件 handshake_server.go readClientHello() 265

hs.masterSecret              主密鑰  handshake_server.go doFullHandshake 498 
hs.clientHello.random        clientHello傳進來的隨機數
hs.hello.random              serverHello 隨機數
hs.suite.macLen              echde_ecdsa_aes128_gcm_sha256 0     macLen keyLen ivLen都在cipher_suite.go中定義
hs.suite.keyLen              echde_ecdsa_aes128_gcm_sha256 16
hs.suite.ivLen               echde_ecdsa_aes128_gcm_sha256 4

 

ALERT協議

以簡單的通知機制告知對端通信出現異常。通常會攜帶close_notify異常,在連接關閉時使用。

 

需要關閉從client往server發送ALERT,以避免server端過多timeout

 

加密

AES128-GCM-SHA256

分組加密

type serverHandshakeState struct {
   c                     *Conn                 // tls連接
   clientHello           *clientHelloMsg       // ClientHello消息
   hello                 *serverHelloMsg       // ServeHello 消息
   suite                 *cipherSuite          // 加密套件
   ellipticOk            bool                  //
   ecdsaOk               bool                  //
   rsaDecryptOk          bool                  //
   rsaSignOk             bool                  //
   sessionState          *sessionState         //
   finishedHash          finishedHash          //
   masterSecret          []byte                // 主密鑰
   certsFromClient       [][]byte              // 客戶端證書
   cert                  *Certificate          // 證書
   cachedClientHelloInfo *ClientHelloInfo      //
}

 

 

chacha20-poly1305

流加密








參考資料

1.https://github.com/WeMobileDev/article 

2.https://blog.helong.info/blog/2015/09/07/tls-protocol-analysis-and-crypto-protocol-design/

3.http://www.vuln.cn/6521 TLS擴展

4.http://blog.csdn.net/u010129119/article/details/54090814 tls 1.3規範

5.https://tlswg.github.io/tls13-spec/draft-ietf-tls-tls13.html  tls 1.3最新草案

7.https://tools.ietf.org/html/rfc5246   tls 1.2 rfc

8.http://blog.csdn.net/t0mato_/article/details/53160772 AES-GCM

9.http://blog.csdn.net/fw0124/article/details/8472560 分組加密的四種模式

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章