Hyperledger-Fabric源碼分析(TLS)

前面我們的證書生成的篇章中,只是提了下tls相關(guān)的內(nèi)容,但他們是怎么用的并沒有涉及,這一篇我們來探討下Fabric中tls是怎么運(yùn)轉(zhuǎn)的。

首先我們來看下SecureOptions,這里包含了不管是server還是client所有涉及的配置項(xiàng),搞懂這些選項(xiàng)的來龍去脈,基本上你也就搞懂了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 -- 回調(diào)方法,用來在tls握手期間,正常的證書校驗(yàn)完畢后,回調(diào)這部分自定義校驗(yàn)邏輯。
  • Certificate -- 這里是存放tls所要使用的證書,不論是server還是client
  • Key -- 私鑰
  • ServerRootCAs -- 客戶端用來校驗(yàn)服務(wù)端證書
  • ClientRootCAs -- 服務(wù)端用來校驗(yàn)客戶端證書
  • 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)
    }
}
  • 可以看到,這里的意思是,連接的過程中,連接方希望被連接方的證書跟我所期待的完全一致。可以說這是非常嚴(yán)格的證書校驗(yàn)邏輯。整個(gè)fabric有用到這個(gè)的地方就是etcd的部分,因?yàn)閑tcd的網(wǎng)絡(luò)層是自己實(shí)現(xiàn)的,在一般流程外,還需要嚴(yán)格的證書校驗(yàn)邏輯。普通的tls只是判斷是否是ca簽發(fā),因?yàn)閑tcd節(jié)點(diǎn)保存了所有成員的服務(wù)端證書,它需要證書要嚴(yán)格一致才表示握手通過。
  • 這部分不再展開,可以參考etcdraft篇。

Certificate

handshake的過程需要發(fā)給對方證書,讓對方拿去做校驗(yàn)的。而這里就是放證書的地方。只要發(fā)起tls連接,這個(gè)證書就會(huì)發(fā)送。

  • peer.tls.clientCert.file(CORE_PEER_TLS_CLIENTCERT_FILE),orderer.tls.clientCert.file負(fù)責(zé)客戶端證書配置
  • CORE_PEER_TLS_CERT_FILE, ORDERER_GENERAL_TLS_CERTIFICATE負(fù)責(zé)服務(wù)端證書配置

Key

在tls中,雙方需要進(jìn)行密鑰協(xié)商,協(xié)商的結(jié)果是雙方發(fā)送的內(nèi)容會(huì)用這個(gè)密鑰進(jìn)行加密解密,一般說來這個(gè)密鑰是對稱加密。當(dāng)然了,你也可以用不對稱,但是用在tls的場景效率太低了,一般不這么用。

怎么協(xié)商呢?客戶端在發(fā)起連接時(shí),會(huì)隨機(jī)一個(gè)預(yù)備密鑰,用服務(wù)端的公鑰進(jìn)行加密,發(fā)給對方,而服務(wù)端用自己的私鑰進(jìn)行解密,進(jìn)而協(xié)商出一個(gè)主密鑰。而這里的key就是做協(xié)商加密用的。

  • peer.tls.clientKey.file(CORE_PEER_TLS_CLIENTKEY_FILE),orderer.tls.clientKey.file負(fù)責(zé)客戶端私鑰配置
  • CORE_PEER_TLS_KEY_FILE,ORDERER_GENERAL_TLS_PRIVATEKEY負(fù)責(zé)服務(wù)端私鑰

ServerRootCAs

這是客戶端用來校驗(yàn)服務(wù)端證書的。這里的校驗(yàn)是指收到的服務(wù)端正式是否是該證書簽發(fā)的。我舉兩個(gè)場景的例子,你就明白了。

ordererclient

