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  }