golang与TLS实现
来源:互联网 发布:java初级工资待遇 编辑:程序博客网 时间:2024/05/16 17:04
golang与TLS实现
在最近的项目中,需要对对方服务器的证书状态进行检查,获取证书上,就需要进行TLS握手,获取到证书信息,在项目中但是使用直接拼出ClientHello包的方式进行TLS握手操作,今天看一些go中的源码中是如何进行TLS握手的。
首先从建立连接开始:tls.DialWithDialer(dialer *net.Dialer,network,addr string ,config *tls.Config)
,该方法在cryto/tls
的tls.go
文件中。
ClientHello
先上代码
//去除了一些个人认为不是很重要的代码,只留下和tls相关的代码rawConn, err := dialer.Dial(network, addr) if err != nil { return nil, err } colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { colonPos = len(addr) } hostname := addr[:colonPos] if config == nil { config = defaultConfig() } // If no ServerName is set, infer the ServerName // from the hostname we're connecting to. if config.ServerName == "" { // Make a copy to avoid polluting argument or default. c := config.clone() c.ServerName = hostname config = c } conn := Client(rawConn, config) if timeout == 0 { err = conn.Handshake() } else { go func() { errChannel <- conn.Handshake() }() err = <-errChannel }
从代码中可以看到,调用了dialer的拨号方法,得到net
包下的Conn
结构,然后通过Client(conn net.Conn, config *Config)
,封装出一个tls
包下的Conn
结构。在进行TLS连接时,因为现在有很多的公司使用了SNI,因此,在进行tls连接时要指定连接的服务器名称。在tls
的源码中,对config
中是否添加了ServerName
进行了判断,如果没有填写,就使传入的addr
中取出服务器地址。接下来就是进行握手(conn.Handshake
)
func (c *Conn) Handshake() error
//一些锁操作省却 if c.isClient { c.handshakeErr = c.clientHandshake() } else { c.handshakeErr = c.serverHandshake() } if c.handshakeErr == nil { c.handshakes++ }
现在阶段是Client
向Server
发送Hello
信息。因此我点击c.clientHandshake
到这中一探究竟。
在clientHandshake() err方法中
,进行了ClientHello
的信息的生成。首先是判断是否有tls.Config
hello := &clientHelloMsg{ vers: c.config.maxVersion(), compressionMethods: []uint8{compressionNone}, random: make([]byte, 32), ocspStapling: true, scts: true, serverName: hostnameInSNI(c.config.ServerName), supportedCurves: c.config.curvePreferences(), supportedPoints: []uint8{pointFormatUncompressed}, nextProtoNeg: len(c.config.NextProtos) > 0, secureRenegotiationSupported: true, alpnProtocols: c.config.NextProtos, }
hello
是需要发送的clientHello
信息,但是在上面的hello
信息中缺少了使用的套件的信息,在套件的选择上也很有意思:
NextCipherSuite: for _, suiteId := range possibleCipherSuites { for _, suite := range cipherSuites { if suite.id != suiteId { continue } // Don't advertise TLS 1.2-only cipher suites unless // we're attempting TLS 1.2. if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 { continue } hello.cipherSuites = append(hello.cipherSuites, suiteId) continue NextCipherSuite } }
其中possibleCipherSuites
是用户在tls.Config
中设置的CipherSuites
,在上面的代码中使用到了cipherSuites
,cipherSuites
是go中内置的一些加密套件 :
var cipherSuites = []*cipherSuite{ // Ciphersuite order is chosen so that ECDHE comes before plain RSA // and RC4 comes before AES-CBC (because of the Lucky13 attack). {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, {TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE | suiteDefaultOff, cipherRC4, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteDefaultOff, cipherRC4, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM}, {TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, suiteDefaultOff, cipherRC4, macSHA1, nil}, {TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil}, {TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, 0, cipher3DES, macSHA1, nil},}
在上面的循环中,会对用户在config
中所填写的加密套件进行筛选,首先会把不是上面所列举的9的加密套件去除,然后根据用户在config
中使用的最大版本进行筛选套件,筛选条件:如果不使用TSLv1.2,那么将TLS1.2才支持的加密套件移除。然后对ClientHello
中的随机数进行填充。随后是一些对Session的填写,当所有的应该填充的数据后,使用writeRecord()
发送ClientHello
信息。
获取ServerHello信息
在发送完ClientHello
信息后使用c.readHandshake()
,获取从服务器过来的ServerHello
信息。然后是使用类型强转serverHello, ok := msg.(*serverHelloMsg)
判断得到的信息是否是ServerHello
类型的数据。如果不是ServerHello
则发送Alert
终止这次TLS握手。
然后根据SeverHello
中选择的TLS版本和ClientHello
中的版本范围进行校验。看服务器发送过来的TLS版本是否在ClientHello
指定的范围中。但是如果ServerHello
和ClientHello
两方商量出来的TLS版本小于TLSv1.0,客户端就发送Alert
终止当前握手。换句话说,使用go进行对服务器访问,如果服务器只支持SSL2、SSL3,该访问将无法完成。(虽然这种情况不常见)。
vers, ok := c.config.mutualVersion(serverHello.vers) if !ok || vers < VersionTLS10 { // TLS 1.0 is the minimum version supported as a client. c.sendAlert(alertProtocolVersion) return fmt.Errorf("tls: server selected unsupported protocol version %x", serverHello.vers) }
在确定了使用哪个协议之后,就要确定使用哪个加密套件了。suite := mutualCipherSuite(hello.cipherSuites, serverHello.cipherSuite)
suite := mutualCipherSuite(hello.cipherSuites, serverHello.cipherSuite)if suite == nil { c.sendAlert(alertHandshakeFailure) return errors.New("tls: server chose an unconfigured cipher suite")}
如果没有合适的加密套件,也会发送Alert()
终止这次握手。mutualCipherSuite()
就是在ClientHello
中去查找是否有ServerHello
发送过来的套件:
func mutualCipherSuite(have []uint16, want uint16) *cipherSuite { for _, id := range have { if id == want { for _, suite := range cipherSuites { if suite.id == want { return suite } } return nil } } return nil}
这些验证完成后,生成握手信息。用于客户端的秘钥交换。根据是否商谈握手,需要做不同的查找。
if isResume || len(c.config.Certificates) == 0 { hs.finishedHash.discardHandshakeBuffer() } hs.finishedHash.Write(hs.hello.marshal()) hs.finishedHash.Write(hs.serverHello.marshal()) c.buffering = true if isResume { if err := hs.establishKeys(); err != nil { return err } if err := hs.readSessionTicket(); err != nil { return err } if err := hs.readFinished(c.serverFinished[:]); err != nil { return err } c.clientFinishedIsFirst = false if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err } if _, err := c.flush(); err != nil { return err } } else { if err := hs.doFullHandshake(); err != nil { return err } if err := hs.establishKeys(); err != nil { return err } if err := hs.sendFinished(c.clientFinished[:]); err != nil { return err } if _, err := c.flush(); err != nil { return err } c.clientFinishedIsFirst = true if err := hs.readSessionTicket(); err != nil { return err } if err := hs.readFinished(c.serverFinished[:]); err != nil { return err } } if sessionCache != nil && hs.session != nil && session != hs.session { sessionCache.Put(cacheKey, hs.session) } c.didResume = isResume c.handshakeComplete = true c.cipherSuite = suite.id return nil}
如果不是商谈握手。进行区别对待。应为如果是商谈握手,那么之前已经完成了一次完整的握手状态,因此不需要重新做完成的握手,否则需要完成完整的握手即:doFullHandshake()
:
doFullHandshake
CertificateVerify
在doFullHandshake
中完成了ClientKeyExchange
、CertificateVerify
、ChangeCipherSpec
等操作。
在源码中,如果没有拿到证书信息吗,也会Alert()
终止这次握手。并且,如果是第一次握手,将去对证书进行验证的有效性进行验证:
if c.handshakes == 0 { // If this is the first handshake on a connection, process and // (optionally) verify the server's certificates. certs := make([]*x509.Certificate, len(certMsg.certificates)) for i, asn1Data := range certMsg.certificates { cert, err := x509.ParseCertificate(asn1Data) if err != nil { c.sendAlert(alertBadCertificate) return errors.New("tls: failed to parse certificate from server: " + err.Error()) } certs[i] = cert } if !c.config.InsecureSkipVerify { opts := x509.VerifyOptions{ Roots: c.config.RootCAs, CurrentTime: c.config.time(), DNSName: c.config.ServerName, Intermediates: x509.NewCertPool(), } for i, cert := range certs { if i == 0 { continue } opts.Intermediates.AddCert(cert) } c.verifiedChains, err = certs[0].Verify(opts) if err != nil { c.sendAlert(alertBadCertificate) return err } } switch certs[0].PublicKey.(type) { case *rsa.PublicKey, *ecdsa.PublicKey: break default: c.sendAlert(alertUnsupportedCertificate) return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", certs[0].PublicKey) } c.peerCertificates = certs } else { // This is a renegotiation handshake. We require that the // server's identity (i.e. leaf certificate) is unchanged and // thus any previous trust decision is still valid. // // See https://mitls.org/pages/attacks/3SHAKE for the // motivation behind this requirement. if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) { c.sendAlert(alertBadCertificate) return errors.New("tls: server's identity changed during renegotiation") } }
先看不是第一次握手,根据注释说明,只要验证服务器的叶子证书没有改变就可以了。如果是第一次握手,拿到证书并且能够生成go中的证书结构。(但是,有些证书是无法解析成go
中的证书结构,但使用Openssl可以展示出证书结构)。如证书无法解析同样的发送Alert
终止握手。如果用户在生成tls/Config
对象时没有将InsecureSkipVerify
设置为true时,将使用在Config
中设置的RootCAs
,并且把从服务器传过来的非叶子证书,添加到中间证书的池中,使用设置的根证书和中间证书对叶子证书进行验证。如果没有通过验证也发送Alert
终止握手。当验证通过后,获取证书的公钥算法,go
只能解析RSA
和ECDSA
类型的公钥证书。
OCSPStapling
如果服务器提供ocspStapling信息,在doFullHandshake
中也将对ocspstaping信息验证。如没有获取到OCSP信息那么也会发送Alert
终止握手。
ServerKeyExchangeMsg
获取服务器端的秘钥交换信息:
skx, ok := msg.(*serverKeyExchangeMsg) if ok { hs.finishedHash.Write(skx.marshal()) err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx) if err != nil { c.sendAlert(alertUnexpectedMessage) return err } msg, err = c.readHandshake() if err != nil { return err } }
在该阶段只是简单处理ServerKeyExchangeMsg
,对双方发送的数据进行验证。如果验证不过就发送Alert
终止握手。
CertificateRequestMsg
这种请求在我们进行平常的网页浏览的时候是不会出现的,但是在进行一些金融交易的时候,有些人需要使用银行随卡一起发放的U盾,在U盾上进行确认,其实在U盾中有一张个人的数字证书,银行服务器需要校验这张证书上的内容,以便完成交易。
ServerHelloDone
当接收到ServerHelloDone时表示握手协商已经完成,以后的数据将全部进行加密处理。
结语
整个Client端的半握手,基本就是这样了,但是,在上面的讲解过程中,对发送完ClientHello
后,Client发送的ClientKeyExchagne
的数据结构构成,不是很清除,因此有很多的hs.finishedHash.Write(shd.marshal())
这类的写数据操作的作用没有将讲清楚。虽然现在对TLS握手的过程有了一定的了解,但是还是要对TLS中发送的每一个数据包的组成需要进行了解。
- golang与TLS实现
- golang tls 似乎例子
- Golang tls 链接通信
- Windows与Linux下TLS实现
- golang简单实现一个基于TLS/SSL的 TCP服务器和客户端
- Golang的TLS通信,证书文件使用.
- golang中sort包实现与应用
- TLS协议分析 (八) 实现与开源项目
- TLS/SSL Socket 实现
- Linux & Windows TLS实现
- GLibc TLS实现
- TLS-PRF实现示例
- Netty Tls实现
- SSL与TLS
- SSL与TLS
- SSL与TLS
- SSL与TLS
- SSL与TLS
- docker之mysql镜像使用
- Netty基础知识
- git入门
- android bringToFront()
- working copy is not up-to-date:SVN
- golang与TLS实现
- onFinishInflate()
- Java 实现简单登陆案例
- google-android-mvp例子简析
- GoPdf的简单使用
- android View
- 京东首页之页面顶部、Logo&搜索框
- go测试用例该如何写
- C#获取本机局域网ip和公网ip