Hyperledger-Fabric源码分析(MSP-身份验证)

Hyperledger-Fabric源码分析(MSP-身份验证)_第1张图片
1554214699271.png

在MSP中,身份是建立在X.509证书标准上的,基于PKI体系进行身份认证,进而可以做权限控制。那么下面我们来深入探讨下MSP是怎么做身份验证的。

在深入之前,请自行补充相关知识。

建立身份

func newIdentity(cert *x509.Certificate, pk bccsp.Key, msp *bccspmsp) (Identity, error) {
   if mspIdentityLogger.IsEnabledFor(zapcore.DebugLevel) {
      mspIdentityLogger.Debugf("Creating identity instance for cert %s", certToPEM(cert))
   }

   // Sanitize first the certificate
   cert, err := msp.sanitizeCert(cert)
   if err != nil {
      return nil, err
   }

   // Compute identity identifier

   // Use the hash of the identity's certificate as id in the IdentityIdentifier
   hashOpt, err := bccsp.GetHashOpt(msp.cryptoConfig.IdentityIdentifierHashFunction)
   if err != nil {
      return nil, errors.WithMessage(err, "failed getting hash function options")
   }

   digest, err := msp.bccsp.Hash(cert.Raw, hashOpt)
   if err != nil {
      return nil, errors.WithMessage(err, "failed hashing raw certificate to compute the id of the IdentityIdentifier")
   }

   id := &IdentityIdentifier{
      Mspid: msp.name,
      Id:    hex.EncodeToString(digest)}

   return &identity{id: id, cert: cert, pk: pk, msp: msp}, nil
}

可以看到身份是由证书,公钥,msp,mspid,及证书内容的hash组成。

身份验证

func (msp *bccspmsp) validateIdentity(id *identity) error {
   validationChain, err := msp.getCertificationChainForBCCSPIdentity(id)
   if err != nil {
      return errors.WithMessage(err, "could not obtain certification chain")
   }

   err = msp.validateIdentityAgainstChain(id, validationChain)
   if err != nil {
      return errors.WithMessage(err, "could not validate identity against certification chain")
   }

   err = msp.internalValidateIdentityOusFunc(id)
   if err != nil {
      return errors.WithMessage(err, "could not validate identity's OUs")
   }

   return nil
}
  • getCertificationChainForBCCSPIdentity这里是通过身份来获取证书链
  • validateIdentityAgainstChain这里是确认证书不在CRL里面,也就是证书吊销列表
  • internalValidateIdentityOusFunc这里主要是校验证书的OU
  • 接下来我们具体来看看里面的详细实现

getCertificationChainForBCCSPIdentity

func (msp *bccspmsp) getCertificationChainForBCCSPIdentity(id *identity) ([]*x509.Certificate, error) {
   if id == nil {
      return nil, errors.New("Invalid bccsp identity. Must be different from nil.")
   }

   // we expect to have a valid VerifyOptions instance
   if msp.opts == nil {
      return nil, errors.New("Invalid msp instance")
   }

   // CAs cannot be directly used as identities..
   if id.cert.IsCA {
      return nil, errors.New("An X509 certificate with Basic Constraint: " +
         "Certificate Authority equals true cannot be used as an identity")
   }

   return msp.getValidationChain(id.cert, false)
}
  • 这里唯一需要注意的是你的身份不应该是ca证书,这个除非你指定错了路径,不然不会出这个错误。
  • 接着去获取证书链

getUniqueValidationChain

