Hyperledger-Fabric源码分析(TLS)

前面我们的证书生成的篇章中,只是提了下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的部分,原来这里的目的是为了校验服务端证书用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,737评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,103评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,710评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,909评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,794评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,557评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,939评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,572评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,852评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,871评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,692评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,490评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,939评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,114评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,409评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,971评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,176评论 2 339

推荐阅读更多精彩内容