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 }