func (msp *bccspmsp) getUniqueValidationChain(cert *x509.Certificate, opts x509.VerifyOptions) ([]*x509.Certificate, error) {
   // ask golang to validate the cert for us based on the options that we've built at setup time
   if msp.opts == nil {
      return nil, errors.New("the supplied identity has no verify options")
   }
   validationChains, err := cert.Verify(opts)
   if err != nil {
      return nil, errors.WithMessage(err, "the supplied identity is not valid")
   }

   // we only support a single validation chain;
   // if there's more than one then there might
   // be unclarity about who owns the identity
   if len(validationChains) != 1 {
      return nil, errors.Errorf("this MSP only supports a single validation chain, got %d", len(validationChains))
   }

   return validationChains[0], nil
}
  • 这里首先是调用底层方法来返回证书链,需要注意的是,理论上一个证书的链路可能会有多条,不过在fabric的世界里,有且只有一条。
  • 证书链的第一个元素,是证书本身,往后一个是他的签发单位,以此类推。

getValidationChain

func (msp *bccspmsp) getValidationChain(cert *x509.Certificate, isIntermediateChain bool) ([]*x509.Certificate, error) {
   validationChain, err := msp.getUniqueValidationChain(cert, msp.getValidityOptsForCert(cert))
   if err != nil {
      return nil, errors.WithMessage(err, "failed getting validation chain")
   }

   // we expect a chain of length at least 2
   if len(validationChain) < 2 {
      return nil, errors.Errorf("expected a chain of length at least 2, got %d", len(validationChain))
   }

   // check that the parent is a leaf of the certification tree
   // if validating an intermediate chain, the first certificate will the parent
   parentPosition := 1
   if isIntermediateChain {
      parentPosition = 0
   }
   if msp.certificationTreeInternalNodesMap[string(validationChain[parentPosition].Raw)] {
      return nil, errors.Errorf("invalid validation chain. Parent certificate should be a leaf of the certification tree [%v]", cert.Raw)
   }
   return validationChain, nil
}
  • 这里拿到证书链后,做必要的校验
  • 首先,不可能小于2,要不是cert-intermediateca-rootca,要不就是cert-rootca
  • certificationTreeInternalNodesMap值得注意,这是整个中间证书+根证书组成的树形结构。可能有多个根节点。另外所有的身份证书都由这颗树形的叶子节点来签发。
  • 搞懂了上面的描述,接下来的逻辑就很合理了。这里会判断证书的签发单位是否是树形的叶子节点。
  • 以上校验通过,那么证书链符合要求,可以返回了。

validateIdentityAgainstChain

func (msp *bccspmsp) validateCertAgainstChain(cert *x509.Certificate, validationChain []*x509.Certificate) error {
   // here we know that the identity is valid; now we have to check whether it has been revoked

   // identify the SKI of the CA that signed this cert
   SKI, err := getSubjectKeyIdentifierFromCert(validationChain[1])
   if err != nil {
      return errors.WithMessage(err, "could not obtain Subject Key Identifier for signer cert")
   }

   // check whether one of the CRLs we have has this cert's
   // SKI as its AuthorityKeyIdentifier
   for _, crl := range msp.CRL {
      aki, err := getAuthorityKeyIdentifierFromCrl(crl)
      if err != nil {
         return errors.WithMessage(err, "could not obtain Authority Key Identifier for crl")
      }

      // check if the SKI of the cert that signed us matches the AKI of any of the CRLs
      if bytes.Equal(aki, SKI) {
         // we have a CRL, check whether the serial number is revoked
         for _, rc := range crl.TBSCertList.RevokedCertificates {
            if rc.SerialNumber.Cmp(cert.SerialNumber) == 0 { 
               // We have found a CRL whose AKI matches the SKI of
               // the CA (root or intermediate) that signed the
               // certificate that is under validation. As a
               // precaution, we verify that said CA is also the
               // signer of this CRL.
               err = validationChain[1].CheckCRLSignature(crl)
               if err != nil {
                  // the CA cert that signed the certificate
                  // that is under validation did not sign the
                  // candidate CRL - skip
                  mspLogger.Warningf("Invalid signature over the identified CRL, error %+v", err)
                  continue
               }

               // A CRL also includes a time of revocation so that
               // the CA can say "this cert is to be revoked starting
               // from this time"; however here we just assume that
               // revocation applies instantaneously from the time
               // the MSP config is committed and used so we will not
               // make use of that field
               return errors.New("The certificate has been revoked")
            }
         }
      }
   }

   return nil
}
  • 首先拿到签发单位的SKI,相当于证书的唯一标识,实际是公钥的摘要
  • 根据签发单位的SKI拿到它签发的CRL证书列表,看下当前证书是否包括,如果包括,说明证书已经无效,校验失败。
  • 这里在查找的过程中不光只是比对SerialNumber,还验证了CRL里面证书的签名是否有效。double-check

