github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/storage/tree_validation_test.go (about)

     1  // Copyright 2017 Google Inc. 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 storage
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/protobuf/ptypes"
    23  	"github.com/golang/protobuf/ptypes/any"
    24  	"github.com/golang/protobuf/ptypes/empty"
    25  	"github.com/google/trillian"
    26  	"github.com/google/trillian/crypto/keyspb"
    27  	"github.com/google/trillian/crypto/sigpb"
    28  	"github.com/google/trillian/testonly"
    29  	"google.golang.org/grpc/codes"
    30  	"google.golang.org/grpc/status"
    31  
    32  	ktestonly "github.com/google/trillian/crypto/keys/testonly"
    33  
    34  	_ "github.com/google/trillian/crypto/keys/der/proto"
    35  	_ "github.com/google/trillian/crypto/keys/pem/proto"
    36  )
    37  
    38  const (
    39  	privateKeyPath = "../testdata/log-rpc-server.privkey.pem"
    40  	privateKeyPass = "towel"
    41  	privateKeyPEM  = `
    42  -----BEGIN EC PRIVATE KEY-----
    43  Proc-Type: 4,ENCRYPTED
    44  DEK-Info: DES-CBC,D95ECC664FF4BDEC
    45  
    46  Xy3zzHFwlFwjE8L1NCngJAFbu3zFf4IbBOCsz6Fa790utVNdulZncNCl2FMK3U2T
    47  sdoiTW8ymO+qgwcNrqvPVmjFRBtkN0Pn5lgbWhN/aK3TlS9IYJ/EShbMUzjgVzie
    48  S9+/31whWcH/FLeLJx4cBzvhgCtfquwA+s5ojeLYYsk=
    49  -----END EC PRIVATE KEY-----`
    50  	publicKeyPEM = `
    51  -----BEGIN PUBLIC KEY-----
    52  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEywnWicNEQ8bn3GXcGpA+tiU4VL70
    53  Ws9xezgQPrg96YGsFrF6KYG68iqyHDlQ+4FWuKfGKXHn3ooVtB/pfawb5Q==
    54  -----END PUBLIC KEY-----`
    55  )
    56  
    57  func TestValidateTreeForCreation(t *testing.T) {
    58  	ctx := context.Background()
    59  
    60  	valid1 := newTree()
    61  
    62  	valid2 := newTree()
    63  	valid2.TreeType = trillian.TreeType_MAP
    64  
    65  	invalidState1 := newTree()
    66  	invalidState1.TreeState = trillian.TreeState_UNKNOWN_TREE_STATE
    67  	invalidState2 := newTree()
    68  	invalidState2.TreeState = trillian.TreeState_FROZEN
    69  
    70  	invalidType := newTree()
    71  	invalidType.TreeType = trillian.TreeType_UNKNOWN_TREE_TYPE
    72  
    73  	invalidHashStrategy := newTree()
    74  	invalidHashStrategy.HashStrategy = trillian.HashStrategy_UNKNOWN_HASH_STRATEGY
    75  
    76  	invalidHashAlgorithm := newTree()
    77  	invalidHashAlgorithm.HashAlgorithm = sigpb.DigitallySigned_NONE
    78  
    79  	invalidSignatureAlgorithm := newTree()
    80  	invalidSignatureAlgorithm.SignatureAlgorithm = sigpb.DigitallySigned_ANONYMOUS
    81  
    82  	invalidDisplayName := newTree()
    83  	invalidDisplayName.DisplayName = "A Very Long Display Name That Clearly Won't Fit But At Least Mentions Llamas Somewhere"
    84  
    85  	invalidDescription := newTree()
    86  	invalidDescription.Description = `
    87  		A Very Long Description That Clearly Won't Fit, Also Mentions Llamas, For Some Reason Has Only Capitalized Words And Keeps Repeating Itself.
    88  		A Very Long Description That Clearly Won't Fit, Also Mentions Llamas, For Some Reason Has Only Capitalized Words And Keeps Repeating Itself.
    89  		`
    90  
    91  	unsupportedPrivateKey := newTree()
    92  	unsupportedPrivateKey.PrivateKey.TypeUrl = "urn://unknown-type"
    93  
    94  	invalidPrivateKey := newTree()
    95  	invalidPrivateKey.PrivateKey.Value = []byte("foobar")
    96  
    97  	nilPrivateKey := newTree()
    98  	nilPrivateKey.PrivateKey = nil
    99  
   100  	invalidPublicKey := newTree()
   101  	invalidPublicKey.PublicKey.Der = []byte("foobar")
   102  
   103  	nilPublicKey := newTree()
   104  	nilPublicKey.PublicKey = nil
   105  
   106  	invalidSettings := newTree()
   107  	invalidSettings.StorageSettings = &any.Any{Value: []byte("foobar")}
   108  
   109  	// As long as settings is a valid proto, the type doesn't matter for this test.
   110  	settings, err := ptypes.MarshalAny(&keyspb.PEMKeyFile{})
   111  	if err != nil {
   112  		t.Fatalf("Error marshaling proto: %v", err)
   113  	}
   114  	validSettings := newTree()
   115  	validSettings.StorageSettings = settings
   116  
   117  	nilRootDuration := newTree()
   118  	nilRootDuration.MaxRootDuration = nil
   119  
   120  	invalidRootDuration := newTree()
   121  	invalidRootDuration.MaxRootDuration = ptypes.DurationProto(-1 * time.Second)
   122  
   123  	deletedTree := newTree()
   124  	deletedTree.Deleted = true
   125  
   126  	deleteTimeTree := newTree()
   127  	deleteTimeTree.DeleteTime = ptypes.TimestampNow()
   128  
   129  	tests := []struct {
   130  		desc    string
   131  		tree    *trillian.Tree
   132  		wantErr bool
   133  	}{
   134  		{
   135  			desc: "valid1",
   136  			tree: valid1,
   137  		},
   138  		{
   139  			desc: "valid2",
   140  			tree: valid2,
   141  		},
   142  		{
   143  			desc:    "nilTree",
   144  			tree:    nil,
   145  			wantErr: true,
   146  		},
   147  		{
   148  			desc:    "invalidState1",
   149  			tree:    invalidState1,
   150  			wantErr: true,
   151  		},
   152  		{
   153  			desc:    "invalidState2",
   154  			tree:    invalidState2,
   155  			wantErr: true,
   156  		},
   157  		{
   158  			desc:    "invalidType",
   159  			tree:    invalidType,
   160  			wantErr: true,
   161  		},
   162  		{
   163  			desc:    "invalidHashStrategy",
   164  			tree:    invalidHashStrategy,
   165  			wantErr: true,
   166  		},
   167  		{
   168  			desc:    "invalidHashAlgorithm",
   169  			tree:    invalidHashAlgorithm,
   170  			wantErr: true,
   171  		},
   172  		{
   173  			desc:    "invalidSignatureAlgorithm",
   174  			tree:    invalidSignatureAlgorithm,
   175  			wantErr: true,
   176  		},
   177  		{
   178  			desc:    "invalidDisplayName",
   179  			tree:    invalidDisplayName,
   180  			wantErr: true,
   181  		},
   182  		{
   183  			desc:    "invalidDescription",
   184  			tree:    invalidDescription,
   185  			wantErr: true,
   186  		},
   187  		{
   188  			desc:    "unsupportedPrivateKey",
   189  			tree:    unsupportedPrivateKey,
   190  			wantErr: true,
   191  		},
   192  		{
   193  			desc:    "invalidPrivateKey",
   194  			tree:    invalidPrivateKey,
   195  			wantErr: true,
   196  		},
   197  		{
   198  			desc:    "nilPrivateKey",
   199  			tree:    nilPrivateKey,
   200  			wantErr: true,
   201  		},
   202  		{
   203  			desc:    "invalidPublicKey",
   204  			tree:    invalidPublicKey,
   205  			wantErr: true,
   206  		},
   207  		{
   208  			desc:    "nilPublicKey",
   209  			tree:    nilPublicKey,
   210  			wantErr: true,
   211  		},
   212  		{
   213  			desc:    "invalidSettings",
   214  			tree:    invalidSettings,
   215  			wantErr: true,
   216  		},
   217  		{
   218  			desc: "validSettings",
   219  			tree: validSettings,
   220  		},
   221  		{
   222  			desc:    "nilRootDuration",
   223  			tree:    nilRootDuration,
   224  			wantErr: true,
   225  		},
   226  		{
   227  			desc:    "invalidRootDuration",
   228  			tree:    invalidRootDuration,
   229  			wantErr: true,
   230  		},
   231  		{
   232  			desc:    "deletedTree",
   233  			tree:    deletedTree,
   234  			wantErr: true,
   235  		},
   236  		{
   237  			desc:    "deleteTimeTree",
   238  			tree:    deleteTimeTree,
   239  			wantErr: true,
   240  		},
   241  	}
   242  	for _, test := range tests {
   243  		err := ValidateTreeForCreation(ctx, test.tree)
   244  		switch hasErr := err != nil; {
   245  		case hasErr != test.wantErr:
   246  			t.Errorf("%v: ValidateTreeForCreation() = %v, wantErr = %v", test.desc, err, test.wantErr)
   247  		case hasErr && status.Code(err) != codes.InvalidArgument:
   248  			t.Errorf("%v: ValidateTreeForCreation() = %v, wantCode = %v", test.desc, err, codes.InvalidArgument)
   249  		}
   250  	}
   251  }
   252  
   253  func TestValidateTreeForUpdate(t *testing.T) {
   254  	ctx := context.Background()
   255  
   256  	tests := []struct {
   257  		desc      string
   258  		treeState trillian.TreeState
   259  		treeType  trillian.TreeType
   260  		updatefn  func(*trillian.Tree)
   261  		wantErr   bool
   262  	}{
   263  		{
   264  			desc: "valid",
   265  			updatefn: func(tree *trillian.Tree) {
   266  				tree.TreeState = trillian.TreeState_FROZEN
   267  				tree.DisplayName = "Frozen Tree"
   268  				tree.Description = "A Frozen Tree"
   269  			},
   270  		},
   271  		{
   272  			desc:     "noop",
   273  			updatefn: func(tree *trillian.Tree) {},
   274  		},
   275  		{
   276  			desc: "validSettings",
   277  			updatefn: func(tree *trillian.Tree) {
   278  				// As long as settings is a valid proto, the type doesn't matter for this test.
   279  				settings, err := ptypes.MarshalAny(&keyspb.PEMKeyFile{})
   280  				if err != nil {
   281  					t.Fatalf("Error marshaling proto: %v", err)
   282  				}
   283  				tree.StorageSettings = settings
   284  			},
   285  		},
   286  		{
   287  			desc: "invalidSettings",
   288  			updatefn: func(tree *trillian.Tree) {
   289  				tree.StorageSettings = &any.Any{Value: []byte("foobar")}
   290  			},
   291  			wantErr: true,
   292  		},
   293  		{
   294  			desc: "validRootDuration",
   295  			updatefn: func(tree *trillian.Tree) {
   296  				tree.MaxRootDuration = ptypes.DurationProto(200 * time.Millisecond)
   297  			},
   298  		},
   299  		{
   300  			desc: "invalidRootDuration",
   301  			updatefn: func(tree *trillian.Tree) {
   302  				tree.MaxRootDuration = ptypes.DurationProto(-200 * time.Millisecond)
   303  			},
   304  			wantErr: true,
   305  		},
   306  		{
   307  			desc: "differentPrivateKeyProtoButSameKeyMaterial",
   308  			updatefn: func(tree *trillian.Tree) {
   309  				key, err := ptypes.MarshalAny(&keyspb.PrivateKey{
   310  					Der: ktestonly.MustMarshalPrivatePEMToDER(privateKeyPEM, privateKeyPass),
   311  				})
   312  				if err != nil {
   313  					panic(err)
   314  				}
   315  				tree.PrivateKey = key
   316  			},
   317  		},
   318  		{
   319  			desc: "differentPrivateKeyProtoAndDifferentKeyMaterial",
   320  			updatefn: func(tree *trillian.Tree) {
   321  				key, err := ptypes.MarshalAny(&keyspb.PrivateKey{
   322  					Der: ktestonly.MustMarshalPrivatePEMToDER(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass),
   323  				})
   324  				if err != nil {
   325  					panic(err)
   326  				}
   327  				tree.PrivateKey = key
   328  			},
   329  			wantErr: true,
   330  		},
   331  		{
   332  			desc: "unsupportedPrivateKeyProto",
   333  			updatefn: func(tree *trillian.Tree) {
   334  				key, err := ptypes.MarshalAny(&empty.Empty{})
   335  				if err != nil {
   336  					panic(err)
   337  				}
   338  				tree.PrivateKey = key
   339  			},
   340  			wantErr: true,
   341  		},
   342  		{
   343  			desc: "nilPrivateKeyProto",
   344  			updatefn: func(tree *trillian.Tree) {
   345  				tree.PrivateKey = nil
   346  			},
   347  			wantErr: true,
   348  		},
   349  		// Changes on readonly fields
   350  		{
   351  			desc: "TreeId",
   352  			updatefn: func(tree *trillian.Tree) {
   353  				tree.TreeId++
   354  			},
   355  			wantErr: true,
   356  		},
   357  		{
   358  			desc: "TreeType",
   359  			updatefn: func(tree *trillian.Tree) {
   360  				tree.TreeType = trillian.TreeType_MAP
   361  			},
   362  			wantErr: true,
   363  		},
   364  		{
   365  			desc:     "TreeTypeFromPreorderedLogToLog",
   366  			treeType: trillian.TreeType_PREORDERED_LOG,
   367  			updatefn: func(tree *trillian.Tree) {
   368  				tree.TreeType = trillian.TreeType_LOG
   369  			},
   370  			wantErr: true,
   371  		},
   372  		{
   373  			desc:      "TreeTypeFromFrozenPreorderedLogToLog",
   374  			treeState: trillian.TreeState_FROZEN,
   375  			treeType:  trillian.TreeType_PREORDERED_LOG,
   376  			updatefn: func(tree *trillian.Tree) {
   377  				tree.TreeType = trillian.TreeType_LOG
   378  			},
   379  		},
   380  		{
   381  			desc:      "TreeTypeFromFrozenPreorderedLogToActiveLog",
   382  			treeState: trillian.TreeState_FROZEN,
   383  			treeType:  trillian.TreeType_PREORDERED_LOG,
   384  			updatefn: func(tree *trillian.Tree) {
   385  				tree.TreeState = trillian.TreeState_ACTIVE
   386  				tree.TreeType = trillian.TreeType_LOG
   387  			},
   388  			wantErr: true,
   389  		},
   390  		{
   391  			desc: "HashStrategy",
   392  			updatefn: func(tree *trillian.Tree) {
   393  				tree.HashStrategy = trillian.HashStrategy_UNKNOWN_HASH_STRATEGY
   394  			},
   395  			wantErr: true,
   396  		},
   397  		{
   398  			desc: "HashAlgorithm",
   399  			updatefn: func(tree *trillian.Tree) {
   400  				tree.HashAlgorithm = sigpb.DigitallySigned_NONE
   401  			},
   402  			wantErr: true,
   403  		},
   404  		{
   405  			desc: "SignatureAlgorithm",
   406  			updatefn: func(tree *trillian.Tree) {
   407  				tree.SignatureAlgorithm = sigpb.DigitallySigned_RSA
   408  			},
   409  			wantErr: true,
   410  		},
   411  		{
   412  			desc: "CreateTime",
   413  			updatefn: func(tree *trillian.Tree) {
   414  				tree.CreateTime, _ = ptypes.TimestampProto(time.Now())
   415  			},
   416  			wantErr: true,
   417  		},
   418  		{
   419  			desc: "UpdateTime",
   420  			updatefn: func(tree *trillian.Tree) {
   421  				tree.UpdateTime, _ = ptypes.TimestampProto(time.Now())
   422  			},
   423  			wantErr: true,
   424  		},
   425  		{
   426  			desc:     "Deleted",
   427  			updatefn: func(tree *trillian.Tree) { tree.Deleted = !tree.Deleted },
   428  			wantErr:  true,
   429  		},
   430  		{
   431  			desc:     "DeleteTime",
   432  			updatefn: func(tree *trillian.Tree) { tree.DeleteTime = ptypes.TimestampNow() },
   433  			wantErr:  true,
   434  		},
   435  	}
   436  	for _, test := range tests {
   437  		tree := newTree()
   438  		if test.treeType != trillian.TreeType_UNKNOWN_TREE_TYPE {
   439  			tree.TreeType = test.treeType
   440  		}
   441  		if test.treeState != trillian.TreeState_UNKNOWN_TREE_STATE {
   442  			tree.TreeState = test.treeState
   443  		}
   444  
   445  		baseTree := *tree
   446  		test.updatefn(tree)
   447  
   448  		err := ValidateTreeForUpdate(ctx, &baseTree, tree)
   449  		switch hasErr := err != nil; {
   450  		case hasErr != test.wantErr:
   451  			t.Errorf("%v: ValidateTreeForUpdate() = %v, wantErr = %v", test.desc, err, test.wantErr)
   452  		case hasErr && status.Code(err) != codes.InvalidArgument:
   453  			t.Errorf("%v: ValidateTreeForUpdate() = %v, wantCode = %d", test.desc, err, codes.InvalidArgument)
   454  		}
   455  	}
   456  }
   457  
   458  // newTree returns a valid log tree for tests.
   459  func newTree() *trillian.Tree {
   460  	privateKey, err := ptypes.MarshalAny(&keyspb.PEMKeyFile{
   461  		Path:     privateKeyPath,
   462  		Password: privateKeyPass,
   463  	})
   464  	if err != nil {
   465  		panic(err)
   466  	}
   467  
   468  	return &trillian.Tree{
   469  		TreeState:          trillian.TreeState_ACTIVE,
   470  		TreeType:           trillian.TreeType_LOG,
   471  		HashStrategy:       trillian.HashStrategy_RFC6962_SHA256,
   472  		HashAlgorithm:      sigpb.DigitallySigned_SHA256,
   473  		SignatureAlgorithm: sigpb.DigitallySigned_ECDSA,
   474  		DisplayName:        "Llamas Log",
   475  		Description:        "Registry of publicly-owned llamas",
   476  		PrivateKey:         privateKey,
   477  		PublicKey: &keyspb.PublicKey{
   478  			Der: ktestonly.MustMarshalPublicPEMToDER(publicKeyPEM),
   479  		},
   480  		MaxRootDuration: ptypes.DurationProto(1000 * time.Millisecond),
   481  	}
   482  }