Skip to content

Commit

Permalink
[FAB-1994] use intermediate CAs to validate certs
Browse files Browse the repository at this point in the history
This change-set modifies msp to use intermediate CAs to validate
certificates.

Change-Id: I3d0a5888af78085686eba01704369800857625a2
Signed-off-by: Alessandro Sorniotti <[email protected]>
  • Loading branch information
ale-linux authored and yacovm committed Feb 11, 2017
1 parent 31b9c40 commit add9ff2
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 26 deletions.
36 changes: 31 additions & 5 deletions msp/configbuilder.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
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 (
Expand Down Expand Up @@ -64,17 +80,19 @@ func getPemMaterialFromDir(dir string) ([][]byte, error) {
}

const (
cacerts = "cacerts"
admincerts = "admincerts"
signcerts = "signcerts"
keystore = "keystore"
cacerts = "cacerts"
admincerts = "admincerts"
signcerts = "signcerts"
keystore = "keystore"
intermediatecerts = "intermediatecerts"
)

func GetLocalMspConfig(dir string) (*msp.MSPConfig, error) {
cacertDir := filepath.Join(dir, cacerts)
signcertDir := filepath.Join(dir, signcerts)
admincertDir := filepath.Join(dir, admincerts)
keystoreDir := filepath.Join(dir, keystore)
intermediatecertsDir := filepath.Join(dir, intermediatecerts)

cacerts, err := getPemMaterialFromDir(cacertDir)
if err != nil || len(cacerts) == 0 {
Expand All @@ -96,6 +114,9 @@ func GetLocalMspConfig(dir string) (*msp.MSPConfig, error) {
return nil, fmt.Errorf("Could not load a valid signing key from directory %s, err %s", keystoreDir, err)
}

intermediatecert, _ := getPemMaterialFromDir(intermediatecertsDir)
// intermediate certs are not mandatory

// FIXME: for now we're making the following assumptions
// 1) there is exactly one signing cert
// 2) there is exactly one signing key
Expand All @@ -105,7 +126,12 @@ func GetLocalMspConfig(dir string) (*msp.MSPConfig, error) {

sigid := &msp.SigningIdentityInfo{PublicSigner: signcert[0], PrivateSigner: keyinfo}

fmspconf := &msp.FabricMSPConfig{Admins: admincert, RootCerts: cacerts, SigningIdentity: sigid, Name: "DEFAULT"}
fmspconf := &msp.FabricMSPConfig{
Admins: admincert,
RootCerts: cacerts,
IntermediateCerts: intermediatecert,
SigningIdentity: sigid,
Name: "DEFAULT"}

fmpsjs, _ := proto.Marshal(fmspconf)

Expand Down
16 changes: 16 additions & 0 deletions msp/msp_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
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 (
Expand Down
93 changes: 72 additions & 21 deletions msp/mspimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,29 @@ package msp
import (
"crypto/x509"
"fmt"
"time"

"encoding/pem"

"bytes"

"errors"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/signer"
"github.com/hyperledger/fabric/bccsp/sw"
"github.com/hyperledger/fabric/protos/common"
m "github.com/hyperledger/fabric/protos/msp"
"github.com/syndtr/goleveldb/leveldb/errors"
)

// This is an instantiation of an MSP that
// uses BCCSP for its cryptographic primitives.
type bccspmsp struct {
// list of certs we trust
trustedCerts []Identity
// list of CA certs we trust
rootCerts []Identity

// list of intermediate certs we trust
intermediateCerts []Identity

// list of signing identities
signer SigningIdentity
Expand All @@ -51,6 +54,9 @@ type bccspmsp struct {

// the provider identifier for this MSP
name string

// verification options for MSP members
opts *x509.VerifyOptions
}

// NewBccspMsp returns an MSP instance backed up by a BCCSP
Expand Down Expand Up @@ -153,26 +159,46 @@ func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error {
msp.name = conf.Name
mspLogger.Debugf("Setting up MSP instance %s", msp.name)

// make and fill the set of admin certs
msp.admins = make([]Identity, len(conf.Admins))
for i, admCert := range conf.Admins {
id, err := msp.getIdentityFromConf(admCert)
if err != nil {
return err
}
// 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[i] = id
}
}

// make and fill the set of CA certs
msp.trustedCerts = make([]Identity, len(conf.RootCerts))
// make and fill the set of CA certs - we expect them to be there
if len(conf.RootCerts) == 0 {
return errors.New("Expected at least one CA certificate")
}
msp.rootCerts = make([]Identity, len(conf.RootCerts))
for i, trustedCert := range conf.RootCerts {
id, err := msp.getIdentityFromConf(trustedCert)
if err != nil {
return err
}

msp.trustedCerts[i] = id
msp.rootCerts[i] = id
}

// 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[i] = id
}
} else {
msp.intermediateCerts = make([]Identity, 0)
}

// setup the signer (if present)
Expand All @@ -185,6 +211,18 @@ func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error {
msp.signer = sid
}

// pre-create the verify options with roots and intermediates
msp.opts = &x509.VerifyOptions{
Roots: x509.NewCertPool(),
Intermediates: x509.NewCertPool(),
}
for _, v := range msp.rootCerts {
msp.opts.Roots.AddCert(v.(*identity).cert)
}
for _, v := range msp.intermediateCerts {
msp.opts.Intermediates.AddCert(v.(*identity).cert)
}

return nil
}

Expand Down Expand Up @@ -230,16 +268,29 @@ func (msp *bccspmsp) Validate(id Identity) error {
// this is how I can validate it given the
// root of trust this MSP has
case *identity:
opts := x509.VerifyOptions{
Roots: x509.NewCertPool(),
CurrentTime: time.Now(),
// we expect to have a valid VerifyOptions instance
if msp.opts == nil {
return errors.New("Invalid msp instance")
}

for _, v := range msp.trustedCerts {
opts.Roots.AddCert(v.(*identity).cert)
// CAs cannot be directly used as identities..
if id.(*identity).cert.IsCA {
return errors.New("A CA certificate cannot be used directly by this MSP")
}

_, err := id.(*identity).cert.Verify(opts)
// at this point we might want to perform some
// more elaborate validation. We do not do this
// yet because we do not want to impose any
// constraints without knowing the exact requirements,
// but we at least list the kind of extra validation that we might perform:
// 1) we might only allow a single verification chain (e.g. we expect the
// cert to be signed exactly only by the CA or only by the intermediate)
// 2) we might want to let golang find any path, and then have a blacklist
// of paths (e.g. it can be signed by CA -> iCA1 -> iCA2 and it can be
// 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))
if err != nil {
return fmt.Errorf("The supplied identity is not valid, Verify() returned %s", err)
} else {
Expand Down
129 changes: 129 additions & 0 deletions msp/mspwithintermediatecas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
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);
// signcert is not signed by a CA directly but by an intermediate CA
// 2) intermediatecert is an intermediate CA, signed by the CA
// 3) cacert is the CA that signed the intermediate
const key = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEII27gKS2mFIIGkyGFEvHyv1khaJHe+p+sDt0++JByCDToAoGCCqGSM49
AwEHoUQDQgAEJUUpwMg/jQ+qpmkVewEvwTySl+XWbd4AXtb/0XsDqXNcyXl0DVgA
gJNGnt5r+bvZdB8SOk1ySAEEsCQArkarMg==
-----END EC PRIVATE KEY-----`

var signcert = `-----BEGIN CERTIFICATE-----
MIIDAzCCAqigAwIBAgIBAjAKBggqhkjOPQQDAjBsMQswCQYDVQQGEwJHQjEQMA4G
A1UECAwHRW5nbGFuZDEOMAwGA1UECgwFQmFyMTkxDjAMBgNVBAsMBUJhcjE5MQ4w
DAYDVQQDDAVCYXIxOTEbMBkGCSqGSIb3DQEJARYMQmFyMTktY2xpZW50MB4XDTE3
MDIwOTE2MDcxMFoXDTE4MDIxOTE2MDcxMFowfDELMAkGA1UEBhMCR0IxEDAOBgNV
BAgMB0VuZ2xhbmQxEDAOBgNVBAcMB0lwc3dpY2gxDjAMBgNVBAoMBUJhcjE5MQ4w
DAYDVQQLDAVCYXIxOTEOMAwGA1UEAwwFQmFyMTkxGTAXBgkqhkiG9w0BCQEWCkJh
cjE5LXBlZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQlRSnAyD+ND6qmaRV7
AS/BPJKX5dZt3gBe1v/RewOpc1zJeXQNWACAk0ae3mv5u9l0HxI6TXJIAQSwJACu
Rqsyo4IBKTCCASUwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZI
AYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAd
BgNVHQ4EFgQUwHzbLJQMaWd1cpHdkSaEFxdKB1owgYsGA1UdIwSBgzCBgIAUYxFe
+cXOD5iQ223bZNdOuKCRiTKhZaRjMGExCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdF
bmdsYW5kMRAwDgYDVQQHDAdJcHN3aWNoMQ4wDAYDVQQKDAVCYXIxOTEOMAwGA1UE
CwwFQmFyMTkxDjAMBgNVBAMMBUJhcjE5ggEBMA4GA1UdDwEB/wQEAwIFoDATBgNV
HSUEDDAKBggrBgEFBQcDATAKBggqhkjOPQQDAgNJADBGAiEAuMq65lOaie4705Ol
Ow52DjbaO2YuIxK2auBCqNIu0gECIQCDoKdUQ/sa+9Ah1mzneE6iz/f/YFVWo4EP
HeamPGiDTQ==
-----END CERTIFICATE-----`

var intermediatecert = `-----BEGIN CERTIFICATE-----
MIICITCCAcigAwIBAgIBATAKBggqhkjOPQQDAjBhMQswCQYDVQQGEwJHQjEQMA4G
A1UECAwHRW5nbGFuZDEQMA4GA1UEBwwHSXBzd2ljaDEOMAwGA1UECgwFQmFyMTkx
DjAMBgNVBAsMBUJhcjE5MQ4wDAYDVQQDDAVCYXIxOTAeFw0xNzAyMDkxNTUyMDBa
Fw0yNzAyMDcxNTUyMDBaMGwxCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5k
MQ4wDAYDVQQKDAVCYXIxOTEOMAwGA1UECwwFQmFyMTkxDjAMBgNVBAMMBUJhcjE5
MRswGQYJKoZIhvcNAQkBFgxCYXIxOS1jbGllbnQwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAQBymfTx4GWt1lnTV4Xp3skM5LJpZ40HVhCDLfvfrD8/3WQLHaLc7XW
KpphhXW8HYLyyjkEZVLsAFHkKjwmlcpzo2YwZDAdBgNVHQ4EFgQUYxFe+cXOD5iQ
223bZNdOuKCRiTIwHwYDVR0jBBgwFoAU4UJ1xRnh6zeW2IKABUOjIt9Wk8gwEgYD
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDRwAw
RAIgGUgzRtqWx98KkgKNDyeEmBmhpptW966iS7+c8ig4ksMCIEyzhATMpiI4pHzH
xSwZMvo3y3wkMwgf/WrhwdCyZNku
-----END CERTIFICATE-----`

var cacert = `-----BEGIN CERTIFICATE-----
MIICHDCCAcKgAwIBAgIJAJ/qse7uYF0LMAoGCCqGSM49BAMCMGExCzAJBgNVBAYT
AkdCMRAwDgYDVQQIDAdFbmdsYW5kMRAwDgYDVQQHDAdJcHN3aWNoMQ4wDAYDVQQK
DAVCYXIxOTEOMAwGA1UECwwFQmFyMTkxDjAMBgNVBAMMBUJhcjE5MB4XDTE3MDIw
OTE1MzE1MloXDTM3MDIwNDE1MzE1MlowYTELMAkGA1UEBhMCR0IxEDAOBgNVBAgM
B0VuZ2xhbmQxEDAOBgNVBAcMB0lwc3dpY2gxDjAMBgNVBAoMBUJhcjE5MQ4wDAYD
VQQLDAVCYXIxOTEOMAwGA1UEAwwFQmFyMTkwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAAQcG4qwA7jeGzgkakV+IYyQH/GwgtOw6+Y3ZabCmw8dk0vrDwdZ7fEI9C10
b9ckm9n4LvnooSxQEzfLDk9N+S7yo2MwYTAdBgNVHQ4EFgQU4UJ1xRnh6zeW2IKA
BUOjIt9Wk8gwHwYDVR0jBBgwFoAU4UJ1xRnh6zeW2IKABUOjIt9Wk8gwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwIDSAAwRQIgGvB0
854QmGi1yG5wnWMiwzQxtcEhvCXbnCuiQvr5VrkCIQDoMooDC/WmhBwuCfo7iGDo
AsFd44a8aa9yzABfALG2Gw==
-----END CERTIFICATE-----`

func TestMSPWithIntermediateCAs(t *testing.T) {
keyinfo := &msp.KeyInfo{KeyIdentifier: "PEER", KeyMaterial: []byte(key)}

sigid := &msp.SigningIdentityInfo{PublicSigner: []byte(signcert), PrivateSigner: keyinfo}

fmspconf := &msp.FabricMSPConfig{
RootCerts: [][]byte{[]byte(cacert)},
IntermediateCerts: [][]byte{[]byte(intermediatecert)},
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)

// This MSP will trust any cert signed by the CA directly OR by the intermediate

id, err := thisMSP.GetDefaultSigningIdentity()
assert.NoError(t, err)

// ensure that we validate correctly the identity
err = thisMSP.Validate(id.GetPublicVersion())
assert.NoError(t, err)

// ensure that validation of an identity of the MSP with intermediate CAs
// fails with the local MSP
err = localMsp.Validate(id.GetPublicVersion())
assert.Error(t, err)

// ensure that validation of an identity of the local MSP
// fails with the MSP with intermediate CAs
localMSPID, err := localMsp.GetDefaultSigningIdentity()
assert.NoError(t, err)
err = thisMSP.Validate(localMSPID.GetPublicVersion())
assert.Error(t, err)
}

0 comments on commit add9ff2

Please sign in to comment.