validateIdentityOUs

func (msp *bccspmsp) validateIdentityOUsV1(id *identity) error {
    // Check that the identity's OUs are compatible with those recognized by this MSP,
    // meaning that the intersection is not empty.
    if len(msp.ouIdentifiers) > 0 {
        found := false

        for _, OU := range id.GetOrganizationalUnits() {
            certificationIDs, exists := msp.ouIdentifiers[OU.OrganizationalUnitIdentifier]

            if exists {
                for _, certificationID := range certificationIDs {
                    if bytes.Equal(certificationID, OU.CertifiersIdentifier) {
                        found = true
                        break
                    }
                }
            }
        }

        if !found {
            if len(id.GetOrganizationalUnits()) == 0 {
                return errors.New("the identity certificate does not contain an Organizational Unit (OU)")
            }
            return errors.Errorf("none of the identity's organizational units [%v] are in MSP %s", id.GetOrganizationalUnits(), msp.name)
        }
    }

    return nil
}

func (msp *bccspmsp) validateIdentityOUsV11(id *identity) error {
   // Run the same checks as per V1
   err := msp.validateIdentityOUsV1(id)
   if err != nil {
      return err
   }

   // Perform V1_1 additional checks:
   //
   // -- Check for OU enforcement
   if !msp.ouEnforcement {
      // No enforcement required
      return nil
   }

   // Make sure that the identity has only one of the special OUs
   // used to tell apart clients, peers and orderers.
   counter := 0
   for _, OU := range id.GetOrganizationalUnits() {
      // Is OU.OrganizationalUnitIdentifier one of the special OUs?
      var nodeOU *OUIdentifier
      switch OU.OrganizationalUnitIdentifier {
      case msp.clientOU.OrganizationalUnitIdentifier:
         nodeOU = msp.clientOU
      case msp.peerOU.OrganizationalUnitIdentifier:
         nodeOU = msp.peerOU
      default:
         continue
      }

      // Yes. Then, enforce the certifiers identifier is this is specified.
      // It is not specified, it means that any certification path is fine.
      if len(nodeOU.CertifiersIdentifier) != 0 && !bytes.Equal(nodeOU.CertifiersIdentifier, OU.CertifiersIdentifier) {
         return errors.Errorf("certifiersIdentifier does not match: [%v], MSP: [%s]", id.GetOrganizationalUnits(), msp.name)
      }
      counter++
      if counter > 1 {
         break
      }
   }
   if counter != 1 {
      return errors.Errorf("the identity must be a client, a peer or an orderer identity to be valid, not a combination of them. OUs: [%v], MSP: [%s]", id.GetOrganizationalUnits(), msp.name)
   }

   return nil
}
  • 首先需要保证的是证书所属的OU要被本地MSP所认识,这样才有资格继续
  • 如果OU是peer或client,需要跟confix.yaml中指定的证书的链路摘要做比较,否则校验失败。看起来似乎peer或client证书是从配置文件里的证书签发的,因为这里的证书链的摘要并不包含自己本身。

总结

至此,整个校验就是以上这些东西。不过,在获取证书链的过程中其中会做low-s的处理,还不明白为什么这么处理,查了下资料似乎是一种特殊处理,来防止某种形式的攻击。待研究。不过不影响整体流程。

你可能感兴趣的:(Hyperledger-Fabric源码分析(MSP-身份验证))