216 lines
6.7 KiB
Go
216 lines
6.7 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package deliver
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"net"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric/internal/pkg/comm"
|
|
"github.com/hyperledger/fabric/internal/pkg/comm/testpb"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
func TestBindingInspectorBadInit(t *testing.T) {
|
|
require.Panics(t, func() { NewBindingInspector(false, nil) })
|
|
}
|
|
|
|
func TestNoopBindingInspector(t *testing.T) {
|
|
extract := func(msg proto.Message) []byte {
|
|
return nil
|
|
}
|
|
require.Nil(t, NewBindingInspector(false, extract)(context.Background(), &common.Envelope{}))
|
|
err := NewBindingInspector(false, extract)(context.Background(), nil)
|
|
require.Error(t, err)
|
|
require.Equal(t, "message is nil", err.Error())
|
|
}
|
|
|
|
func TestBindingInspector(t *testing.T) {
|
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to create listener for test server: %v", err)
|
|
}
|
|
|
|
extract := func(msg proto.Message) []byte {
|
|
env, isEnvelope := msg.(*common.Envelope)
|
|
if !isEnvelope || env == nil {
|
|
return nil
|
|
}
|
|
ch, err := protoutil.ChannelHeader(env)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return ch.TlsCertHash
|
|
}
|
|
srv := newInspectingServer(lis, NewBindingInspector(true, extract))
|
|
go srv.Start()
|
|
defer srv.Stop()
|
|
time.Sleep(time.Second)
|
|
|
|
// Scenario I: Invalid header sent
|
|
err = srv.newInspection(t).inspectBinding(nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "client didn't include its TLS cert hash")
|
|
|
|
// Scenario II: invalid channel header
|
|
ch, _ := proto.Marshal(protoutil.MakeChannelHeader(common.HeaderType_CONFIG, 0, "test", 0))
|
|
// Corrupt channel header
|
|
ch = append(ch, 0)
|
|
err = srv.newInspection(t).inspectBinding(envelopeWithChannelHeader(ch))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "client didn't include its TLS cert hash")
|
|
|
|
// Scenario III: No TLS cert hash in envelope
|
|
chanHdr := protoutil.MakeChannelHeader(common.HeaderType_CONFIG, 0, "test", 0)
|
|
ch, _ = proto.Marshal(chanHdr)
|
|
err = srv.newInspection(t).inspectBinding(envelopeWithChannelHeader(ch))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "client didn't include its TLS cert hash")
|
|
|
|
// Scenario IV: Client sends its TLS cert hash as needed, but doesn't use mutual TLS
|
|
cert, _ := tls.X509KeyPair([]byte(selfSignedCertPEM), []byte(selfSignedKeyPEM))
|
|
h := sha256.New()
|
|
h.Write([]byte(cert.Certificate[0]))
|
|
chanHdr.TlsCertHash = h.Sum(nil)
|
|
ch, _ = proto.Marshal(chanHdr)
|
|
err = srv.newInspection(t).inspectBinding(envelopeWithChannelHeader(ch))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "client didn't send a TLS certificate")
|
|
|
|
// Scenario V: Client uses mutual TLS but sends the wrong TLS cert hash
|
|
chanHdr.TlsCertHash = []byte{1, 2, 3}
|
|
chHdrWithWrongTLSCertHash, _ := proto.Marshal(chanHdr)
|
|
err = srv.newInspection(t).withMutualTLS().inspectBinding(envelopeWithChannelHeader(chHdrWithWrongTLSCertHash))
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "claimed TLS cert hash is [1 2 3] but actual TLS cert hash is")
|
|
|
|
// Scenario VI: Client uses mutual TLS and also sends the correct TLS cert hash
|
|
err = srv.newInspection(t).withMutualTLS().inspectBinding(envelopeWithChannelHeader(ch))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
type inspectingServer struct {
|
|
addr string
|
|
*comm.GRPCServer
|
|
lastContext atomic.Value
|
|
inspector BindingInspector
|
|
}
|
|
|
|
func (is *inspectingServer) EmptyCall(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
|
|
is.lastContext.Store(ctx)
|
|
return &testpb.Empty{}, nil
|
|
}
|
|
|
|
func (is *inspectingServer) inspect(envelope *common.Envelope) error {
|
|
return is.inspector(is.lastContext.Load().(context.Context), envelope)
|
|
}
|
|
|
|
func newInspectingServer(listener net.Listener, inspector BindingInspector) *inspectingServer {
|
|
srv, err := comm.NewGRPCServerFromListener(listener, comm.ServerConfig{
|
|
ConnectionTimeout: 250 * time.Millisecond,
|
|
SecOpts: comm.SecureOptions{
|
|
UseTLS: true,
|
|
Certificate: []byte(selfSignedCertPEM),
|
|
Key: []byte(selfSignedKeyPEM),
|
|
},
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
is := &inspectingServer{
|
|
addr: listener.Addr().String(),
|
|
GRPCServer: srv,
|
|
inspector: inspector,
|
|
}
|
|
testpb.RegisterTestServiceServer(srv.Server(), is)
|
|
return is
|
|
}
|
|
|
|
type inspection struct {
|
|
tlsConfig *tls.Config
|
|
server *inspectingServer
|
|
creds credentials.TransportCredentials
|
|
t *testing.T
|
|
}
|
|
|
|
func (is *inspectingServer) newInspection(t *testing.T) *inspection {
|
|
tlsConfig := &tls.Config{
|
|
RootCAs: x509.NewCertPool(),
|
|
}
|
|
tlsConfig.RootCAs.AppendCertsFromPEM([]byte(selfSignedCertPEM))
|
|
return &inspection{
|
|
server: is,
|
|
creds: credentials.NewTLS(tlsConfig),
|
|
t: t,
|
|
tlsConfig: tlsConfig,
|
|
}
|
|
}
|
|
|
|
func (ins *inspection) withMutualTLS() *inspection {
|
|
cert, err := tls.X509KeyPair([]byte(selfSignedCertPEM), []byte(selfSignedKeyPEM))
|
|
require.NoError(ins.t, err)
|
|
ins.tlsConfig.Certificates = []tls.Certificate{cert}
|
|
ins.creds = credentials.NewTLS(ins.tlsConfig)
|
|
return ins
|
|
}
|
|
|
|
func (ins *inspection) inspectBinding(envelope *common.Envelope) error {
|
|
ctx := context.Background()
|
|
ctx, c := context.WithTimeout(ctx, time.Second*3)
|
|
defer c()
|
|
conn, err := grpc.DialContext(ctx, ins.server.addr, grpc.WithTransportCredentials(ins.creds), grpc.WithBlock())
|
|
require.NoError(ins.t, err)
|
|
defer conn.Close()
|
|
_, err = testpb.NewTestServiceClient(conn).EmptyCall(context.Background(), &testpb.Empty{})
|
|
require.NoError(ins.t, err)
|
|
return ins.server.inspect(envelope)
|
|
}
|
|
|
|
func envelopeWithChannelHeader(ch []byte) *common.Envelope {
|
|
pl := &common.Payload{
|
|
Header: &common.Header{
|
|
ChannelHeader: ch,
|
|
},
|
|
}
|
|
payload, _ := proto.Marshal(pl)
|
|
return &common.Envelope{
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
// Embedded certificates for testing
|
|
// The self-signed cert expires in 2028
|
|
var selfSignedKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIMLemLh3+uDzww1pvqP6Xj2Z0Kc6yqf3RxyfTBNwRuuyoAoGCCqGSM49
|
|
AwEHoUQDQgAEDB3l94vM7EqKr2L/vhqU5IsEub0rviqCAaWGiVAPp3orb/LJqFLS
|
|
yo/k60rhUiir6iD4S4pb5TEb2ouWylQI3A==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
|
|
var selfSignedCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIBdDCCARqgAwIBAgIRAKCiW5r6W32jGUn+l9BORMAwCgYIKoZIzj0EAwIwEjEQ
|
|
MA4GA1UEChMHQWNtZSBDbzAeFw0xODA4MjExMDI1MzJaFw0yODA4MTgxMDI1MzJa
|
|
MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQM
|
|
HeX3i8zsSoqvYv++GpTkiwS5vSu+KoIBpYaJUA+neitv8smoUtLKj+TrSuFSKKvq
|
|
IPhLilvlMRvai5bKVAjco1EwTzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYI
|
|
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
|
|
AAEwCgYIKoZIzj0EAwIDSAAwRQIgOaYc3pdGf2j0uXRyvdBJq2PlK9FkgvsUjXOT
|
|
bQ9fWRkCIQCr1FiRRzapgtrnttDn3O2fhLlbrw67kClzY8pIIN42Qw==
|
|
-----END CERTIFICATE-----
|
|
`
|