github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/accesscontrol/access_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package accesscontrol 8 9 import ( 10 "context" 11 "crypto/tls" 12 "crypto/x509" 13 "fmt" 14 "net" 15 "testing" 16 "time" 17 18 "github.com/golang/protobuf/proto" 19 pb "github.com/hyperledger/fabric-protos-go/peer" 20 "github.com/hyperledger/fabric/common/crypto/tlsgen" 21 "github.com/hyperledger/fabric/common/flogging/floggingtest" 22 "github.com/stretchr/testify/assert" 23 "go.uber.org/zap/zapcore" 24 "google.golang.org/grpc" 25 "google.golang.org/grpc/credentials" 26 ) 27 28 type ccSrv struct { 29 l net.Listener 30 grpcSrv *grpc.Server 31 t *testing.T 32 expectedCCname string 33 } 34 35 func (cs *ccSrv) Register(stream pb.ChaincodeSupport_RegisterServer) error { 36 msg, err := stream.Recv() 37 if err != nil { 38 return err 39 } 40 41 // First message is a register message 42 assert.Equal(cs.t, pb.ChaincodeMessage_REGISTER.String(), msg.Type.String()) 43 // And its chaincode name is the expected one 44 chaincodeID := &pb.ChaincodeID{} 45 err = proto.Unmarshal(msg.Payload, chaincodeID) 46 if err != nil { 47 return err 48 } 49 assert.Equal(cs.t, cs.expectedCCname, chaincodeID.Name) 50 // Subsequent messages are just echoed back 51 for { 52 msg, _ = stream.Recv() 53 if err != nil { 54 return err 55 } 56 err = stream.Send(msg) 57 if err != nil { 58 return err 59 } 60 } 61 } 62 63 func (cs *ccSrv) stop() { 64 cs.grpcSrv.Stop() 65 cs.l.Close() 66 } 67 68 func createTLSService(t *testing.T, ca tlsgen.CA, host string) *grpc.Server { 69 keyPair, err := ca.NewServerCertKeyPair(host) 70 assert.NoError(t, err) 71 cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key) 72 assert.NoError(t, err) 73 tlsConf := &tls.Config{ 74 Certificates: []tls.Certificate{cert}, 75 ClientAuth: tls.RequireAndVerifyClientCert, 76 ClientCAs: x509.NewCertPool(), 77 } 78 tlsConf.ClientCAs.AppendCertsFromPEM(ca.CertBytes()) 79 return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConf))) 80 } 81 82 func newCCServer(t *testing.T, port int, expectedCCname string, withTLS bool, ca tlsgen.CA) *ccSrv { 83 var s *grpc.Server 84 if withTLS { 85 s = createTLSService(t, ca, "localhost") 86 } else { 87 s = grpc.NewServer() 88 } 89 90 l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "", port)) 91 assert.NoError(t, err, "%v", err) 92 return &ccSrv{ 93 t: t, 94 expectedCCname: expectedCCname, 95 l: l, 96 grpcSrv: s, 97 } 98 } 99 100 type ccClient struct { 101 conn *grpc.ClientConn 102 stream pb.ChaincodeSupport_RegisterClient 103 } 104 105 func newClient(t *testing.T, port int, cert *tls.Certificate, peerCACert []byte) (*ccClient, error) { 106 tlsCfg := &tls.Config{ 107 RootCAs: x509.NewCertPool(), 108 } 109 110 tlsCfg.RootCAs.AppendCertsFromPEM(peerCACert) 111 if cert != nil { 112 tlsCfg.Certificates = []tls.Certificate{*cert} 113 } 114 tlsOpts := grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)) 115 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 116 defer cancel() 117 conn, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), tlsOpts, grpc.WithBlock()) 118 if err != nil { 119 return nil, err 120 } 121 chaincodeSupportClient := pb.NewChaincodeSupportClient(conn) 122 stream, err := chaincodeSupportClient.Register(context.Background()) 123 assert.NoError(t, err) 124 return &ccClient{ 125 conn: conn, 126 stream: stream, 127 }, nil 128 } 129 130 func (c *ccClient) close() { 131 c.conn.Close() 132 } 133 134 func (c *ccClient) sendMsg(msg *pb.ChaincodeMessage) { 135 c.stream.Send(msg) 136 } 137 138 func (c *ccClient) recv() *pb.ChaincodeMessage { 139 msgs := make(chan *pb.ChaincodeMessage, 1) 140 go func() { 141 msg, _ := c.stream.Recv() 142 if msg != nil { 143 msgs <- msg 144 } 145 }() 146 select { 147 case <-time.After(time.Second): 148 return nil 149 case msg := <-msgs: 150 return msg 151 } 152 } 153 154 func TestAccessControl(t *testing.T) { 155 backupTTL := ttl 156 defer func() { 157 ttl = backupTTL 158 }() 159 ttl = time.Second * 3 160 161 oldLogger := logger 162 l, recorder := floggingtest.NewTestLogger(t, floggingtest.AtLevel(zapcore.InfoLevel)) 163 logger = l 164 defer func() { logger = oldLogger }() 165 166 chaincodeID := &pb.ChaincodeID{Name: "example02"} 167 payload, err := proto.Marshal(chaincodeID) 168 assert.NoError(t, err) 169 registerMsg := &pb.ChaincodeMessage{ 170 Type: pb.ChaincodeMessage_REGISTER, 171 Payload: payload, 172 } 173 putStateMsg := &pb.ChaincodeMessage{ 174 Type: pb.ChaincodeMessage_PUT_STATE, 175 } 176 177 ca, _ := tlsgen.NewCA() 178 srv := newCCServer(t, 7052, "example02", true, ca) 179 auth := NewAuthenticator(ca) 180 pb.RegisterChaincodeSupportServer(srv.grpcSrv, auth.Wrap(srv)) 181 go srv.grpcSrv.Serve(srv.l) 182 defer srv.stop() 183 184 // Create an attacker without a TLS certificate 185 _, err = newClient(t, 7052, nil, ca.CertBytes()) 186 assert.Error(t, err) 187 assert.Contains(t, err.Error(), "context deadline exceeded") 188 189 // Create an attacker with its own TLS certificate 190 maliciousCA, _ := tlsgen.NewCA() 191 keyPair, err := maliciousCA.NewClientCertKeyPair() 192 assert.NoError(t, err) 193 cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key) 194 assert.NoError(t, err) 195 _, err = newClient(t, 7052, &cert, ca.CertBytes()) 196 assert.Error(t, err) 197 assert.Contains(t, err.Error(), "context deadline exceeded") 198 199 // Create a chaincode for example01 that tries to impersonate example02 200 kp, err := auth.Generate("example01") 201 assert.NoError(t, err) 202 cert, err = tls.X509KeyPair(kp.Cert, kp.Key) 203 assert.NoError(t, err) 204 mismatchedShim, err := newClient(t, 7052, &cert, ca.CertBytes()) 205 assert.NoError(t, err) 206 defer mismatchedShim.close() 207 mismatchedShim.sendMsg(registerMsg) 208 mismatchedShim.sendMsg(putStateMsg) 209 // Mismatched chaincode didn't get back anything 210 assert.Nil(t, mismatchedShim.recv()) 211 assertLogContains(t, recorder, "with given certificate hash", "belongs to a different chaincode") 212 213 // Create the real chaincode that its cert is generated by us that should pass the security checks 214 kp, err = auth.Generate("example02") 215 assert.NoError(t, err) 216 cert, err = tls.X509KeyPair(kp.Cert, kp.Key) 217 assert.NoError(t, err) 218 realCC, err := newClient(t, 7052, &cert, ca.CertBytes()) 219 assert.NoError(t, err) 220 defer realCC.close() 221 realCC.sendMsg(registerMsg) 222 realCC.sendMsg(putStateMsg) 223 echoMsg := realCC.recv() 224 // The real chaincode should be echoed back its message 225 assert.NotNil(t, echoMsg) 226 assert.Equal(t, pb.ChaincodeMessage_PUT_STATE, echoMsg.Type) 227 // Log should not complain about anything 228 assert.Empty(t, recorder.Messages()) 229 230 // Create the real chaincode that its cert is generated by us 231 // but one that the first message sent by it isn't a register message. 232 // The second message that is sent is a register message but it's "too late" 233 // and the stream is already denied. 234 kp, err = auth.Generate("example02") 235 assert.NoError(t, err) 236 cert, err = tls.X509KeyPair(kp.Cert, kp.Key) 237 assert.NoError(t, err) 238 confusedCC, err := newClient(t, 7052, &cert, ca.CertBytes()) 239 assert.NoError(t, err) 240 defer confusedCC.close() 241 confusedCC.sendMsg(putStateMsg) 242 confusedCC.sendMsg(registerMsg) 243 confusedCC.sendMsg(putStateMsg) 244 assert.Nil(t, confusedCC.recv()) 245 assertLogContains(t, recorder, "expected a ChaincodeMessage_REGISTER message") 246 247 // Create a real chaincode, that its cert was generated by us 248 // but it sends a malformed first message 249 kp, err = auth.Generate("example02") 250 assert.NoError(t, err) 251 cert, err = tls.X509KeyPair(kp.Cert, kp.Key) 252 assert.NoError(t, err) 253 malformedMessageCC, err := newClient(t, 7052, &cert, ca.CertBytes()) 254 assert.NoError(t, err) 255 defer malformedMessageCC.close() 256 // Save old payload 257 originalPayload := registerMsg.Payload 258 registerMsg.Payload = append(registerMsg.Payload, 0) 259 malformedMessageCC.sendMsg(registerMsg) 260 malformedMessageCC.sendMsg(putStateMsg) 261 assert.Nil(t, malformedMessageCC.recv()) 262 assertLogContains(t, recorder, "Failed unmarshaling message") 263 // Recover old payload 264 registerMsg.Payload = originalPayload 265 266 // Create a real chaincode, that its cert was generated by us 267 // but have it reconnect only after too much time. 268 // This tests a use case where the CC's cert has been expired 269 // and the CC has been compromised. We don't want it to be able 270 // to reconnect to us. 271 kp, err = auth.Generate("example02") 272 assert.NoError(t, err) 273 cert, err = tls.X509KeyPair(kp.Cert, kp.Key) 274 assert.NoError(t, err) 275 lateCC, err := newClient(t, 7052, &cert, ca.CertBytes()) 276 assert.NoError(t, err) 277 defer lateCC.close() 278 time.Sleep(ttl + time.Second*2) 279 lateCC.sendMsg(registerMsg) 280 lateCC.sendMsg(putStateMsg) 281 echoMsg = lateCC.recv() 282 assert.Nil(t, echoMsg) 283 assertLogContains(t, recorder, "with given certificate hash", "not found in registry") 284 } 285 286 func assertLogContains(t *testing.T, r *floggingtest.Recorder, ss ...string) { 287 defer r.Reset() 288 for _, s := range ss { 289 assert.NotEmpty(t, r.MessagesContaining(s)) 290 } 291 }