...

Source file src/github.com/google/certificate-transparency-go/trillian/ctfe/config_test.go

Documentation: github.com/google/certificate-transparency-go/trillian/ctfe

     1  // Copyright 2016 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ctfe
    16  
    17  import (
    18  	"crypto/x509"
    19  	"encoding/base64"
    20  	"encoding/pem"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
    27  	"github.com/google/trillian/crypto/keyspb"
    28  	"google.golang.org/protobuf/proto"
    29  	"google.golang.org/protobuf/types/known/anypb"
    30  	"google.golang.org/protobuf/types/known/timestamppb"
    31  )
    32  
    33  var (
    34  	invalidTimestamp = &timestamppb.Timestamp{Nanos: int32(1e9)}
    35  
    36  	// validSTH is an STH signed by "../testdata/ct-http-server.privkey.pem".
    37  	validSTH = &configpb.SignedTreeHead{
    38  		TreeSize:          766,
    39  		Timestamp:         1538659276115,
    40  		Sha256RootHash:    mustDecodeBase64("rMWSvrYQ+n6kAmU6sMJuVV5LjoKBGK2OL719X5a+T9Y="),
    41  		TreeHeadSignature: mustDecodeBase64("BAMASDBGAiEApo+OIdPXIVEwdnS1v5Iu1gQHaiWmCY73h28zfKMmHrYCIQD1U6qj1mKBXYIQVP52YCdaxdHJH4jDsL1JlaA+J/MqCw=="),
    42  	}
    43  )
    44  
    45  func mustMarshalAny(pb proto.Message) *anypb.Any {
    46  	ret, err := anypb.New(pb)
    47  	if err != nil {
    48  		panic(fmt.Sprintf("MarshalAny failed: %v", err))
    49  	}
    50  	return ret
    51  }
    52  
    53  func mustReadPublicKey(path string) *keyspb.PublicKey {
    54  	keyPEM, err := os.ReadFile(path)
    55  	if err != nil {
    56  		panic(fmt.Sprintf("os.ReadFile(%q): %v", path, err))
    57  	}
    58  	block, _ := pem.Decode(keyPEM)
    59  	pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    60  	if err != nil {
    61  		panic(fmt.Sprintf("ReadPublicKeyFile(): %v", err))
    62  	}
    63  	keyDER, err := x509.MarshalPKIXPublicKey(pubKey)
    64  	if err != nil {
    65  		panic(fmt.Sprintf("x509.MarshalPKIXPublicKey(): %v", err))
    66  	}
    67  	return &keyspb.PublicKey{Der: keyDER}
    68  }
    69  
    70  func mustDecodeBase64(str string) []byte {
    71  	data, err := base64.StdEncoding.DecodeString(str)
    72  	if err != nil {
    73  		panic(fmt.Sprintf("base64: DecodeString failed: %v", err))
    74  	}
    75  	return data
    76  }
    77  
    78  func TestValidateLogConfig(t *testing.T) {
    79  	pubKey := mustReadPublicKey("../testdata/ct-http-server.pubkey.pem")
    80  	privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"})
    81  
    82  	corruptedSTH := proto.Clone(validSTH).(*configpb.SignedTreeHead)
    83  	corruptedSTH.TreeSize = 1234
    84  
    85  	for _, tc := range []struct {
    86  		desc    string
    87  		cfg     *configpb.LogConfig
    88  		wantErr string
    89  	}{
    90  		{
    91  			desc:    "empty-log-ID",
    92  			wantErr: "empty log ID",
    93  			cfg:     &configpb.LogConfig{},
    94  		},
    95  		{
    96  			desc:    "empty-public-key-mirror",
    97  			wantErr: "empty public key for mirror",
    98  			cfg:     &configpb.LogConfig{LogId: 123, IsMirror: true},
    99  		},
   100  		{
   101  			desc:    "empty-public-key-frozen",
   102  			wantErr: "empty public key for frozen STH",
   103  			cfg:     &configpb.LogConfig{LogId: 123, FrozenSth: &configpb.SignedTreeHead{}},
   104  		},
   105  		{
   106  			desc:    "invalid-public-key-empty",
   107  			wantErr: "x509.ParsePKIXPublicKey",
   108  			cfg: &configpb.LogConfig{
   109  				LogId:     123,
   110  				PublicKey: &keyspb.PublicKey{},
   111  				IsMirror:  true,
   112  			},
   113  		},
   114  		{
   115  			desc:    "invalid-public-key-abacaba",
   116  			wantErr: "x509.ParsePKIXPublicKey",
   117  			cfg: &configpb.LogConfig{
   118  				LogId:     123,
   119  				PublicKey: &keyspb.PublicKey{Der: []byte("abacaba")},
   120  				IsMirror:  true,
   121  			},
   122  		},
   123  		{
   124  			desc:    "empty-private-key",
   125  			wantErr: "empty private key",
   126  			cfg:     &configpb.LogConfig{LogId: 123},
   127  		},
   128  		{
   129  			desc:    "invalid-private-key",
   130  			wantErr: "invalid private key",
   131  			cfg: &configpb.LogConfig{
   132  				LogId:      123,
   133  				PrivateKey: &anypb.Any{},
   134  			},
   135  		},
   136  		{
   137  			desc:    "unnecessary-private-key",
   138  			wantErr: "unnecessary private key",
   139  			cfg: &configpb.LogConfig{
   140  				LogId:      123,
   141  				PublicKey:  pubKey,
   142  				PrivateKey: privKey,
   143  				IsMirror:   true,
   144  			},
   145  		},
   146  		{
   147  			desc:    "rejecting-all",
   148  			wantErr: "rejecting all certificates",
   149  			cfg: &configpb.LogConfig{
   150  				LogId:           123,
   151  				RejectExpired:   true,
   152  				RejectUnexpired: true,
   153  				PrivateKey:      privKey,
   154  			},
   155  		},
   156  		{
   157  			desc:    "unknown-ext-key-usage-1",
   158  			wantErr: "unknown extended key usage",
   159  			cfg: &configpb.LogConfig{
   160  				LogId:        123,
   161  				PrivateKey:   privKey,
   162  				ExtKeyUsages: []string{"wrong_usage"},
   163  			},
   164  		},
   165  		{
   166  			desc:    "unknown-ext-key-usage-2",
   167  			wantErr: "unknown extended key usage",
   168  			cfg: &configpb.LogConfig{
   169  				LogId:        123,
   170  				PrivateKey:   privKey,
   171  				ExtKeyUsages: []string{"ClientAuth", "ServerAuth", "TimeStomping"},
   172  			},
   173  		},
   174  		{
   175  			desc:    "unknown-ext-key-usage-3",
   176  			wantErr: "unknown extended key usage",
   177  			cfg: &configpb.LogConfig{
   178  				LogId:        123,
   179  				PrivateKey:   privKey,
   180  				ExtKeyUsages: []string{"Any "},
   181  			},
   182  		},
   183  		{
   184  			desc:    "invalid-start-timestamp",
   185  			wantErr: "invalid start timestamp",
   186  			cfg: &configpb.LogConfig{
   187  				LogId:         123,
   188  				PrivateKey:    privKey,
   189  				NotAfterStart: invalidTimestamp,
   190  			},
   191  		},
   192  		{
   193  			desc:    "invalid-limit-timestamp",
   194  			wantErr: "invalid limit timestamp",
   195  			cfg: &configpb.LogConfig{
   196  				LogId:         123,
   197  				PrivateKey:    privKey,
   198  				NotAfterLimit: invalidTimestamp,
   199  			},
   200  		},
   201  		{
   202  			desc:    "limit-before-start",
   203  			wantErr: "limit before start",
   204  			cfg: &configpb.LogConfig{
   205  				LogId:         123,
   206  				PrivateKey:    privKey,
   207  				NotAfterStart: &timestamppb.Timestamp{Seconds: 200},
   208  				NotAfterLimit: &timestamppb.Timestamp{Seconds: 100},
   209  			},
   210  		},
   211  		{
   212  			desc:    "negative-maximum-merge",
   213  			wantErr: "negative maximum merge",
   214  			cfg: &configpb.LogConfig{
   215  				LogId:            123,
   216  				PrivateKey:       privKey,
   217  				MaxMergeDelaySec: -100,
   218  			},
   219  		},
   220  		{
   221  			desc:    "negative-expected-merge",
   222  			wantErr: "negative expected merge",
   223  			cfg: &configpb.LogConfig{
   224  				LogId:                 123,
   225  				PrivateKey:            privKey,
   226  				ExpectedMergeDelaySec: -100,
   227  			},
   228  		},
   229  		{
   230  			desc:    "expected-exceeds-max",
   231  			wantErr: "expected merge delay exceeds MMD",
   232  			cfg: &configpb.LogConfig{
   233  				LogId:                 123,
   234  				PrivateKey:            privKey,
   235  				MaxMergeDelaySec:      50,
   236  				ExpectedMergeDelaySec: 100,
   237  			},
   238  		},
   239  		{
   240  			desc:    "invalid-frozen-STH",
   241  			wantErr: "invalid frozen STH",
   242  			cfg: &configpb.LogConfig{
   243  				LogId:     123,
   244  				PublicKey: pubKey,
   245  				IsMirror:  true,
   246  				FrozenSth: &configpb.SignedTreeHead{
   247  					TreeSize:  10,
   248  					Timestamp: 100500,
   249  				},
   250  			},
   251  		},
   252  		{
   253  			desc:    "signature-verification-failed",
   254  			wantErr: "signature verification failed",
   255  			cfg: &configpb.LogConfig{
   256  				LogId:      123,
   257  				PublicKey:  pubKey,
   258  				PrivateKey: privKey,
   259  				FrozenSth:  corruptedSTH,
   260  			},
   261  		},
   262  		{
   263  			desc: "ok",
   264  			cfg: &configpb.LogConfig{
   265  				LogId:      123,
   266  				PrivateKey: privKey,
   267  			},
   268  		},
   269  		{
   270  			// Note: Substituting an arbitrary proto.Message as a PrivateKey will not
   271  			// fail the validation because the actual key loading happens at runtime.
   272  			// TODO(pavelkalinnikov): Decouple key protos validation and loading, and
   273  			// make this test fail.
   274  			desc: "ok-not-a-key",
   275  			cfg: &configpb.LogConfig{
   276  				LogId:      123,
   277  				PrivateKey: mustMarshalAny(&configpb.LogConfig{}),
   278  			},
   279  		},
   280  		{
   281  			desc: "ok-mirror",
   282  			cfg: &configpb.LogConfig{
   283  				LogId:     123,
   284  				PublicKey: pubKey,
   285  				IsMirror:  true,
   286  			},
   287  		},
   288  		{
   289  			desc: "ok-ext-key-usages",
   290  			cfg: &configpb.LogConfig{
   291  				LogId:        123,
   292  				PrivateKey:   privKey,
   293  				ExtKeyUsages: []string{"ServerAuth", "ClientAuth", "OCSPSigning"},
   294  			},
   295  		},
   296  		{
   297  			desc: "ok-start-timestamp",
   298  			cfg: &configpb.LogConfig{
   299  				LogId:         123,
   300  				PrivateKey:    privKey,
   301  				NotAfterStart: &timestamppb.Timestamp{Seconds: 100},
   302  			},
   303  		},
   304  		{
   305  			desc: "ok-limit-timestamp",
   306  			cfg: &configpb.LogConfig{
   307  				LogId:         123,
   308  				PrivateKey:    privKey,
   309  				NotAfterLimit: &timestamppb.Timestamp{Seconds: 200},
   310  			},
   311  		},
   312  		{
   313  			desc: "ok-range-timestamp",
   314  			cfg: &configpb.LogConfig{
   315  				LogId:         123,
   316  				PrivateKey:    privKey,
   317  				NotAfterStart: &timestamppb.Timestamp{Seconds: 300},
   318  				NotAfterLimit: &timestamppb.Timestamp{Seconds: 400},
   319  			},
   320  		},
   321  		{
   322  			desc: "ok-merge-delay",
   323  			cfg: &configpb.LogConfig{
   324  				LogId:                 123,
   325  				PrivateKey:            privKey,
   326  				MaxMergeDelaySec:      86400,
   327  				ExpectedMergeDelaySec: 7200,
   328  			},
   329  		},
   330  		{
   331  			desc: "ok-frozen-STH",
   332  			cfg: &configpb.LogConfig{
   333  				LogId:      123,
   334  				PublicKey:  pubKey,
   335  				PrivateKey: privKey,
   336  				FrozenSth:  validSTH,
   337  			},
   338  		},
   339  	} {
   340  		t.Run(tc.desc, func(t *testing.T) {
   341  			vc, err := ValidateLogConfig(tc.cfg)
   342  			if len(tc.wantErr) == 0 && err != nil {
   343  				t.Errorf("ValidateLogConfig()=%v, want nil", err)
   344  			}
   345  			if len(tc.wantErr) > 0 && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) {
   346  				t.Errorf("ValidateLogConfig()=%v, want err containing %q", err, tc.wantErr)
   347  			}
   348  			if err == nil && vc == nil {
   349  				t.Error("err and ValidatedLogConfig are both nil")
   350  			}
   351  			// TODO(pavelkalinnikov): Test that ValidatedLogConfig is correct.
   352  		})
   353  	}
   354  }
   355  
   356  func TestValidateLogMultiConfig(t *testing.T) {
   357  	privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"})
   358  	for _, tc := range []struct {
   359  		desc    string
   360  		cfg     *configpb.LogMultiConfig
   361  		wantErr string
   362  	}{
   363  		{
   364  			desc:    "empty-backend-name",
   365  			wantErr: "empty backend name",
   366  			cfg: &configpb.LogMultiConfig{
   367  				Backends: &configpb.LogBackendSet{
   368  					Backend: []*configpb.LogBackend{
   369  						{BackendSpec: "testspec"},
   370  					},
   371  				},
   372  			},
   373  		},
   374  		{
   375  			desc:    "empty-backend-spec",
   376  			wantErr: "empty backend spec",
   377  			cfg: &configpb.LogMultiConfig{
   378  				Backends: &configpb.LogBackendSet{
   379  					Backend: []*configpb.LogBackend{
   380  						{Name: "log1"},
   381  					},
   382  				},
   383  			},
   384  		},
   385  		{
   386  			desc:    "duplicate-backend-name",
   387  			wantErr: "duplicate backend name",
   388  			cfg: &configpb.LogMultiConfig{
   389  				Backends: &configpb.LogBackendSet{
   390  					Backend: []*configpb.LogBackend{
   391  						{Name: "dup", BackendSpec: "testspec"},
   392  						{Name: "dup", BackendSpec: "testspec"},
   393  					},
   394  				},
   395  			},
   396  		},
   397  		{
   398  			desc:    "duplicate-backend-spec",
   399  			wantErr: "duplicate backend spec",
   400  			cfg: &configpb.LogMultiConfig{
   401  				Backends: &configpb.LogBackendSet{
   402  					Backend: []*configpb.LogBackend{
   403  						{Name: "log1", BackendSpec: "testspec"},
   404  						{Name: "log2", BackendSpec: "testspec"},
   405  					},
   406  				},
   407  			},
   408  		},
   409  		{
   410  			desc:    "invalid-log-config",
   411  			wantErr: "log config: empty log ID",
   412  			cfg: &configpb.LogMultiConfig{
   413  				Backends: &configpb.LogBackendSet{
   414  					Backend: []*configpb.LogBackend{
   415  						{Name: "log1", BackendSpec: "testspec"},
   416  					},
   417  				},
   418  				LogConfigs: &configpb.LogConfigSet{
   419  					Config: []*configpb.LogConfig{
   420  						{Prefix: "pref"},
   421  					},
   422  				},
   423  			},
   424  		},
   425  		{
   426  			desc:    "empty-prefix",
   427  			wantErr: "empty prefix",
   428  			cfg: &configpb.LogMultiConfig{
   429  				Backends: &configpb.LogBackendSet{
   430  					Backend: []*configpb.LogBackend{
   431  						{Name: "log1", BackendSpec: "testspec"},
   432  					},
   433  				},
   434  				LogConfigs: &configpb.LogConfigSet{
   435  					Config: []*configpb.LogConfig{
   436  						{LogId: 1, PrivateKey: privKey, LogBackendName: "log1"},
   437  					},
   438  				},
   439  			},
   440  		},
   441  		{
   442  			desc:    "duplicate-prefix",
   443  			wantErr: "duplicate prefix",
   444  			cfg: &configpb.LogMultiConfig{
   445  				Backends: &configpb.LogBackendSet{
   446  					Backend: []*configpb.LogBackend{
   447  						{Name: "log1", BackendSpec: "testspec1"},
   448  					},
   449  				},
   450  				LogConfigs: &configpb.LogConfigSet{
   451  					Config: []*configpb.LogConfig{
   452  						{LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"},
   453  						{LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log1"},
   454  						{LogId: 3, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"},
   455  					},
   456  				},
   457  			},
   458  		},
   459  		{
   460  			desc:    "references-undefined-backend",
   461  			wantErr: "references undefined backend",
   462  			cfg: &configpb.LogMultiConfig{
   463  				Backends: &configpb.LogBackendSet{
   464  					Backend: []*configpb.LogBackend{
   465  						{Name: "log1", BackendSpec: "testspec"},
   466  					},
   467  				},
   468  				LogConfigs: &configpb.LogConfigSet{
   469  					Config: []*configpb.LogConfig{
   470  						{LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"},
   471  					},
   472  				},
   473  			},
   474  		},
   475  		{
   476  			desc:    "dup-tree-id-on-same-backend",
   477  			wantErr: "dup tree id",
   478  			cfg: &configpb.LogMultiConfig{
   479  				Backends: &configpb.LogBackendSet{
   480  					Backend: []*configpb.LogBackend{
   481  						{Name: "log1", BackendSpec: "testspec1"},
   482  					},
   483  				},
   484  				LogConfigs: &configpb.LogConfigSet{
   485  					Config: []*configpb.LogConfig{
   486  						{LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"},
   487  						{LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log1"},
   488  						{LogId: 1, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log1"},
   489  					},
   490  				},
   491  			},
   492  		},
   493  		{
   494  			desc: "ok-all-distinct",
   495  			cfg: &configpb.LogMultiConfig{
   496  				Backends: &configpb.LogBackendSet{
   497  					Backend: []*configpb.LogBackend{
   498  						{Name: "log1", BackendSpec: "testspec1"},
   499  						{Name: "log2", BackendSpec: "testspec2"},
   500  						{Name: "log3", BackendSpec: "testspec3"},
   501  					},
   502  				},
   503  				LogConfigs: &configpb.LogConfigSet{
   504  					Config: []*configpb.LogConfig{
   505  						{LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"},
   506  						{LogId: 2, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"},
   507  						{LogId: 3, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log3"},
   508  					},
   509  				},
   510  			},
   511  		},
   512  		{
   513  			desc: "ok-dup-tree-ids-on-different-backends",
   514  			cfg: &configpb.LogMultiConfig{
   515  				Backends: &configpb.LogBackendSet{
   516  					Backend: []*configpb.LogBackend{
   517  						{Name: "log1", BackendSpec: "testspec1"},
   518  						{Name: "log2", BackendSpec: "testspec2"},
   519  						{Name: "log3", BackendSpec: "testspec3"},
   520  					},
   521  				},
   522  				LogConfigs: &configpb.LogConfigSet{
   523  					Config: []*configpb.LogConfig{
   524  						{LogId: 1, Prefix: "pref1", PrivateKey: privKey, LogBackendName: "log1"},
   525  						{LogId: 1, Prefix: "pref2", PrivateKey: privKey, LogBackendName: "log2"},
   526  						{LogId: 1, Prefix: "pref3", PrivateKey: privKey, LogBackendName: "log3"},
   527  					},
   528  				},
   529  			},
   530  		},
   531  	} {
   532  		t.Run(tc.desc, func(t *testing.T) {
   533  			_, err := ValidateLogMultiConfig(tc.cfg)
   534  			if len(tc.wantErr) == 0 && err != nil {
   535  				t.Fatalf("ValidateLogMultiConfig()=%v, want nil", err)
   536  			}
   537  			if len(tc.wantErr) > 0 && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) {
   538  				t.Errorf("ValidateLogMultiConfig()=%v, want err containing %q", err, tc.wantErr)
   539  			}
   540  		})
   541  	}
   542  }
   543  
   544  func TestToMultiLogConfig(t *testing.T) {
   545  	// TODO(pavelkalinnikov): Log configs in this test are not valid (they don't
   546  	// have keys etc). In addition, we should have tests to ensure that valid log
   547  	// configs result in valid MultiLogConfig.
   548  
   549  	for _, tc := range []struct {
   550  		desc string
   551  		cfg  []*configpb.LogConfig
   552  		want *configpb.LogMultiConfig
   553  	}{
   554  		{
   555  			desc: "ok-one-config",
   556  			cfg: []*configpb.LogConfig{
   557  				{LogId: 1, Prefix: "test"},
   558  			},
   559  			want: &configpb.LogMultiConfig{
   560  				Backends: &configpb.LogBackendSet{
   561  					Backend: []*configpb.LogBackend{{Name: "default", BackendSpec: "spec"}},
   562  				},
   563  				LogConfigs: &configpb.LogConfigSet{
   564  					Config: []*configpb.LogConfig{
   565  						{LogId: 1, Prefix: "test", LogBackendName: "default"},
   566  					},
   567  				},
   568  			},
   569  		},
   570  		{
   571  			desc: "ok-three-configs",
   572  			cfg: []*configpb.LogConfig{
   573  				{LogId: 1, Prefix: "test1"},
   574  				{LogId: 2, Prefix: "test2"},
   575  				{LogId: 3, Prefix: "test3"},
   576  			},
   577  			want: &configpb.LogMultiConfig{
   578  				Backends: &configpb.LogBackendSet{
   579  					Backend: []*configpb.LogBackend{{Name: "default", BackendSpec: "spec"}},
   580  				},
   581  				LogConfigs: &configpb.LogConfigSet{
   582  					Config: []*configpb.LogConfig{
   583  						{LogId: 1, Prefix: "test1", LogBackendName: "default"},
   584  						{LogId: 2, Prefix: "test2", LogBackendName: "default"},
   585  						{LogId: 3, Prefix: "test3", LogBackendName: "default"},
   586  					},
   587  				},
   588  			},
   589  		},
   590  	} {
   591  		t.Run(tc.desc, func(t *testing.T) {
   592  			got := ToMultiLogConfig(tc.cfg, "spec")
   593  			if !proto.Equal(got, tc.want) {
   594  				t.Errorf("TestToMultiLogConfig()=%v, want %v", got, tc.want)
   595  			}
   596  		})
   597  	}
   598  }
   599  

View as plain text