Skip to content

Commit

Permalink
[FAB-5406] Mutual TLS in chaincode service-P1
Browse files Browse the repository at this point in the history
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
yacovm committed Aug 4, 2017
1 parent 269151c commit c7fe108
Show file tree
Hide file tree
Showing 5 changed files with 416 additions and 0 deletions.
45 changes: 45 additions & 0 deletions core/chaincode/accesscontrol/ca.go
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)
}
91 changes: 91 additions & 0 deletions core/chaincode/accesscontrol/ca_test.go
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")
}
105 changes: 105 additions & 0 deletions core/chaincode/accesscontrol/key.go
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
}
116 changes: 116 additions & 0 deletions core/chaincode/accesscontrol/mapper.go
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)
}
Loading

0 comments on commit c7fe108

Please sign in to comment.