-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-5406] Mutual TLS in chaincode service-P1
This commit adds the needed infrastructure to implement an interceptor that would wrap the existing chaincode service (chaincode Support struct) and would check that the chaincode name in the REGISTER message matches the mapping that was set at the chaincode launch. The full details of the flow is in the JIRA item. Change-Id: I2a8d33fc33adf845984b73e3ab1010c34914c716 Signed-off-by: yacovm <[email protected]>
- Loading branch information
Showing
5 changed files
with
416 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package accesscontrol | ||
|
||
// CA defines a certificate authority that can generate | ||
// certificates signed by it | ||
type CA interface { | ||
// CertBytes returns the certificate of the CA in PEM encoding | ||
CertBytes() []byte | ||
|
||
// newCertKeyPair returns a certificate and private key pair and nil, | ||
// or nil, error in case of failure | ||
// The certificate is signed by the CA | ||
newCertKeyPair() (*certKeyPair, error) | ||
} | ||
|
||
type ca struct { | ||
caCert *certKeyPair | ||
} | ||
|
||
func NewCA() (CA, error) { | ||
c := &ca{} | ||
var err error | ||
c.caCert, err = newCertKeyPair(true, nil, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return c, nil | ||
} | ||
|
||
// CertBytes returns the certificate of the CA in PEM encoding | ||
func (c *ca) CertBytes() []byte { | ||
return c.caCert.certBytes | ||
} | ||
|
||
// newCertKeyPair returns a certificate and private key pair and nil, | ||
// or nil, error in case of failure | ||
// The certificate is signed by the CA | ||
func (c *ca) newCertKeyPair() (*certKeyPair, error) { | ||
return newCertKeyPair(false, c.caCert.Signer, c.caCert.cert) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package accesscontrol | ||
|
||
import ( | ||
"crypto/tls" | ||
"encoding/base64" | ||
"fmt" | ||
"math/rand" | ||
"net" | ||
"testing" | ||
"time" | ||
|
||
"crypto/x509" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials" | ||
) | ||
|
||
func createTLSService(t *testing.T, clientCAcert []byte) *grpc.Server { | ||
ca, err := NewCA() | ||
assert.NoError(t, err) | ||
keyPair, err := ca.newCertKeyPair() | ||
cert, err := tls.X509KeyPair(keyPair.certBytes, keyPair.keyBytes) | ||
assert.NoError(t, err) | ||
tlsConf := &tls.Config{ | ||
Certificates: []tls.Certificate{cert}, | ||
ClientAuth: tls.RequireAndVerifyClientCert, | ||
ClientCAs: x509.NewCertPool(), | ||
} | ||
tlsConf.ClientCAs.AppendCertsFromPEM(clientCAcert) | ||
return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConf))) | ||
} | ||
|
||
func TestTLSCA(t *testing.T) { | ||
// This test checks that the CA can create certificates | ||
// and corresponding keys that are signed by itself | ||
|
||
rand.Seed(time.Now().UnixNano()) | ||
randomPort := 1234 + rand.Intn(1234) // some random port | ||
|
||
ca, err := NewCA() | ||
assert.NoError(t, err) | ||
assert.NotNil(t, ca) | ||
|
||
srv := createTLSService(t, ca.CertBytes()) | ||
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "", randomPort)) | ||
assert.NoError(t, err) | ||
go srv.Serve(l) | ||
defer srv.Stop() | ||
defer l.Close() | ||
|
||
probeTLS := func(kp *certKeyPair) error { | ||
keyBytes, err := base64.StdEncoding.DecodeString(kp.privKeyString()) | ||
assert.NoError(t, err) | ||
certBytes, err := base64.StdEncoding.DecodeString(kp.pubKeyString()) | ||
assert.NoError(t, err) | ||
cert, err := tls.X509KeyPair(certBytes, keyBytes) | ||
tlsCfg := &tls.Config{ | ||
InsecureSkipVerify: true, | ||
Certificates: []tls.Certificate{cert}, | ||
} | ||
tlsOpts := grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)) | ||
conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", randomPort), tlsOpts, grpc.WithBlock(), grpc.WithTimeout(time.Second)) | ||
if err != nil { | ||
return err | ||
} | ||
conn.Close() | ||
return nil | ||
} | ||
|
||
// Good path - use a cert key pair generated from the CA | ||
// that the TLS server started with | ||
kp, err := ca.newCertKeyPair() | ||
assert.NoError(t, err) | ||
err = probeTLS(kp) | ||
assert.NoError(t, err) | ||
|
||
// Bad path - use a cert key pair generated from a foreign CA | ||
foreignCA, _ := NewCA() | ||
kp, err = foreignCA.newCertKeyPair() | ||
assert.NoError(t, err) | ||
err = probeTLS(kp) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "tls: bad certificate") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package accesscontrol | ||
|
||
import ( | ||
"crypto" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/pem" | ||
"math/big" | ||
"time" | ||
) | ||
|
||
type KeyGenFunc func() (*certKeyPair, error) | ||
|
||
type certKeyPair struct { | ||
keyBytes []byte | ||
certBytes []byte | ||
crypto.Signer | ||
cert *x509.Certificate | ||
} | ||
|
||
func (p *certKeyPair) privKeyString() string { | ||
return base64.StdEncoding.EncodeToString(p.keyBytes) | ||
} | ||
|
||
func (p *certKeyPair) pubKeyString() string { | ||
return base64.StdEncoding.EncodeToString(p.certBytes) | ||
} | ||
|
||
func newPrivKey() (*ecdsa.PrivateKey, []byte, error) { | ||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
privBytes, err := x509.MarshalECPrivateKey(privateKey) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return privateKey, privBytes, nil | ||
} | ||
|
||
func newCertTemplate() (x509.Certificate, error) { | ||
sn, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) | ||
if err != nil { | ||
return x509.Certificate{}, err | ||
} | ||
return x509.Certificate{ | ||
NotBefore: time.Now().Add(time.Hour * (-24)), | ||
NotAfter: time.Now().Add(time.Hour * 24), | ||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
SerialNumber: sn, | ||
}, nil | ||
} | ||
|
||
func newCertKeyPair(isCA bool, certSigner crypto.Signer, parent *x509.Certificate) (*certKeyPair, error) { | ||
privateKey, privBytes, err := newPrivKey() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
template, err := newCertTemplate() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if isCA { | ||
template.NotAfter = time.Now().Add(time.Hour * 24 * 365 * 10) | ||
template.IsCA = true | ||
template.KeyUsage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign | ||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageAny} | ||
template.BasicConstraintsValid = true | ||
} else { | ||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} | ||
} | ||
// If no parent cert, it's a self signed cert | ||
if parent == nil || certSigner == nil { | ||
parent = &template | ||
certSigner = privateKey | ||
} | ||
rawBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, &privateKey.PublicKey, certSigner) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pubKey := encodePEM("CERTIFICATE", rawBytes) | ||
|
||
block, _ := pem.Decode(pubKey) | ||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
privKey := encodePEM("EC PRIVATE KEY", privBytes) | ||
return &certKeyPair{ | ||
Signer: privateKey, | ||
keyBytes: privKey, | ||
certBytes: pubKey, | ||
cert: cert, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package accesscontrol | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/pem" | ||
"sync" | ||
"time" | ||
|
||
"github.com/hyperledger/fabric/common/util" | ||
"github.com/spf13/viper" | ||
"golang.org/x/net/context" | ||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/peer" | ||
) | ||
|
||
var ttl = time.Minute * 10 | ||
|
||
type certHash string | ||
|
||
type certMapper struct { | ||
keyGen KeyGenFunc | ||
sync.RWMutex | ||
m map[certHash]string | ||
tls bool | ||
} | ||
|
||
func newCertMapper(keyGen KeyGenFunc) *certMapper { | ||
return &certMapper{ | ||
keyGen: keyGen, | ||
tls: viper.GetBool("peer.tls.enabled"), | ||
m: make(map[certHash]string), | ||
} | ||
} | ||
|
||
func (r *certMapper) lookup(h certHash) string { | ||
r.RLock() | ||
defer r.RUnlock() | ||
return r.m[h] | ||
} | ||
|
||
func (r *certMapper) register(hash certHash, name string) { | ||
r.Lock() | ||
defer r.Unlock() | ||
r.m[hash] = name | ||
time.AfterFunc(ttl, func() { | ||
r.purge(hash) | ||
}) | ||
} | ||
|
||
func (r *certMapper) purge(hash certHash) { | ||
r.Lock() | ||
defer r.Unlock() | ||
delete(r.m, hash) | ||
} | ||
|
||
func certKeyPairFromString(privKey string, pubKey string) (*certKeyPair, error) { | ||
priv, err := base64.StdEncoding.DecodeString(privKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pub, err := base64.StdEncoding.DecodeString(pubKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &certKeyPair{ | ||
certBytes: pub, | ||
keyBytes: priv, | ||
}, nil | ||
} | ||
|
||
func (r *certMapper) genCert(name string) (*certKeyPair, error) { | ||
keyPair, err := r.keyGen() | ||
if err != nil { | ||
return nil, err | ||
} | ||
hash := util.ComputeSHA256(keyPair.cert.Raw) | ||
r.register(certHash(hash), name) | ||
return keyPair, nil | ||
} | ||
|
||
func encodePEM(keyType string, data []byte) []byte { | ||
return pem.EncodeToMemory(&pem.Block{Type: keyType, Bytes: data}) | ||
} | ||
|
||
// ExtractCertificateHash extracts the hash of the certificate from the stream | ||
func extractCertificateHashFromContext(ctx context.Context) []byte { | ||
pr, extracted := peer.FromContext(ctx) | ||
if !extracted { | ||
return nil | ||
} | ||
|
||
authInfo := pr.AuthInfo | ||
if authInfo == nil { | ||
return nil | ||
} | ||
|
||
tlsInfo, isTLSConn := authInfo.(credentials.TLSInfo) | ||
if !isTLSConn { | ||
return nil | ||
} | ||
certs := tlsInfo.State.PeerCertificates | ||
if len(certs) == 0 { | ||
return nil | ||
} | ||
raw := certs[0].Raw | ||
if len(raw) == 0 { | ||
return nil | ||
} | ||
return util.ComputeSHA256(raw) | ||
} |
Oops, something went wrong.