From c971c77eff320da8e050045989f30e87cfcd1b0e Mon Sep 17 00:00:00 2001 From: Alessandro Sorniotti Date: Mon, 23 Jan 2017 22:00:15 +0100 Subject: [PATCH] [FAB-1558] - Revocation support in MSP This change-set adds revocation support for the default MSP. Revocation is implemented by supplying zero or more 'complete CRLs' (as described in rfc 5280) in the RevocationList field of the FabricMSPConfig struct. The CRL can only apply to certificates and not to CAs or intermediate CAs. If a CA or an intermediate CA is to be revoked, it can just be removed from the respective fields in the config. Change-Id: I7a4d290aed264952b329fc981c17f87b94b10899 Signed-off-by: Alessandro Sorniotti --- msp/mspimpl.go | 227 ++++++++++++++++++++++++++++++++++++----- msp/revocation_test.go | 108 ++++++++++++++++++++ 2 files changed, 311 insertions(+), 24 deletions(-) create mode 100644 msp/revocation_test.go diff --git a/msp/mspimpl.go b/msp/mspimpl.go index 0d4118314b2..1730903db20 100644 --- a/msp/mspimpl.go +++ b/msp/mspimpl.go @@ -24,7 +24,11 @@ import ( "bytes" + "crypto/x509/pkix" + "encoding/asn1" "errors" + "math/big" + "reflect" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/bccsp" @@ -57,6 +61,9 @@ type bccspmsp struct { // verification options for MSP members opts *x509.VerifyOptions + + // list of certificate revocation lists + CRL []*pkix.CertificateList } // NewBccspMsp returns an MSP instance backed up by a BCCSP @@ -140,6 +147,86 @@ func (msp *bccspmsp) getSigningIdentityFromConf(sidInfo *m.SigningIdentityInfo) idPub.(*identity).cert, idPub.(*identity).pk, peerSigner, msp), nil } +/* + This is the definition of the ASN.1 marshalling of AuthorityKeyIdentifier + from https://www.ietf.org/rfc/rfc5280.txt + + AuthorityKeyIdentifier ::= SEQUENCE { + keyIdentifier [0] KeyIdentifier OPTIONAL, + authorityCertIssuer [1] GeneralNames OPTIONAL, + authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } + + KeyIdentifier ::= OCTET STRING + + CertificateSerialNumber ::= INTEGER + +*/ + +type authorityKeyIdentifier struct { + KeyIdentifier []byte `asn1:"optional,tag:0"` + AuthorityCertIssuer []byte `asn1:"optional,tag:1"` + AuthorityCertSerialNumber big.Int `asn1:"optional,tag:2"` +} + +// getAuthorityKeyIdentifierFromCrl returns the Authority Key Identifier +// for the supplied CRL. The authority key identifier can be used to identify +// the public key corresponding to the private key which was used to sign the CRL. +func getAuthorityKeyIdentifierFromCrl(crl *pkix.CertificateList) ([]byte, error) { + aki := authorityKeyIdentifier{} + + for _, ext := range crl.TBSCertList.Extensions { + // Authority Key Identifier is identified by the following ASN.1 tag + // authorityKeyIdentifier (2 5 29 35) (see https://tools.ietf.org/html/rfc3280.html) + if reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 35}) { + _, err := asn1.Unmarshal(ext.Value, &aki) + if err != nil { + return nil, fmt.Errorf("Failed to unmarshal AKI, error %s", err) + } + + return aki.KeyIdentifier, nil + } + } + + return nil, errors.New("authorityKeyIdentifier not found in certificate") +} + +// getSubjectKeyIdentifierFromCert returns the Subject Key Identifier for the supplied certificate +// Subject Key Identifier is an identifier of the public key of this certificate +func getSubjectKeyIdentifierFromCert(cert *x509.Certificate) ([]byte, error) { + var SKI []byte + + for _, ext := range cert.Extensions { + // Subject Key Identifier is identified by the following ASN.1 tag + // subjectKeyIdentifier (2 5 29 14) (see https://tools.ietf.org/html/rfc3280.html) + if reflect.DeepEqual(ext.Id, asn1.ObjectIdentifier{2, 5, 29, 14}) { + _, err := asn1.Unmarshal(ext.Value, &SKI) + if err != nil { + return nil, fmt.Errorf("Failed to unmarshal Subject Key Identifier, err %s", err) + } + + return SKI, nil + } + } + + return nil, errors.New("subjectKeyIdentifier not found in certificate") +} + +// isCAProperlyFormed does a few checks on the certificate, +// assuming it's a CA; it returns true if all looks good +// and false otherwise +func isCAProperlyFormed(cert *x509.Certificate) bool { + _, err := getSubjectKeyIdentifierFromCert(cert) + if err != nil { + return false + } + + if !cert.IsCA { + return false + } + + return true +} + // Setup sets up the internal data structures // for this MSP, given an MSPConfig ref; it // returns nil in case of success or an error otherwise @@ -160,16 +247,14 @@ func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error { mspLogger.Debugf("Setting up MSP instance %s", msp.name) // make and fill the set of admin certs (if present) - if conf.Admins != nil { - msp.admins = make([]Identity, len(conf.Admins)) - for i, admCert := range conf.Admins { - id, err := msp.getIdentityFromConf(admCert) - if err != nil { - return err - } - - msp.admins[i] = id + msp.admins = make([]Identity, len(conf.Admins)) + for i, admCert := range conf.Admins { + id, err := msp.getIdentityFromConf(admCert) + if err != nil { + return err } + + msp.admins[i] = id } // make and fill the set of CA certs - we expect them to be there @@ -187,18 +272,21 @@ func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error { } // make and fill the set of intermediate certs (if present) - if conf.IntermediateCerts != nil { - msp.intermediateCerts = make([]Identity, len(conf.IntermediateCerts)) - for i, trustedCert := range conf.IntermediateCerts { - id, err := msp.getIdentityFromConf(trustedCert) - if err != nil { - return err - } + msp.intermediateCerts = make([]Identity, len(conf.IntermediateCerts)) + for i, trustedCert := range conf.IntermediateCerts { + id, err := msp.getIdentityFromConf(trustedCert) + if err != nil { + return err + } - msp.intermediateCerts[i] = id + msp.intermediateCerts[i] = id + } + + // ensure that our CAs are properly formed + for _, cert := range append(append([]Identity{}, msp.rootCerts...), msp.intermediateCerts...) { + if !isCAProperlyFormed(cert.(*identity).cert) { + return fmt.Errorf("CA Certificate did not have the Subject Key Identifier extension, (SN: %s)", cert.(*identity).cert.SerialNumber) } - } else { - msp.intermediateCerts = make([]Identity, 0) } // setup the signer (if present) @@ -223,6 +311,52 @@ func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error { msp.opts.Intermediates.AddCert(v.(*identity).cert) } + // setup the CRL (if present) + msp.CRL = make([]*pkix.CertificateList, len(conf.RevocationList)) + for i, crlbytes := range conf.RevocationList { + valid := false + + // at first we parse it + crl, err := x509.ParseCRL(crlbytes) + if err != nil { + return fmt.Errorf("Could not parse RevocationList, err %s", err) + } + + // we extract the AKI - this is needed so that we know which CA should have signed us + aki, err := getAuthorityKeyIdentifierFromCrl(crl) + if err != nil { + return fmt.Errorf("Could not get AuthorityKeyIdentifier from RevocationList, err %s", err) + } + + // now check the signature over the CRL - it has to be signed + // by one of our CAs (either root or intermediate) + for _, ca := range append(append([]Identity{}, msp.rootCerts...), msp.intermediateCerts...) { + // get the SKI for the CA + ski, err := getSubjectKeyIdentifierFromCert(ca.(*identity).cert) + if err != nil { + return fmt.Errorf("Could not get SubjectKeyIdentifier from CA cert, err %s", err) + } + + // this is the CA that should have signed + // this CRL, check its signature over it + if bytes.Equal(aki, ski) { + err := ca.(*identity).cert.CheckCRLSignature(crl) + if err != nil { + return fmt.Errorf("Invalid signature on the CRL, err %s", err) + } + valid = true + break + } + } + + // valid may not be set in case none of the CAs signed the CRL + if !valid { + return errors.New("Could not verify signature over the CRL") + } + + msp.CRL[i] = crl + } + return nil } @@ -263,7 +397,7 @@ func (msp *bccspmsp) GetSigningIdentity(identifier *IdentityIdentifier) (Signing func (msp *bccspmsp) Validate(id Identity) error { mspLogger.Infof("MSP %s validating identity", msp.name) - switch id.(type) { + switch id := id.(type) { // If this identity is of this specific type, // this is how I can validate it given the // root of trust this MSP has @@ -274,7 +408,7 @@ func (msp *bccspmsp) Validate(id Identity) error { } // CAs cannot be directly used as identities.. - if id.(*identity).cert.IsCA { + if id.cert.IsCA { return errors.New("A CA certificate cannot be used directly by this MSP") } @@ -290,12 +424,57 @@ func (msp *bccspmsp) Validate(id Identity) error { // signed by CA but not by CA -> iCA1) // ask golang to validate the cert for us based on the options that we've built at setup time - _, err := id.(*identity).cert.Verify(*(msp.opts)) + validationChain, err := id.cert.Verify(*(msp.opts)) if err != nil { return fmt.Errorf("The supplied identity is not valid, Verify() returned %s", err) - } else { - return nil } + + // 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(validationChain) != 1 { + return fmt.Errorf("This MSP only supports a single validation chain, got %d", len(validationChain)) + } + + // we expect a chain of length at least 2 + if len(validationChain[0]) < 2 { + return fmt.Errorf("Expected a chain of length at least 2, got %d", len(validationChain)) + } + + // 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[0][1]) + if err != nil { + return fmt.Errorf("Could not obtain Subject Key Identifier for signer cert, err %s", err) + } + + // 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 fmt.Errorf("Could not obtain Authority Key Identifier for crl, err %s", err) + } + + // 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(id.cert.SerialNumber) == 0 { + // 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 default: return fmt.Errorf("Identity type not recognized") } diff --git a/msp/revocation_test.go b/msp/revocation_test.go new file mode 100644 index 00000000000..74af8bbd9ff --- /dev/null +++ b/msp/revocation_test.go @@ -0,0 +1,108 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package msp + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/protos/msp" + "github.com/stretchr/testify/assert" +) + +// the following strings contain the credentials for a test MSP setup that has +// 1) a key and a signcert (used to populate the default signing identity); +// 2) cacert is the CA that signed the intermediate; +// 2) a revocation list that revokes signcert +const keyrev = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAsWwFunEzqz1Rh6nvD4MiPkKCtmoxzh3jTquG5MSbeLoAoGCCqGSM49 +AwEHoUQDQgAEHBuKsAO43hs4JGpFfiGMkB/xsILTsOvmN2WmwpsPHZNL6w8HWe3x +CPQtdG/XJJvZ+C756KEsUBM3yw5PTfku8g== +-----END EC PRIVATE KEY-----` + +var signcertrev = `-----BEGIN CERTIFICATE----- +MIICjDCCAjKgAwIBAgIUBEVwsSx0TmqdbzNwleNBBzoIT0wwCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK +BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYxMTExMTcwNzAw +WhcNMTcxMTExMTcwNzAwWjBjMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGgg +Q2Fyb2xpbmExEDAOBgNVBAcTB1JhbGVpZ2gxGzAZBgNVBAoTEkh5cGVybGVkZ2Vy +IEZhYnJpYzEMMAoGA1UECxMDQ09QMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +HBuKsAO43hs4JGpFfiGMkB/xsILTsOvmN2WmwpsPHZNL6w8HWe3xCPQtdG/XJJvZ ++C756KEsUBM3yw5PTfku8qOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOFC +dcUZ4es3ltiCgAVDoyLfVpPIMB8GA1UdIwQYMBaAFBdnQj2qnoI/xMUdn1vDmdG1 +nEgQMCUGA1UdEQQeMByCCm15aG9zdC5jb22CDnd3dy5teWhvc3QuY29tMAoGCCqG +SM49BAMCA0gAMEUCIDf9Hbl4xn3z4EwNKmilM9lX2Fq4jWpAaRVB97OmVEeyAiEA +25aDPQHGGq2AvhKT0wvt08cX1GTGCIbfmuLpMwKQj38= +-----END CERTIFICATE-----` + +var cacertrev = `-----BEGIN CERTIFICATE----- +MIICYjCCAgmgAwIBAgIUB3CTDOU47sUC5K4kn/Caqnh114YwCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK +BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYxMDEyMTkzMTAw +WhcNMjExMDExMTkzMTAwWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZv +cm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEChMWSW50ZXJuZXQg +V2lkZ2V0cywgSW5jLjEMMAoGA1UECxMDV1dXMRQwEgYDVQQDEwtleGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKIH5b2JaSmqiQXHyqC+cmknICcF +i5AddVjsQizDV6uZ4v6s+PWiJyzfA/rTtMvYAPq/yeEHpBUB1j053mxnpMujYzBh +MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQXZ0I9 +qp6CP8TFHZ9bw5nRtZxIEDAfBgNVHSMEGDAWgBQXZ0I9qp6CP8TFHZ9bw5nRtZxI +EDAKBggqhkjOPQQDAgNHADBEAiAHp5Rbp9Em1G/UmKn8WsCbqDfWecVbZPQj3RK4 +oG5kQQIgQAe4OOKYhJdh3f7URaKfGTf492/nmRmtK+ySKjpHSrU= +-----END CERTIFICATE-----` + +var crlrev = `-----BEGIN X509 CRL----- +MIIBYzCCAQgCAQEwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHzAdBgNVBAoTFklu +dGVybmV0IFdpZGdldHMsIEluYy4xDDAKBgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhh +bXBsZS5jb20XDTE3MDEyMzIwNTYyMFoXDTE3MDEyNjIwNTYyMFowJzAlAhQERXCx +LHROap1vM3CV40EHOghPTBcNMTcwMTIzMjA0NzMxWqAvMC0wHwYDVR0jBBgwFoAU +F2dCPaqegj/ExR2fW8OZ0bWcSBAwCgYDVR0UBAMCAQgwCgYIKoZIzj0EAwIDSQAw +RgIhAOTTpQYkGO+gwVe1LQOcNMD5fzFViOwBUraMrk6dRMlmAiEA8z2dpXKGwHrj +FRBbKkDnSpaVcZgjns+mLdHV2JkF0gk= +-----END X509 CRL-----` + +func TestRevocation(t *testing.T) { + keyinfo := &msp.KeyInfo{KeyIdentifier: "PEER", KeyMaterial: []byte(keyrev)} + + sigid := &msp.SigningIdentityInfo{PublicSigner: []byte(signcertrev), PrivateSigner: keyinfo} + + fmspconf := &msp.FabricMSPConfig{ + RootCerts: [][]byte{[]byte(cacertrev)}, + RevocationList: [][]byte{[]byte(crlrev)}, + SigningIdentity: sigid, + Name: "DEFAULT"} + + fmpsjs, _ := proto.Marshal(fmspconf) + + mspconf := &msp.MSPConfig{Config: fmpsjs, Type: int32(FABRIC)} + + thisMSP, err := NewBccspMsp() + assert.NoError(t, err) + + err = thisMSP.Setup(mspconf) + assert.NoError(t, err) + + id, err := thisMSP.GetDefaultSigningIdentity() + assert.NoError(t, err) + + // the certificate associated to this id is revoked and so validation should fail! + err = id.Validate() + assert.Error(t, err) +}