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 分组加密的四种模式

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值