什么叫ordererclient,其實(shí)就是peer,peer會(huì)去broadcast推送給orderer事件,也會(huì)deliver拉取block。按照上面的邏輯,這個(gè)client創(chuàng)建的時(shí)候會(huì)將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,相當(dāng)于去獲取了orderer.tls.rootcert.file,也可以當(dāng)成是ORDERER_GENERAL_TLS_ROOTCAS。注意這兩個(gè)配置理論上并沒有什么關(guān)系。在first-network中orderer.tls.rootcert.file指定的是tlscacerts目錄,而ORDERER_GENERAL_TLS_ROOTCAS是指定的tls目錄的ca.crt, 有可能是向下兼容的關(guān)系,比較混亂,但是指定的證書都是同一個(gè),只是名字不同,最好兩個(gè)還是指保持一致。

peerclient

我們知道peer相互會(huì)互聯(lián),其中最知名的就是gossip協(xié)議,我們看下peer作為client的時(shí)候

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,相當(dāng)于去獲取了peer.tls.rootcert.file,也就是CORE_PEER_TLS_ROOTCERT_FILE

ClientRootCAs

在tls的世界里,一般來說服務(wù)端發(fā)來證書,客戶端校驗(yàn)就完了。有時(shí),服務(wù)端也可以要求客戶端把證書發(fā)來,進(jìn)行雙向校驗(yàn)。跟上面同理,客戶端連接時(shí),也需要明確的告訴服務(wù)端哪些客戶端證書是有效的,被承認(rèn)的。

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"
}

這里的意思是,如果需要對客戶端證書進(jìn)行校驗(yàn)的話,從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
}

這里跟上面類似,如果需要對客戶端證書進(jìn)行校驗(yàn)的話,從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需要客戶端證書進(jìn)行校驗(yàn)

CipherSuites

什么叫加密套件,在tls中協(xié)商的過程中是需要有前提的,雙方都支持的加密算法。就好比買東西,客戶端提出自己的需求,然后服務(wù)端按照對方的情況對他進(jìn)行量身打造,雙方共同協(xié)商一致。

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

這個(gè)圖只是解釋下這一串的意義,沒有深入的意思。大家就當(dāng)聽個(gè)響。

Add Org

想象一個(gè)場景,動(dòng)態(tài)加入Org的場景,現(xiàn)在orderer已經(jīng)更新最新的配置bundle。假如orderer的tls開啟了客戶端證書校驗(yàn),會(huì)有什么問題?

orderer現(xiàn)在還沒有將新Org的tlsca證書加到ClientRootCAs里面,所有所有新Org的節(jié)點(diǎn)發(fā)來的請求服務(wù)端根本就不認(rèn)識(shí)。

沒關(guān)系,下面我們一起來看下,至少得出一個(gè)結(jié)論,服務(wù)端會(huì)觸發(fā)的時(shí)機(jī)就是更新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啟動(dòng)的時(shí)候,會(huì)設(shè)置相關(guān)的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證書和中間證書
  • 最重要的時(shí)候的調(diào)用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
}

好了,終于到了我們想要找的邏輯。

  • 首先根據(jù)前面收集的證書集合來組裝tlsca的certpool,因?yàn)镸SP那邊過來的證書PEM格式的,還需要轉(zhuǎn)換成x509的標(biāo)準(zhǔn)
  • 之后就是更新gServer.tlsConfig.ClientCAs了,這就是前面所講的ClientRootCAs

小結(jié)

好了,至此,fabric的tls配置的部分就是這點(diǎn)東西,看完,你起碼知道那幾個(gè)配置項(xiàng)的意義,出問題有的放矢了。

下面我們通過一個(gè)實(shí)際的場景來串聯(lián)下上面涉及的知識(shí)。

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

這是一個(gè)通道更新的腳本,這條命令告訴我們幾件事情

  • 通道更新是需要發(fā)給orderer去處理的
  • 連接需要開啟tls
  • 指定了cafile

那問題來了,為什么要指定cafile?而且還是orderer的ca。

首先我們先要確認(rèn)的是cafile是對應(yīng)的前面提到的哪種配置。

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

回顧下ServerRootCAs的部分,原來這里的目的是為了校驗(yàn)服務(wù)端證書用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容