美文网首页
Hyperledger-Fabric源码分析(TLS)

Hyperledger-Fabric源码分析(TLS)

作者: Pillar_Zhong | 来源:发表于2019-04-09 22:53 被阅读0次

    前面我们的证书生成的篇章中,只是提了下tls相关的内容,但他们是怎么用的并没有涉及,这一篇我们来探讨下Fabric中tls是怎么运转的。

    首先我们来看下SecureOptions,这里包含了不管是server还是client所有涉及的配置项,搞懂这些选项的来龙去脉,基本上你也就搞懂了tls。

    SecureOptions

    type SecureOptions struct {
        // VerifyCertificate, if not nil, is called after normal
        // certificate verification by either a TLS client or server.
        // If it returns a non-nil error, the handshake is aborted and that error results.
        VerifyCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
        // PEM-encoded X509 public key to be used for TLS communication
        Certificate []byte
        // PEM-encoded private key to be used for TLS communication
        Key []byte
        // Set of PEM-encoded X509 certificate authorities used by clients to
        // verify server certificates
        ServerRootCAs [][]byte
        // Set of PEM-encoded X509 certificate authorities used by servers to
        // verify client certificates
        ClientRootCAs [][]byte
        // Whether or not to use TLS for communication
        UseTLS bool
        // Whether or not TLS client must present certificates for authentication
        RequireClientCert bool
        // CipherSuites is a list of supported cipher suites for TLS
        CipherSuites []uint16
    }
    
    • VerifyCertificate -- 回调方法,用来在tls握手期间,正常的证书校验完毕后,回调这部分自定义校验逻辑。
    • Certificate -- 这里是存放tls所要使用的证书,不论是server还是client
    • Key -- 私钥
    • ServerRootCAs -- 客户端用来校验服务端证书
    • ClientRootCAs -- 服务端用来校验客户端证书
    • UseTLS -- 是否开启tls
    • RequireClientCert -- 是否需要客户端证书
    • CipherSuites -- 加密套件

    VerifyCertificate

    v := c.verifyHandshake(endpoint, expectedServerCert)
    conn, err := c.dialer.Dial(endpoint, v)
    
    func (c *ConnectionStore) verifyHandshake(endpoint string, certificate []byte) RemoteVerifier {
        return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
            if bytes.Equal(certificate, rawCerts[0]) {
                return nil
            }
            return errors.Errorf("certificate presented by %s doesn't match any authorized certificate", endpoint)
        }
    }
    
    • 可以看到,这里的意思是,连接的过程中,连接方希望被连接方的证书跟我所期待的完全一致。可以说这是非常严格的证书校验逻辑。整个fabric有用到这个的地方就是etcd的部分,因为etcd的网络层是自己实现的,在一般流程外,还需要严格的证书校验逻辑。普通的tls只是判断是否是ca签发,因为etcd节点保存了所有成员的服务端证书,它需要证书要严格一致才表示握手通过。
    • 这部分不再展开,可以参考etcdraft篇。

    Certificate

    handshake的过程需要发给对方证书,让对方拿去做校验的。而这里就是放证书的地方。只要发起tls连接,这个证书就会发送。

    • peer.tls.clientCert.file(CORE_PEER_TLS_CLIENTCERT_FILE),orderer.tls.clientCert.file负责客户端证书配置
    • CORE_PEER_TLS_CERT_FILE, ORDERER_GENERAL_TLS_CERTIFICATE负责服务端证书配置

    Key

    在tls中,双方需要进行密钥协商,协商的结果是双方发送的内容会用这个密钥进行加密解密,一般说来这个密钥是对称加密。当然了,你也可以用不对称,但是用在tls的场景效率太低了,一般不这么用。

    怎么协商呢?客户端在发起连接时,会随机一个预备密钥,用服务端的公钥进行加密,发给对方,而服务端用自己的私钥进行解密,进而协商出一个主密钥。而这里的key就是做协商加密用的。

    • peer.tls.clientKey.file(CORE_PEER_TLS_CLIENTKEY_FILE),orderer.tls.clientKey.file负责客户端私钥配置
    • CORE_PEER_TLS_KEY_FILE,ORDERER_GENERAL_TLS_PRIVATEKEY负责服务端私钥

    ServerRootCAs

    这是客户端用来校验服务端证书的。这里的校验是指收到的服务端正式是否是该证书签发的。我举两个场景的例子,你就明白了。

    ordererclient

    什么叫ordererclient,其实就是peer,peer会去broadcast推送给orderer事件,也会deliver拉取block。按照上面的逻辑,这个client创建的时候会将orderer的ca证书作为tls的ServerRootCAs。

    if secOpts.UseTLS {
       caPEM, res := ioutil.ReadFile(config.GetPath(prefix + ".tls.rootcert.file"))
       if res != nil {
          err = errors.WithMessage(res,
             fmt.Sprintf("unable to load %s.tls.rootcert.file", prefix))
          return
       }
       secOpts.ServerRootCAs = [][]byte{caPEM}
    }
    

    这里prefix是orderer,相当于去获取了orderer.tls.rootcert.file,也可以当成是ORDERER_GENERAL_TLS_ROOTCAS。注意这两个配置理论上并没有什么关系。在first-network中orderer.tls.rootcert.file指定的是tlscacerts目录,而ORDERER_GENERAL_TLS_ROOTCAS是指定的tls目录的ca.crt, 有可能是向下兼容的关系,比较混乱,但是指定的证书都是同一个,只是名字不同,最好两个还是指保持一致。

    peerclient

    我们知道peer相互会互联,其中最知名的就是gossip协议,我们看下peer作为client的时候

    if secOpts.UseTLS {
       caPEM, res := ioutil.ReadFile(config.GetPath(prefix + ".tls.rootcert.file"))
       if res != nil {
          err = errors.WithMessage(res,
             fmt.Sprintf("unable to load %s.tls.rootcert.file", prefix))
          return
       }
       secOpts.ServerRootCAs = [][]byte{caPEM}
    }
    

    同样的代码,只不过是prefix不同,这里是peer,相当于去获取了peer.tls.rootcert.file,也就是CORE_PEER_TLS_ROOTCERT_FILE

    ClientRootCAs

    在tls的世界里,一般来说服务端发来证书,客户端校验就完了。有时,服务端也可以要求客户端把证书发来,进行双向校验。跟上面同理,客户端连接时,也需要明确的告诉服务端哪些客户端证书是有效的,被承认的。

    orderer

    if secureOpts.RequireClientCert {
       for _, clientRoot := range conf.General.TLS.ClientRootCAs {
          root, err := ioutil.ReadFile(clientRoot)
          if err != nil {
             logger.Fatalf("Failed to load ClientRootCAs file '%s' (%s)",
                err, clientRoot)
          }
          clientRootCAs = append(clientRootCAs, root)
       }
       msg = "mutual TLS"
    }
    

    这里的意思是,如果需要对客户端证书进行校验的话,从conf.General.TLS.ClientRootCAs加载客户端的ca证书。也就是ORDERER_GENERAL_TLS_CLIENTROOTCAS配置。

    peer

    if secureOptions.RequireClientCert {
       var clientRoots [][]byte
       for _, file := range viper.GetStringSlice("peer.tls.clientRootCAs.files") {
          clientRoot, err := ioutil.ReadFile(
             config.TranslatePath(filepath.Dir(viper.ConfigFileUsed()), file))
          if err != nil {
             return serverConfig,
                fmt.Errorf("error loading client root CAs (%s)", err)
          }
          clientRoots = append(clientRoots, clientRoot)
       }
       secureOptions.ClientRootCAs = clientRoots
    }
    

    这里跟上面类似,如果需要对客户端证书进行校验的话,从peer.tls.clientRootCAs.files加载客户端的ca证书。也就是CORE_PEER_TLS_CLIENTROOTCAS_FILES配置。

    UseTLS

    CORE_PEER_TLS_ENABLED,ORDERER_GENERAL_TLS_ENABLED分别开启peer和orderer的tls

    RequireClientCert

    CORE_PEER_TLS_CLIENTAUTHREQUIRED,ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED分别指示peer和orderer需要客户端证书进行校验

    CipherSuites

    什么叫加密套件,在tls中协商的过程中是需要有前提的,双方都支持的加密算法。就好比买东西,客户端提出自己的需求,然后服务端按照对方的情况对他进行量身打造,双方共同协商一致。

    DefaultTLSCipherSuites = []uint16{
       tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
       tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
       tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
       tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
       tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
       tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
    }
    
    1554817013176.png

    这个图只是解释下这一串的意义,没有深入的意思。大家就当听个响。

    Add Org

    想象一个场景,动态加入Org的场景,现在orderer已经更新最新的配置bundle。假如orderer的tls开启了客户端证书校验,会有什么问题?

    orderer现在还没有将新Org的tlsca证书加到ClientRootCAs里面,所有所有新Org的节点发来的请求服务端根本就不认识。

    没关系,下面我们一起来看下,至少得出一个结论,服务端会触发的时机就是更新bundle。

    func (bs *BundleSource) Update(newBundle *Bundle) {
       bs.bundle.Store(newBundle)
       for _, callback := range bs.callbacks {
          callback(newBundle)
       }
    }
    

    可以看到更新bundle没那么简单,有一堆callback等着去处理

    callback

    tlsCallback := func(bundle *channelconfig.Bundle) {
       // only need to do this if mutual TLS is required or if the orderer node is part of a cluster
       if grpcServer.MutualTLSRequired() || clusterType {
          logger.Debug("Executing callback to update root CAs")
          updateTrustedRoots(caSupport, bundle, servers...)
          if clusterType {
             updateClusterDialer(caSupport, clusterDialer, clusterClientConfig.SecOpts.ServerRootCAs)
          }
       }
    }
    

    在orderer启动的时候,会设置相关的callback,也就是tlscallback

    func updateTrustedRoots(rootCASupport *comm.CASupport, cm channelconfig.Resources, servers ...*comm.GRPCServer) {
       rootCASupport.Lock()
       defer rootCASupport.Unlock()
    
       appRootCAs := [][]byte{}
       ordererRootCAs := [][]byte{}
       appOrgMSPs := make(map[string]struct{})
       ordOrgMSPs := make(map[string]struct{})
    
       if ac, ok := cm.ApplicationConfig(); ok {
          // loop through app orgs and build map of MSPIDs
          for _, appOrg := range ac.Organizations() {
             appOrgMSPs[appOrg.MSPID()] = struct{}{}
          }
       }
    
       if ac, ok := cm.OrdererConfig(); ok {
          // loop through orderer orgs and build map of MSPIDs
          for _, ordOrg := range ac.Organizations() {
             ordOrgMSPs[ordOrg.MSPID()] = struct{}{}
          }
       }
    
       if cc, ok := cm.ConsortiumsConfig(); ok {
          for _, consortium := range cc.Consortiums() {
             // loop through consortium orgs and build map of MSPIDs
             for _, consortiumOrg := range consortium.Organizations() {
                appOrgMSPs[consortiumOrg.MSPID()] = struct{}{}
             }
          }
       }
    
       cid := cm.ConfigtxValidator().ChainID()
       logger.Debugf("updating root CAs for channel [%s]", cid)
       msps, err := cm.MSPManager().GetMSPs()
       if err != nil {
          logger.Errorf("Error getting root CAs for channel %s (%s)", cid, err)
          return
       }
       for k, v := range msps {
          // check to see if this is a FABRIC MSP
          if v.GetType() == msp.FABRIC {
             for _, root := range v.GetTLSRootCerts() {
                // check to see of this is an app org MSP
                if _, ok := appOrgMSPs[k]; ok {
                   logger.Debugf("adding app root CAs for MSP [%s]", k)
                   appRootCAs = append(appRootCAs, root)
                }
                // check to see of this is an orderer org MSP
                if _, ok := ordOrgMSPs[k]; ok {
                   logger.Debugf("adding orderer root CAs for MSP [%s]", k)
                   ordererRootCAs = append(ordererRootCAs, root)
                }
             }
             for _, intermediate := range v.GetTLSIntermediateCerts() {
                // check to see of this is an app org MSP
                if _, ok := appOrgMSPs[k]; ok {
                   logger.Debugf("adding app root CAs for MSP [%s]", k)
                   appRootCAs = append(appRootCAs, intermediate)
                }
                // check to see of this is an orderer org MSP
                if _, ok := ordOrgMSPs[k]; ok {
                   logger.Debugf("adding orderer root CAs for MSP [%s]", k)
                   ordererRootCAs = append(ordererRootCAs, intermediate)
                }
             }
          }
       }
       rootCASupport.AppRootCAsByChain[cid] = appRootCAs
       rootCASupport.OrdererRootCAsByChain[cid] = ordererRootCAs
    
       // now iterate over all roots for all app and orderer chains
       trustedRoots := [][]byte{}
       for _, roots := range rootCASupport.AppRootCAsByChain {
          trustedRoots = append(trustedRoots, roots...)
       }
       for _, roots := range rootCASupport.OrdererRootCAsByChain {
          trustedRoots = append(trustedRoots, roots...)
       }
       // also need to append statically configured root certs
       if len(rootCASupport.ClientRootCAs) > 0 {
          trustedRoots = append(trustedRoots, rootCASupport.ClientRootCAs...)
       }
    
       // now update the client roots for the gRPC server
       for _, srv := range servers {
          err = srv.SetClientRootCAs(trustedRoots)
          if err != nil {
             msg := "Failed to update trusted roots for orderer from latest config " +
                "block.  This orderer may not be able to communicate " +
                "with members of channel %s (%s)"
             logger.Warningf(msg, cm.ConfigtxValidator().ChainID(), err)
          }
       }
    }
    
    • 前面的部分就是收集最新配置中的orderer和application下所有org的tlsca证书和中间证书
    • 最重要的时候的调用grpcserver的SetClientRootCAs

    SetClientRootCAs

    func (gServer *GRPCServer) SetClientRootCAs(clientRoots [][]byte) error {
       gServer.lock.Lock()
       defer gServer.lock.Unlock()
    
       errMsg := "Failed to set client root certificate(s): %s"
    
       //create a new map and CertPool
       clientRootCAs := make(map[string]*x509.Certificate)
       for _, clientRoot := range clientRoots {
          certs, subjects, err := pemToX509Certs(clientRoot)
          if err != nil {
             return fmt.Errorf(errMsg, err.Error())
          }
          if len(certs) >= 1 {
             for i, cert := range certs {
                //add it to our clientRootCAs map using subject as key
                clientRootCAs[subjects[i]] = cert
             }
          }
       }
    
       //create a new CertPool and populate with the new clientRootCAs
       certPool := x509.NewCertPool()
       for _, clientRoot := range clientRootCAs {
          certPool.AddCert(clientRoot)
       }
       //replace the internal map
       gServer.clientRootCAs = clientRootCAs
       //replace the current ClientCAs pool
       gServer.tlsConfig.ClientCAs = certPool
       return nil
    }
    

    好了,终于到了我们想要找的逻辑。

    • 首先根据前面收集的证书集合来组装tlsca的certpool,因为MSP那边过来的证书PEM格式的,还需要转换成x509的标准
    • 之后就是更新gServer.tlsConfig.ClientCAs了,这就是前面所讲的ClientRootCAs

    小结

    好了,至此,fabric的tls配置的部分就是这点东西,看完,你起码知道那几个配置项的意义,出问题有的放矢了。

    下面我们通过一个实际的场景来串联下上面涉及的知识。

    peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA
    

    这是一个通道更新的脚本,这条命令告诉我们几件事情

    • 通道更新是需要发给orderer去处理的
    • 连接需要开启tls
    • 指定了cafile

    那问题来了,为什么要指定cafile?而且还是orderer的ca。

    首先我们先要确认的是cafile是对应的前面提到的哪种配置。

    • viper.Set("orderer.tls.rootcert.file", caFile)

    回顾下ServerRootCAs的部分,原来这里的目的是为了校验服务端证书用。

    相关文章

      网友评论

          本文标题:Hyperledger-Fabric源码分析(TLS)

          本文链接:https://www.haomeiwen.com/subject/seiwiqtx.html