在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的处理,还不明白为什么这么处理,查了下资料似乎是一种特殊处理,来防止某种形式的攻击。待研究。不过不影响整体流程。