From 2c0e72a8f967ce78a3b11a1f4505fe1752d73778 Mon Sep 17 00:00:00 2001 From: Gari Singh Date: Sun, 22 Jan 2017 05:44:27 -0500 Subject: [PATCH] [FAB-1799] Add function to replace client root CAs The GRPCServer had methods for appending and removing client root certificates but did not have a method to set/update the list in its entirety. * Add SetClientRootCAs function to GRPCServer * Add new tests and update existing tests Change-Id: I8159ae9a0cea663a882b5bcaecc184b4934ab8eb Signed-off-by: Gari Singh --- core/comm/server.go | 38 ++++++++++ core/comm/server_test.go | 152 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) diff --git a/core/comm/server.go b/core/comm/server.go index 62508db1374..aa91e127d68 100644 --- a/core/comm/server.go +++ b/core/comm/server.go @@ -71,6 +71,9 @@ type GRPCServer interface { //RemoveClientRootCAs removes PEM-encoded X509 certificate authorities from //the list of authorities used to verify client certificates RemoveClientRootCAs(clientRoots [][]byte) error + //SetClientRootCAs sets the list of authorities used to verify client + //certificates based on a list of PEM-encoded X509 certificate authorities + SetClientRootCAs(clientRoots [][]byte) error } type grpcServerImpl struct { @@ -302,6 +305,41 @@ func (gServer *grpcServerImpl) removeClientRootCA(clientRoot []byte) error { return nil } +//SetClientRootCAs sets the list of authorities used to verify client +//certificates based on a list of PEM-encoded X509 certificate authorities +func (gServer *grpcServerImpl) SetClientRootCAs(clientRoots [][]byte) error { + gServer.lock.Lock() + defer gServer.lock.Unlock() + + errMsg := "Failed to set client root certificate(s): %s" + + //create a new map and CertPool + clientRootCAs := make(map[string]*x509.Certificate) + for _, clientRoot := range clientRoots { + certs, subjects, err := pemToX509Certs(clientRoot) + if err != nil { + return fmt.Errorf(errMsg, err.Error()) + } + if len(certs) >= 1 { + for i, cert := range certs { + //add it to our clientRootCAs map using subject as key + clientRootCAs[subjects[i]] = cert + } + } + } + + //create a new CertPool and populate with the new clientRootCAs + certPool := x509.NewCertPool() + for _, clientRoot := range clientRootCAs { + certPool.AddCert(clientRoot) + } + //replace the internal map + gServer.clientRootCAs = clientRootCAs + //replace the current ClientCAs pool + gServer.tlsConfig.ClientCAs = certPool + return nil +} + //utility function to parse PEM-encoded certs func pemToX509Certs(pemCerts []byte) ([]*x509.Certificate, []string, error) { diff --git a/core/comm/server_test.go b/core/comm/server_test.go index fe101c909f0..792bb545c99 100644 --- a/core/comm/server_test.go +++ b/core/comm/server_test.go @@ -461,6 +461,22 @@ func TestNewGRPCServerInvalidParameters(t *testing.T) { if err != nil { t.Log(err.Error()) } + + srv, err := comm.NewGRPCServer(":9046", + comm.SecureServerConfig{ + UseTLS: true, + ServerCertificate: []byte(selfSignedCertPEM), + ServerKey: []byte(selfSignedKeyPEM), + RequireClientCert: true}) + badRootCAs := [][]byte{[]byte(badPEM)} + err = srv.SetClientRootCAs(badRootCAs) + //check for error + msg = "Failed to set client root certificate(s): " + + "asn1: syntax error: data truncated" + assert.EqualError(t, err, msg) + if err != nil { + t.Log(err.Error()) + } } func TestNewGRPCServer(t *testing.T) { @@ -1146,8 +1162,8 @@ func TestRemoveClientRootCAs(t *testing.T) { } -//test for race conditions - test locally using "go test -race -run TestConcurrentAppendRemove" -func TestConcurrentAppendRemove(t *testing.T) { +//test for race conditions - test locally using "go test -race -run TestConcurrentAppendRemoveSet" +func TestConcurrentAppendRemoveSet(t *testing.T) { t.Parallel() //get the config for one of our Org1 test servers and include client CAs from @@ -1183,6 +1199,18 @@ func TestConcurrentAppendRemove(t *testing.T) { }() + wg.Add(1) + go func() { + defer wg.Done() + //set client root CAs + err := srv.SetClientRootCAs([][]byte{testOrgs[1].childOrgs[0].rootCA, + testOrgs[1].childOrgs[1].rootCA}) + if err != nil { + t.Fatal("Failed to set client root CAs") + } + + }() + //TODO: enable this after creating a custom type for grpc.TransportCredentials /* clientConfig := testOrgs[1].childOrgs[0].trustedClients([][]byte{testOrgs[0].rootCA})[0] @@ -1204,6 +1232,126 @@ func TestConcurrentAppendRemove(t *testing.T) { } }() + wg.Add(1) + go func() { + defer wg.Done() + //set client root CAs + err := srv.SetClientRootCAs([][]byte{testOrgs[1].childOrgs[0].rootCA, + testOrgs[1].childOrgs[1].rootCA}) + if err != nil { + t.Fatal("Failed to set client root CAs") + } + + }() + wg.Wait() } + +func TestSetClientRootCAs(t *testing.T) { + + t.Parallel() + + //get the config for one of our Org1 test servers + serverConfig := testOrgs[0].testServers(9303, [][]byte{})[0].config + address := testOrgs[0].testServers(9303, [][]byte{})[0].address + + //create a GRPCServer + srv, err := comm.NewGRPCServer(address, serverConfig) + if err != nil { + t.Fatalf("Failed to create GRPCServer due to: %s", err.Error()) + } + + //register the GRPC test server and start the GRPCServer + testpb.RegisterTestServiceServer(srv.Server(), &testServiceServer{}) + go srv.Start() + defer srv.Stop() + //should not be needed but just in case + time.Sleep(10 * time.Millisecond) + + //set up out test clients + //Org1 + clientConfigOrg1Child1 := testOrgs[0].childOrgs[0].trustedClients([][]byte{testOrgs[0].rootCA})[0] + clientConfigOrg1Child2 := testOrgs[0].childOrgs[1].trustedClients([][]byte{testOrgs[0].rootCA})[0] + clientConfigsOrg1Children := []*tls.Config{clientConfigOrg1Child1, clientConfigOrg1Child2} + org1ChildRootCAs := [][]byte{testOrgs[0].childOrgs[0].rootCA, + testOrgs[0].childOrgs[1].rootCA} + //Org2 + clientConfigOrg2Child1 := testOrgs[1].childOrgs[0].trustedClients([][]byte{testOrgs[0].rootCA})[0] + clientConfigOrg2Child2 := testOrgs[1].childOrgs[1].trustedClients([][]byte{testOrgs[0].rootCA})[0] + clientConfigsOrg2Children := []*tls.Config{clientConfigOrg2Child1, clientConfigOrg2Child2} + org2ChildRootCAs := [][]byte{testOrgs[1].childOrgs[0].rootCA, + testOrgs[1].childOrgs[1].rootCA} + + //initially set client CAs to Org1 children + err = srv.SetClientRootCAs(org1ChildRootCAs) + if err != nil { + t.Fatalf("SetClientRootCAs failed due to: %s", err.Error()) + } + + //clientConfigsOrg1Children are currently trusted + for i, clientConfig := range clientConfigsOrg1Children { + //invoke the EmptyCall service + _, err = invokeEmptyCall(address, []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(clientConfig))}) + + //we expect success as these are trusted clients + if err != nil { + t.Fatalf("Trusted client%d failed to connect to %s with error: %s", + i, address, err.Error()) + } else { + t.Logf("Trusted client%d successfully connected to %s", i, address) + } + } + + //clientConfigsOrg2Children are currently not trusted + for j, clientConfig := range clientConfigsOrg2Children { + //invoke the EmptyCall service + _, err = invokeEmptyCall(address, []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(clientConfig))}) + //we expect failure as these are now untrusted clients + if err != nil { + t.Logf("Untrusted client%d was correctly rejected by %s", j, address) + } else { + t.Fatalf("Untrusted client %d should not have been able to connect to %s", j, + address) + } + } + + //now set client CAs to Org2 children + err = srv.SetClientRootCAs(org2ChildRootCAs) + if err != nil { + t.Fatalf("SetClientRootCAs failed due to: %s", err.Error()) + } + + //now reverse trusted and not trusted + //clientConfigsOrg1Children are currently trusted + for i, clientConfig := range clientConfigsOrg2Children { + //invoke the EmptyCall service + _, err = invokeEmptyCall(address, []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(clientConfig))}) + + //we expect success as these are trusted clients + if err != nil { + t.Fatalf("Trusted client%d failed to connect to %s with error: %s", + i, address, err.Error()) + } else { + t.Logf("Trusted client%d successfully connected to %s", i, address) + } + } + + //clientConfigsOrg2Children are currently not trusted + for j, clientConfig := range clientConfigsOrg1Children { + //invoke the EmptyCall service + _, err = invokeEmptyCall(address, []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(clientConfig))}) + //we expect failure as these are now untrusted clients + if err != nil { + t.Logf("Untrusted client%d was correctly rejected by %s", j, address) + } else { + t.Fatalf("Untrusted client %d should not have been able to connect to %s", j, + address) + } + } + +}