github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/consensus/etcdraft/util_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package etcdraft 8 9 import ( 10 "crypto/x509" 11 "encoding/base64" 12 "fmt" 13 "io/ioutil" 14 "path/filepath" 15 "testing" 16 17 "github.com/golang/protobuf/proto" 18 "github.com/hyperledger/fabric-protos-go/common" 19 etcdraftproto "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" 20 "github.com/osdi23p228/fabric/bccsp/sw" 21 "github.com/osdi23p228/fabric/common/crypto/tlsgen" 22 "github.com/osdi23p228/fabric/common/flogging" 23 "github.com/osdi23p228/fabric/orderer/common/cluster" 24 "github.com/osdi23p228/fabric/protoutil" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 ) 28 29 const ( 30 consentersTestDataDir = "testdata/consenters_certs/" 31 ca1Dir = consentersTestDataDir + "ca1" 32 ca2Dir = consentersTestDataDir + "ca2" 33 ) 34 35 func TestIsConsenterOfChannel(t *testing.T) { 36 certInsideConfigBlock, err := base64.StdEncoding.DecodeString("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNmekNDQWlhZ0F3SUJBZ0l" + 37 "SQUo4bjFLYTVzS1ZaTXRMTHJ1dldERDB3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR" + 38 "2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWUR" + 39 "WUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T0RFeE1EWXdPVFE1TURCYUZ3MHlPREV4TURNd09UUTVNREJhTUZreEN6QU" + 40 "oKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SMHdH" + 41 "d1lEVlFRREV4UnZjbVJsY21WeU1TNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkRUVlFZc0" + 42 "ZKZWxUcFZDMDFsek5DSkx6OENRMFFGVDBvN1BmSnBwSkl2SXgKUCtRVjQvRGRCSnRqQ0cvcGsvMGFxZXRpSjhZRUFMYmMrOUhmWnExN2tJ" + 43 "Q2pnYnN3Z2Jnd0RnWURWUjBQQVFILwpCQVFEQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOV" + 44 "khSTUJBZjhFCkFqQUFNQ3NHQTFVZEl3UWtNQ0tBSUVBOHFrSVJRTVBuWkxBR2g0TXZla2gzZFpHTmNxcEhZZWlXdzE3Rmw0ZlMKTUV3R0" + 45 "ExVWRFUVJGTUVPQ0ZHOXlaR1Z5WlhJeExtVjRZVzF3YkdVdVkyOXRnZ2h2Y21SbGNtVnlNWUlKYkc5agpZV3hvYjNOMGh3Ui9BQUFCaHh" + 46 "BQUFBQUFBQUFBQUFBQUFBQUFBQUFCTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDCklFckJZRFVzV0JwOHB0ZVFSaTZyNjNVelhJQi81Sn" + 47 "YxK0RlTkRIUHc3aDljQWlCakYrM3V5TzBvMEdRclB4MEUKUWptYlI5T3BVREN2LzlEUkNXWU9GZitkVlE9PQotLS0tLUVORCBDRVJUSU" + 48 "ZJQ0FURS0tLS0tCg==") 49 assert.NoError(t, err) 50 51 ca, err := tlsgen.NewCA() 52 require.NoError(t, err) 53 54 kp, err := ca.NewClientCertKeyPair() 55 require.NoError(t, err) 56 57 validBlock := func() *common.Block { 58 b, err := ioutil.ReadFile(filepath.Join("testdata", "etcdraftgenesis.block")) 59 assert.NoError(t, err) 60 block := &common.Block{} 61 err = proto.Unmarshal(b, block) 62 assert.NoError(t, err) 63 return block 64 } 65 for _, testCase := range []struct { 66 name string 67 expectedError string 68 configBlock *common.Block 69 certificate []byte 70 }{ 71 { 72 name: "nil block", 73 expectedError: "nil block or nil header", 74 }, 75 { 76 name: "nil header", 77 expectedError: "nil block or nil header", 78 configBlock: &common.Block{}, 79 }, 80 { 81 name: "no block data", 82 expectedError: "block data is nil", 83 configBlock: &common.Block{Header: &common.BlockHeader{}}, 84 }, 85 { 86 name: "invalid envelope inside block", 87 expectedError: "failed to unmarshal payload from envelope:" + 88 " error unmarshaling Payload: proto: common.Payload: illegal tag 0 (wire type 1)", 89 configBlock: &common.Block{ 90 Header: &common.BlockHeader{}, 91 Data: &common.BlockData{ 92 Data: [][]byte{protoutil.MarshalOrPanic(&common.Envelope{ 93 Payload: []byte{1, 2, 3}, 94 })}, 95 }, 96 }, 97 }, 98 { 99 name: "valid config block with cert mismatch", 100 configBlock: validBlock(), 101 certificate: kp.Cert, 102 expectedError: cluster.ErrNotInChannel.Error(), 103 }, 104 { 105 name: "valid config block with matching cert", 106 configBlock: validBlock(), 107 certificate: certInsideConfigBlock, 108 }, 109 } { 110 t.Run(testCase.name, func(t *testing.T) { 111 cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore()) 112 assert.NoError(t, err) 113 114 consenterCertificate := &ConsenterCertificate{ 115 Logger: flogging.MustGetLogger("test"), 116 ConsenterCertificate: testCase.certificate, 117 CryptoProvider: cryptoProvider, 118 } 119 err = consenterCertificate.IsConsenterOfChannel(testCase.configBlock) 120 if testCase.expectedError != "" { 121 assert.EqualError(t, err, testCase.expectedError) 122 } else { 123 assert.NoError(t, err) 124 } 125 }) 126 } 127 } 128 129 func TestVerifyConfigMetadata(t *testing.T) { 130 tlsCA, err := tlsgen.NewCA() 131 if err != nil { 132 panic(err) 133 } 134 135 caRootCert, err := parseCertificateFromBytes(tlsCA.CertBytes()) 136 if err != nil { 137 panic(err) 138 } 139 140 serverPair, err := tlsCA.NewServerCertKeyPair("localhost") 141 if err != nil { 142 panic(err) 143 } 144 145 clientPair, err := tlsCA.NewClientCertKeyPair() 146 if err != nil { 147 panic(err) 148 } 149 150 unknownTlsCA, err := tlsgen.NewCA() 151 if err != nil { 152 panic(err) 153 } 154 155 unknownServerPair, err := unknownTlsCA.NewServerCertKeyPair("unknownhost") 156 if err != nil { 157 panic(err) 158 } 159 160 unknownServerCert, err := parseCertificateFromBytes(unknownServerPair.Cert) 161 if err != nil { 162 panic(err) 163 } 164 165 unknownClientPair, err := unknownTlsCA.NewClientCertKeyPair() 166 if err != nil { 167 panic(err) 168 } 169 170 unknownClientCert, err := parseCertificateFromBytes(unknownClientPair.Cert) 171 if err != nil { 172 panic(err) 173 } 174 175 validOptions := &etcdraftproto.Options{ 176 TickInterval: "500ms", 177 ElectionTick: 10, 178 HeartbeatTick: 1, 179 MaxInflightBlocks: 5, 180 SnapshotIntervalSize: 20 * 1024 * 1024, // 20 MB 181 } 182 singleConsenter := &etcdraftproto.Consenter{ 183 Host: "host1", 184 Port: 10001, 185 ClientTlsCert: clientPair.Cert, 186 ServerTlsCert: serverPair.Cert, 187 } 188 189 rootCertPool := x509.NewCertPool() 190 rootCertPool.AddCert(caRootCert) 191 goodVerifyingOpts := x509.VerifyOptions{ 192 Roots: rootCertPool, 193 KeyUsages: []x509.ExtKeyUsage{ 194 x509.ExtKeyUsageClientAuth, 195 x509.ExtKeyUsageServerAuth, 196 }, 197 } 198 199 // valid metadata should give nil error 200 goodMetadata := &etcdraftproto.ConfigMetadata{ 201 Options: validOptions, 202 Consenters: []*etcdraftproto.Consenter{ 203 singleConsenter, 204 }, 205 } 206 assert.Nil(t, VerifyConfigMetadata(goodMetadata, goodVerifyingOpts)) 207 208 // test variety of bad metadata 209 for _, testCase := range []struct { 210 description string 211 metadata *etcdraftproto.ConfigMetadata 212 verifyOpts x509.VerifyOptions 213 errRegex string 214 }{ 215 { 216 description: "nil metadata", 217 metadata: nil, 218 errRegex: "nil Raft config metadata", 219 verifyOpts: goodVerifyingOpts, 220 }, 221 { 222 description: "nil options", 223 metadata: &etcdraftproto.ConfigMetadata{}, 224 verifyOpts: goodVerifyingOpts, 225 errRegex: "nil Raft config metadata options", 226 }, 227 { 228 description: "HeartbeatTick is 0", 229 metadata: &etcdraftproto.ConfigMetadata{ 230 Options: &etcdraftproto.Options{ 231 HeartbeatTick: 0, 232 }, 233 }, 234 verifyOpts: goodVerifyingOpts, 235 errRegex: "none of HeartbeatTick .* can be zero", 236 }, 237 { 238 description: "ElectionTick is 0", 239 metadata: &etcdraftproto.ConfigMetadata{ 240 Options: &etcdraftproto.Options{ 241 HeartbeatTick: validOptions.HeartbeatTick, 242 ElectionTick: 0, 243 }, 244 }, 245 verifyOpts: goodVerifyingOpts, 246 errRegex: "none of .* ElectionTick .* can be zero", 247 }, 248 { 249 description: "MaxInflightBlocks is 0", 250 metadata: &etcdraftproto.ConfigMetadata{ 251 Options: &etcdraftproto.Options{ 252 HeartbeatTick: validOptions.HeartbeatTick, 253 ElectionTick: validOptions.ElectionTick, 254 MaxInflightBlocks: 0, 255 }, 256 }, 257 verifyOpts: goodVerifyingOpts, 258 errRegex: "none of .* MaxInflightBlocks .* can be zero", 259 }, 260 { 261 description: "ElectionTick is less than HeartbeatTick", 262 metadata: &etcdraftproto.ConfigMetadata{ 263 Options: &etcdraftproto.Options{ 264 HeartbeatTick: 10, 265 ElectionTick: 1, 266 MaxInflightBlocks: validOptions.MaxInflightBlocks, 267 }, 268 }, 269 verifyOpts: goodVerifyingOpts, 270 errRegex: "ElectionTick .* must be greater than HeartbeatTick", 271 }, 272 { 273 description: "TickInterval is not parsable", 274 metadata: &etcdraftproto.ConfigMetadata{ 275 Options: &etcdraftproto.Options{ 276 HeartbeatTick: validOptions.HeartbeatTick, 277 ElectionTick: validOptions.ElectionTick, 278 MaxInflightBlocks: validOptions.MaxInflightBlocks, 279 TickInterval: "abcd", 280 }, 281 }, 282 verifyOpts: goodVerifyingOpts, 283 errRegex: "failed to parse TickInterval .* to time duration", 284 }, 285 { 286 description: "TickInterval is 0", 287 metadata: &etcdraftproto.ConfigMetadata{ 288 Options: &etcdraftproto.Options{ 289 HeartbeatTick: validOptions.HeartbeatTick, 290 ElectionTick: validOptions.ElectionTick, 291 MaxInflightBlocks: validOptions.MaxInflightBlocks, 292 TickInterval: "0s", 293 }, 294 }, 295 verifyOpts: goodVerifyingOpts, 296 errRegex: "TickInterval cannot be zero", 297 }, 298 { 299 description: "consenter set is empty", 300 metadata: &etcdraftproto.ConfigMetadata{ 301 Options: validOptions, 302 Consenters: []*etcdraftproto.Consenter{}, 303 }, 304 verifyOpts: goodVerifyingOpts, 305 errRegex: "empty consenter set", 306 }, 307 { 308 description: "metadata has nil consenter", 309 metadata: &etcdraftproto.ConfigMetadata{ 310 Options: validOptions, 311 Consenters: []*etcdraftproto.Consenter{ 312 nil, 313 }, 314 }, 315 verifyOpts: goodVerifyingOpts, 316 errRegex: "metadata has nil consenter", 317 }, 318 { 319 description: "consenter has invalid server cert", 320 metadata: &etcdraftproto.ConfigMetadata{ 321 Options: validOptions, 322 Consenters: []*etcdraftproto.Consenter{ 323 { 324 ServerTlsCert: []byte("invalid"), 325 ClientTlsCert: clientPair.Cert, 326 }, 327 }, 328 }, 329 verifyOpts: goodVerifyingOpts, 330 errRegex: "no PEM data found in cert", 331 }, 332 { 333 description: "consenter has invalid client cert", 334 metadata: &etcdraftproto.ConfigMetadata{ 335 Options: validOptions, 336 Consenters: []*etcdraftproto.Consenter{ 337 { 338 ServerTlsCert: serverPair.Cert, 339 ClientTlsCert: []byte("invalid"), 340 }, 341 }, 342 }, 343 verifyOpts: goodVerifyingOpts, 344 errRegex: "no PEM data found in cert", 345 }, 346 { 347 description: "metadata has duplicate consenters", 348 metadata: &etcdraftproto.ConfigMetadata{ 349 Options: validOptions, 350 Consenters: []*etcdraftproto.Consenter{ 351 singleConsenter, 352 singleConsenter, 353 }, 354 }, 355 verifyOpts: goodVerifyingOpts, 356 errRegex: "duplicate consenter", 357 }, 358 { 359 description: "consenter has client cert signed by unknown authority", 360 metadata: &etcdraftproto.ConfigMetadata{ 361 Options: validOptions, 362 Consenters: []*etcdraftproto.Consenter{ 363 { 364 ClientTlsCert: unknownClientPair.Cert, 365 ServerTlsCert: serverPair.Cert, 366 }, 367 }, 368 }, 369 verifyOpts: goodVerifyingOpts, 370 errRegex: fmt.Sprintf("verifying tls client cert with serial number %d: x509: certificate signed by unknown authority", unknownClientCert.SerialNumber), 371 }, 372 { 373 description: "consenter has server cert signed by unknown authority", 374 metadata: &etcdraftproto.ConfigMetadata{ 375 Options: validOptions, 376 Consenters: []*etcdraftproto.Consenter{ 377 { 378 ServerTlsCert: unknownServerPair.Cert, 379 ClientTlsCert: clientPair.Cert, 380 }, 381 }, 382 }, 383 verifyOpts: goodVerifyingOpts, 384 errRegex: fmt.Sprintf("verifying tls server cert with serial number %d: x509: certificate signed by unknown authority", unknownServerCert.SerialNumber), 385 }, 386 } { 387 t.Run(testCase.description, func(t *testing.T) { 388 err := VerifyConfigMetadata(testCase.metadata, testCase.verifyOpts) 389 assert.NotNil(t, err) 390 assert.Regexp(t, testCase.errRegex, err) 391 }) 392 } 393 394 //test use case when consenter has expired certificates 395 tlsCaCertBytes, err := ioutil.ReadFile(filepath.Join(ca1Dir, "ca.pem")) 396 assert.Nil(t, err) 397 tlsCaCert, err := parseCertificateFromBytes(tlsCaCertBytes) 398 assert.Nil(t, err) 399 400 tlsClientCert, err := ioutil.ReadFile(filepath.Join(ca1Dir, "expired-client.pem")) 401 assert.Nil(t, err) 402 403 expiredCertVerifyOpts := goodVerifyingOpts 404 expiredCertVerifyOpts.Roots.AddCert(tlsCaCert) 405 consenterWithExpiredCerts := &etcdraftproto.Consenter{ 406 Host: "host1", 407 Port: 10001, 408 ClientTlsCert: tlsClientCert, 409 ServerTlsCert: tlsClientCert, 410 } 411 412 metadataWithExpiredConsenter := &etcdraftproto.ConfigMetadata{ 413 Options: validOptions, 414 Consenters: []*etcdraftproto.Consenter{ 415 consenterWithExpiredCerts, 416 }, 417 } 418 419 assert.Nil(t, VerifyConfigMetadata(metadataWithExpiredConsenter, expiredCertVerifyOpts)) 420 }