/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package peer_test import ( "context" "crypto/tls" "crypto/x509" "errors" "net" "testing" "time" cb "github.com/hyperledger/fabric-protos-go/common" mspproto "github.com/hyperledger/fabric-protos-go/msp" pb "github.com/hyperledger/fabric-protos-go/peer" configtxtest "github.com/hyperledger/fabric/common/configtx/test" "github.com/hyperledger/fabric/common/crypto/tlsgen" "github.com/hyperledger/fabric/core/ledger/mock" "github.com/hyperledger/fabric/core/peer" "github.com/hyperledger/fabric/internal/pkg/comm" "github.com/hyperledger/fabric/internal/pkg/comm/testpb" "github.com/hyperledger/fabric/internal/pkg/txflags" "github.com/hyperledger/fabric/msp" "github.com/hyperledger/fabric/protoutil" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) // test server to be registered with the GRPCServer type testServiceServer struct{} func (tss *testServiceServer) EmptyCall(context.Context, *testpb.Empty) (*testpb.Empty, error) { return new(testpb.Empty), nil } // createCertPool creates an x509.CertPool from an array of PEM-encoded certificates func createCertPool(rootCAs [][]byte) (*x509.CertPool, error) { certPool := x509.NewCertPool() for _, rootCA := range rootCAs { if !certPool.AppendCertsFromPEM(rootCA) { return nil, errors.New("Failed to load root certificates") } } return certPool, nil } // helper function to invoke the EmptyCall againt the test service func invokeEmptyCall(address string, dialOptions []grpc.DialOption) (*testpb.Empty, error) { // add DialOptions dialOptions = append(dialOptions, grpc.WithBlock()) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // create GRPC client conn clientConn, err := grpc.DialContext(ctx, address, dialOptions...) if err != nil { return nil, err } defer clientConn.Close() // create GRPC client client := testpb.NewTestServiceClient(clientConn) // invoke service empty, err := client.EmptyCall(context.Background(), new(testpb.Empty)) if err != nil { return nil, err } return empty, nil } // helper function to build an MSPConfig given root certs func createMSPConfig(mspID string, rootCerts, tlsRootCerts, tlsIntermediateCerts [][]byte) (*mspproto.MSPConfig, error) { fmspconf := &mspproto.FabricMSPConfig{ RootCerts: rootCerts, TlsRootCerts: tlsRootCerts, TlsIntermediateCerts: tlsIntermediateCerts, Name: mspID, FabricNodeOus: &mspproto.FabricNodeOUs{ Enable: true, ClientOuIdentifier: &mspproto.FabricOUIdentifier{ OrganizationalUnitIdentifier: "client", }, PeerOuIdentifier: &mspproto.FabricOUIdentifier{ OrganizationalUnitIdentifier: "peer", }, AdminOuIdentifier: &mspproto.FabricOUIdentifier{ OrganizationalUnitIdentifier: "admin", }, }, } return &mspproto.MSPConfig{ Config: protoutil.MarshalOrPanic(fmspconf), Type: int32(msp.FABRIC), }, nil } func createConfigBlock(channelID string, appMSPConf, ordererMSPConf *mspproto.MSPConfig, appOrgID, ordererOrgID string) (*cb.Block, error) { block, err := configtxtest.MakeGenesisBlockFromMSPs(channelID, appMSPConf, ordererMSPConf, appOrgID, ordererOrgID) if block == nil || err != nil { return block, err } txsFilter := txflags.NewWithValues(len(block.Data.Data), pb.TxValidationCode_VALID) block.Metadata.Metadata[cb.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter return block, nil } func TestUpdateRootsFromConfigBlock(t *testing.T) { org1CA, err := tlsgen.NewCA() require.NoError(t, err) org1Server1KeyPair, err := org1CA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1") require.NoError(t, err) org2CA, err := tlsgen.NewCA() require.NoError(t, err) org2Server1KeyPair, err := org2CA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1") require.NoError(t, err) org2IntermediateCA, err := org2CA.NewIntermediateCA() require.NoError(t, err) org2IntermediateServer1KeyPair, err := org2IntermediateCA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1") require.NoError(t, err) ordererOrgCA, err := tlsgen.NewCA() require.NoError(t, err) ordererOrgServer1KeyPair, err := ordererOrgCA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1") require.NoError(t, err) // create test MSPConfigs org1MSPConf, err := createMSPConfig("Org1MSP", [][]byte{org2CA.CertBytes()}, [][]byte{org1CA.CertBytes()}, [][]byte{}) require.NoError(t, err) org2MSPConf, err := createMSPConfig("Org2MSP", [][]byte{org1CA.CertBytes()}, [][]byte{org2CA.CertBytes()}, [][]byte{}) require.NoError(t, err) org2IntermediateMSPConf, err := createMSPConfig("Org2IntermediateMSP", [][]byte{org1CA.CertBytes()}, [][]byte{org2CA.CertBytes()}, [][]byte{org2IntermediateCA.CertBytes()}) require.NoError(t, err) ordererOrgMSPConf, err := createMSPConfig("OrdererOrgMSP", [][]byte{org1CA.CertBytes()}, [][]byte{ordererOrgCA.CertBytes()}, [][]byte{}) require.NoError(t, err) // create test channel create blocks channel1Block, err := createConfigBlock("channel1", org1MSPConf, ordererOrgMSPConf, "Org1MSP", "OrdererOrgMSP") require.NoError(t, err) channel2Block, err := createConfigBlock("channel2", org2MSPConf, ordererOrgMSPConf, "Org2MSP", "OrdererOrgMSP") require.NoError(t, err) channel3Block, err := createConfigBlock("channel3", org2IntermediateMSPConf, ordererOrgMSPConf, "Org2IntermediateMSP", "OrdererOrgMSP") require.NoError(t, err) serverConfig := comm.ServerConfig{ SecOpts: comm.SecureOptions{ UseTLS: true, Certificate: org1Server1KeyPair.Cert, Key: org1Server1KeyPair.Key, ServerRootCAs: [][]byte{org1CA.CertBytes()}, RequireClientCert: true, }, } peerInstance, cleanup := peer.NewTestPeer(t) defer cleanup() peerInstance.CredentialSupport = comm.NewCredentialSupport() createChannel := func(t *testing.T, cid string, block *cb.Block) { err = peerInstance.CreateChannel(cid, block, &mock.DeployedChaincodeInfoProvider{}, nil, nil) require.NoError(t, err, "failed to create channel from block") t.Logf("Channel %s MSPIDs: (%s)", cid, peerInstance.GetMSPIDs(cid)) } org1CertPool, err := createCertPool([][]byte{org1CA.CertBytes()}) require.NoError(t, err) // use server cert as client cert org1ClientCert, err := tls.X509KeyPair(org1Server1KeyPair.Cert, org1Server1KeyPair.Key) require.NoError(t, err) org1Creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{org1ClientCert}, RootCAs: org1CertPool, }) org2ClientCert, err := tls.X509KeyPair(org2Server1KeyPair.Cert, org2Server1KeyPair.Key) require.NoError(t, err) org2Creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{org2ClientCert}, RootCAs: org1CertPool, }) org2IntermediateClientCert, err := tls.X509KeyPair(org2IntermediateServer1KeyPair.Cert, org2IntermediateServer1KeyPair.Key) require.NoError(t, err) org2IntermediateCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{org2IntermediateClientCert}, RootCAs: org1CertPool, }) ordererOrgClientCert, err := tls.X509KeyPair(ordererOrgServer1KeyPair.Cert, ordererOrgServer1KeyPair.Key) require.NoError(t, err) ordererOrgCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{ordererOrgClientCert}, RootCAs: org1CertPool, }) // basic function tests tests := []struct { name string serverConfig comm.ServerConfig createChannel func(*testing.T) goodOptions []grpc.DialOption badOptions []grpc.DialOption numAppCAs int numOrdererCAs int }{ { name: "MutualTLSOrg1Org1", serverConfig: serverConfig, createChannel: func(t *testing.T) { createChannel(t, "channel1", channel1Block) }, goodOptions: []grpc.DialOption{grpc.WithTransportCredentials(org1Creds)}, badOptions: []grpc.DialOption{grpc.WithTransportCredentials(ordererOrgCreds)}, numAppCAs: 3, // each channel also has a DEFAULT MSP numOrdererCAs: 1, }, { name: "MutualTLSOrg1Org2", serverConfig: serverConfig, createChannel: func(t *testing.T) { createChannel(t, "channel2", channel2Block) }, goodOptions: []grpc.DialOption{ grpc.WithTransportCredentials(org2Creds), }, badOptions: []grpc.DialOption{ grpc.WithTransportCredentials(ordererOrgCreds), }, numAppCAs: 6, numOrdererCAs: 2, }, { name: "MutualTLSOrg1Org2Intermediate", serverConfig: serverConfig, createChannel: func(t *testing.T) { createChannel(t, "channel3", channel3Block) }, goodOptions: []grpc.DialOption{ grpc.WithTransportCredentials(org2IntermediateCreds), }, badOptions: []grpc.DialOption{ grpc.WithTransportCredentials(ordererOrgCreds), }, numAppCAs: 10, numOrdererCAs: 3, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { server, err := comm.NewGRPCServer("localhost:0", test.serverConfig) require.NoError(t, err, "failed to create gRPC server") require.NotNil(t, server) peerInstance.SetServer(server) peerInstance.ServerConfig = test.serverConfig // register a GRPC test service testpb.RegisterTestServiceServer(server.Server(), &testServiceServer{}) go server.Start() defer server.Stop() // extract dynamic listen port _, port, err := net.SplitHostPort(server.Listener().Addr().String()) require.NoError(t, err, "unable to extract listener port") testAddress := "localhost:" + port // invoke the EmptyCall service with good options but should fail // until channel is created and root CAs are updated _, err = invokeEmptyCall(testAddress, test.goodOptions) require.Error(t, err, "Expected error invoking the EmptyCall service ") // creating channel should update the trusted client roots test.createChannel(t) // invoke the EmptyCall service with good options _, err = invokeEmptyCall(testAddress, test.goodOptions) require.NoError(t, err, "Failed to invoke the EmptyCall service") // invoke the EmptyCall service with bad options _, err = invokeEmptyCall(testAddress, test.badOptions) require.Error(t, err, "Expected error using bad dial options") }) } }