github.com/opentofu/opentofu@v1.7.1/internal/getproviders/package_authentication_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package getproviders 7 8 import ( 9 "crypto/sha256" 10 "encoding/base64" 11 "errors" 12 "fmt" 13 "strings" 14 "testing" 15 16 openpgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors" 17 tfaddr "github.com/opentofu/registry-address" 18 19 "github.com/google/go-cmp/cmp" 20 21 "github.com/ProtonMail/go-crypto/openpgp" 22 ) 23 24 func TestPackageAuthenticationResult(t *testing.T) { 25 tests := []struct { 26 result *PackageAuthenticationResult 27 want string 28 }{ 29 { 30 nil, 31 "unauthenticated", 32 }, 33 { 34 &PackageAuthenticationResult{result: signed}, 35 "signed", 36 }, 37 } 38 for _, test := range tests { 39 if got := test.result.String(); got != test.want { 40 t.Errorf("wrong value: got %q, want %q", got, test.want) 41 } 42 } 43 } 44 45 // mockAuthentication is an implementation of the PackageAuthentication 46 // interface which returns fixed values. This is used to test the combining 47 // logic of PackageAuthenticationAll. 48 type mockAuthentication struct { 49 result packageAuthenticationResult 50 err error 51 } 52 53 func (m mockAuthentication) AuthenticatePackage(localLocation PackageLocation) (*PackageAuthenticationResult, error) { 54 if m.err == nil { 55 return &PackageAuthenticationResult{result: m.result}, nil 56 } else { 57 return nil, m.err 58 } 59 } 60 61 var _ PackageAuthentication = (*mockAuthentication)(nil) 62 63 // If all authentications succeed, the returned result should come from the 64 // last authentication. 65 func TestPackageAuthenticationAll_success(t *testing.T) { 66 result, err := PackageAuthenticationAll( 67 &mockAuthentication{result: verifiedChecksum}, 68 &mockAuthentication{result: signed}, 69 ).AuthenticatePackage(nil) 70 71 want := PackageAuthenticationResult{result: signed} 72 if result == nil || *result != want { 73 t.Errorf("wrong result: want %#v, got %#v", want, result) 74 } 75 if err != nil { 76 t.Errorf("wrong err: got %#v, want nil", err) 77 } 78 } 79 80 // If an authentication fails, its error should be returned along with a nil 81 // result. 82 func TestPackageAuthenticationAll_failure(t *testing.T) { 83 someError := errors.New("some error") 84 result, err := PackageAuthenticationAll( 85 &mockAuthentication{result: verifiedChecksum}, 86 &mockAuthentication{err: someError}, 87 &mockAuthentication{result: signed}, 88 ).AuthenticatePackage(nil) 89 90 if result != nil { 91 t.Errorf("wrong result: got %#v, want nil", result) 92 } 93 if err != someError { 94 t.Errorf("wrong err: got %#v, want %#v", err, someError) 95 } 96 } 97 98 // Package hash authentication requires a zip file or directory fixture and a 99 // known-good set of hashes, of which the authenticator will pick one. The 100 // result should be "verified checksum". 101 func TestPackageHashAuthentication_success(t *testing.T) { 102 // Location must be a PackageLocalArchive path 103 location := PackageLocalDir("testdata/filesystem-mirror/registry.opentofu.org/hashicorp/null/2.0.0/linux_amd64") 104 105 wantHashes := []Hash{ 106 // Known-good HashV1 result for this directory 107 Hash("h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="), 108 } 109 110 auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, wantHashes) 111 result, err := auth.AuthenticatePackage(location) 112 113 wantResult := PackageAuthenticationResult{result: verifiedChecksum} 114 if result == nil || *result != wantResult { 115 t.Errorf("wrong result: got %#v, want %#v", result, wantResult) 116 } 117 if err != nil { 118 t.Errorf("wrong err: got %s, want nil", err) 119 } 120 } 121 122 // Package has authentication can fail for various reasons. 123 func TestPackageHashAuthentication_failure(t *testing.T) { 124 tests := map[string]struct { 125 location PackageLocation 126 err string 127 }{ 128 "missing file": { 129 PackageLocalArchive("testdata/no-package-here.zip"), 130 "failed to verify provider package checksums: lstat testdata/no-package-here.zip: no such file or directory", 131 }, 132 "checksum mismatch": { 133 PackageLocalDir("testdata/filesystem-mirror/registry.opentofu.org/hashicorp/null/2.0.0/linux_amd64"), 134 "provider package doesn't match the expected checksum \"h1:invalid\"", 135 }, 136 "invalid zip file": { 137 PackageLocalArchive("testdata/filesystem-mirror/registry.opentofu.org/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip"), 138 "failed to verify provider package checksums: zip: not a valid zip file", 139 }, 140 } 141 142 for name, test := range tests { 143 t.Run(name, func(t *testing.T) { 144 // Invalid expected hash, either because we'll error before we 145 // reach it, or we want to force a checksum mismatch. 146 auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, []Hash{"h1:invalid"}) 147 result, err := auth.AuthenticatePackage(test.location) 148 149 if result != nil { 150 t.Errorf("wrong result: got %#v, want nil", result) 151 } 152 if gotErr := err.Error(); gotErr != test.err { 153 t.Errorf("wrong err: got %q, want %q", gotErr, test.err) 154 } 155 }) 156 } 157 } 158 159 // Archive checksum authentication requires a file fixture and a known-good 160 // SHA256 hash. The result should be "verified checksum". 161 func TestArchiveChecksumAuthentication_success(t *testing.T) { 162 // Location must be a PackageLocalArchive path 163 location := PackageLocalArchive("testdata/filesystem-mirror/registry.opentofu.org/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip") 164 165 // Known-good SHA256 hash for this archive 166 wantSHA256Sum := [sha256.Size]byte{ 167 0x4f, 0xb3, 0x98, 0x49, 0xf2, 0xe1, 0x38, 0xeb, 168 0x16, 0xa1, 0x8b, 0xa0, 0xc6, 0x82, 0x63, 0x5d, 169 0x78, 0x1c, 0xb8, 0xc3, 0xb2, 0x59, 0x01, 0xdd, 170 0x5a, 0x79, 0x2a, 0xde, 0x97, 0x11, 0xf5, 0x01, 171 } 172 173 auth := NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, wantSHA256Sum) 174 result, err := auth.AuthenticatePackage(location) 175 176 wantResult := PackageAuthenticationResult{result: verifiedChecksum} 177 if result == nil || *result != wantResult { 178 t.Errorf("wrong result: got %#v, want %#v", result, wantResult) 179 } 180 if err != nil { 181 t.Errorf("wrong err: got %s, want nil", err) 182 } 183 } 184 185 // Archive checksum authentication can fail for various reasons. These test 186 // cases are almost exhaustive, missing only an io.Copy error which is 187 // difficult to induce. 188 func TestArchiveChecksumAuthentication_failure(t *testing.T) { 189 tests := map[string]struct { 190 location PackageLocation 191 err string 192 }{ 193 "missing file": { 194 PackageLocalArchive("testdata/no-package-here.zip"), 195 "failed to compute checksum for testdata/no-package-here.zip: lstat testdata/no-package-here.zip: no such file or directory", 196 }, 197 "checksum mismatch": { 198 PackageLocalArchive("testdata/filesystem-mirror/registry.opentofu.org/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip"), 199 "archive has incorrect checksum zh:4fb39849f2e138eb16a18ba0c682635d781cb8c3b25901dd5a792ade9711f501 (expected zh:0000000000000000000000000000000000000000000000000000000000000000)", 200 }, 201 "invalid location": { 202 PackageLocalDir("testdata/filesystem-mirror/tfe.example.com/AwesomeCorp/happycloud/0.1.0-alpha.2/darwin_amd64"), 203 "cannot check archive hash for non-archive location testdata/filesystem-mirror/tfe.example.com/AwesomeCorp/happycloud/0.1.0-alpha.2/darwin_amd64", 204 }, 205 } 206 207 for name, test := range tests { 208 t.Run(name, func(t *testing.T) { 209 // Zero expected checksum, either because we'll error before we 210 // reach it, or we want to force a checksum mismatch 211 auth := NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [sha256.Size]byte{0}) 212 result, err := auth.AuthenticatePackage(test.location) 213 214 if result != nil { 215 t.Errorf("wrong result: got %#v, want nil", result) 216 } 217 if gotErr := err.Error(); gotErr != test.err { 218 t.Errorf("wrong err: got %q, want %q", gotErr, test.err) 219 } 220 }) 221 } 222 } 223 224 // Matching checksum authentication takes a SHA256SUMS document, an archive 225 // filename, and an expected SHA256 hash. On success both return values should 226 // be nil. 227 func TestMatchingChecksumAuthentication_success(t *testing.T) { 228 // Location is unused 229 location := PackageLocalArchive("testdata/my-package.zip") 230 231 // Two different checksums for other files 232 wantSHA256Sum := [sha256.Size]byte{0xde, 0xca, 0xde} 233 otherSHA256Sum := [sha256.Size]byte{0xc0, 0xff, 0xee} 234 235 document := []byte( 236 fmt.Sprintf( 237 "%x README.txt\n%x my-package.zip\n", 238 otherSHA256Sum, 239 wantSHA256Sum, 240 ), 241 ) 242 filename := "my-package.zip" 243 244 auth := NewMatchingChecksumAuthentication(document, filename, wantSHA256Sum) 245 result, err := auth.AuthenticatePackage(location) 246 247 // NOTE: This also tests the expired key ignore logic as they key in the test is expired 248 if result != nil { 249 t.Errorf("wrong result: got %#v, want nil", result) 250 } 251 if err != nil { 252 t.Errorf("wrong err: got %s, want nil", err) 253 } 254 } 255 256 // Matching checksum authentication can fail for three reasons: no checksum 257 // in the document for the filename, invalid checksum value, and non-matching 258 // checksum value. 259 func TestMatchingChecksumAuthentication_failure(t *testing.T) { 260 wantSHA256Sum := [sha256.Size]byte{0xde, 0xca, 0xde} 261 filename := "my-package.zip" 262 263 tests := map[string]struct { 264 document []byte 265 err string 266 }{ 267 "no checksum for filename": { 268 []byte( 269 fmt.Sprintf( 270 "%x README.txt", 271 [sha256.Size]byte{0xbe, 0xef}, 272 ), 273 ), 274 `checksum list has no SHA-256 hash for "my-package.zip"`, 275 }, 276 "invalid checksum": { 277 []byte( 278 fmt.Sprintf( 279 "%s README.txt\n%s my-package.zip", 280 "horses", 281 "chickens", 282 ), 283 ), 284 `checksum list has invalid SHA256 hash "chickens": encoding/hex: invalid byte: U+0068 'h'`, 285 }, 286 "checksum mismatch": { 287 []byte( 288 fmt.Sprintf( 289 "%x README.txt\n%x my-package.zip", 290 [sha256.Size]byte{0xbe, 0xef}, 291 [sha256.Size]byte{0xc0, 0xff, 0xee}, 292 ), 293 ), 294 "checksum list has unexpected SHA-256 hash c0ffee0000000000000000000000000000000000000000000000000000000000 (expected decade0000000000000000000000000000000000000000000000000000000000)", 295 }, 296 } 297 298 for name, test := range tests { 299 t.Run(name, func(t *testing.T) { 300 // Location is unused 301 location := PackageLocalArchive("testdata/my-package.zip") 302 303 auth := NewMatchingChecksumAuthentication(test.document, filename, wantSHA256Sum) 304 result, err := auth.AuthenticatePackage(location) 305 306 if result != nil { 307 t.Errorf("wrong result: got %#v, want nil", result) 308 } 309 if gotErr := err.Error(); gotErr != test.err { 310 t.Errorf("wrong err: got %q, want %q", gotErr, test.err) 311 } 312 }) 313 } 314 } 315 316 // Signature authentication takes a checksum document, a signature, and a list 317 // of signing keys. If the document is signed by one of the given keys, the 318 // authentication is successful. The value of the result depends on the signing 319 // key. 320 func TestSignatureAuthentication_success(t *testing.T) { 321 tests := map[string]struct { 322 signature string 323 keys []SigningKey 324 result PackageAuthenticationResult 325 }{ 326 "community provider": { 327 testAuthorSignatureGoodBase64, 328 []SigningKey{ 329 { 330 ASCIIArmor: testAuthorKeyArmor, 331 }, 332 }, 333 PackageAuthenticationResult{ 334 result: signed, 335 KeyID: testAuthorKeyID, 336 }, 337 }, 338 "multiple signing keys": { 339 testAuthorSignatureGoodBase64, 340 []SigningKey{ 341 { 342 ASCIIArmor: anotherPublicKey, 343 }, 344 { 345 ASCIIArmor: testAuthorKeyArmor, 346 }, 347 }, 348 PackageAuthenticationResult{ 349 result: signed, 350 KeyID: testAuthorKeyID, 351 }, 352 }, 353 } 354 355 for name, test := range tests { 356 t.Run(name, func(t *testing.T) { 357 // Location is unused 358 location := PackageLocalArchive("testdata/my-package.zip") 359 // 360 //providerSource, err := tfaddr.ParseProviderSource("testdata/my-package.zip") 361 //if err != nil { 362 // t.Fatal(err) 363 //} 364 365 signature, err := base64.StdEncoding.DecodeString(test.signature) 366 if err != nil { 367 t.Fatal(err) 368 } 369 370 auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testShaSumsPlaceholder), signature, test.keys, nil) 371 result, err := auth.AuthenticatePackage(location) 372 373 if result == nil || *result != test.result { 374 t.Errorf("wrong result: got %#v, want %#v", result, test.result) 375 } 376 if err != nil { 377 t.Errorf("wrong err: got %s, want nil", err) 378 } 379 }) 380 } 381 } 382 383 func TestNewSignatureAuthentication_success(t *testing.T) { 384 tests := map[string]struct { 385 signature string 386 keys []SigningKey 387 result PackageAuthenticationResult 388 }{ 389 "official provider": { 390 testHashicorpSignatureGoodBase64, 391 []SigningKey{ 392 { 393 ASCIIArmor: TestingPublicKey, 394 }, 395 }, 396 PackageAuthenticationResult{ 397 result: signed, 398 KeyID: testHashiCorpPublicKeyID, 399 }, 400 }, 401 } 402 403 for name, test := range tests { 404 t.Run(name, func(t *testing.T) { 405 // Location is unused 406 location := PackageLocalArchive("testdata/my-package.zip") 407 408 signature, err := base64.StdEncoding.DecodeString(test.signature) 409 if err != nil { 410 t.Fatal(err) 411 } 412 413 auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testProviderShaSums), signature, test.keys, nil) 414 result, err := auth.AuthenticatePackage(location) 415 416 if result == nil || *result != test.result { 417 t.Errorf("wrong result: got %#v, want %#v", result, test.result) 418 } 419 if err != nil { 420 t.Errorf("wrong err: got %s, want nil", err) 421 } 422 }) 423 } 424 } 425 func TestNewSignatureAuthentication_expired(t *testing.T) { 426 tests := map[string]struct { 427 signature string 428 keys []SigningKey 429 }{ 430 "official provider": { 431 testHashicorpSignatureGoodBase64, 432 []SigningKey{ 433 { 434 ASCIIArmor: TestingPublicKey, 435 }, 436 }, 437 }, 438 } 439 t.Setenv(enforceGPGExpirationEnvName, "true") 440 441 for name, test := range tests { 442 t.Run(name, func(t *testing.T) { 443 // Location is unused 444 location := PackageLocalArchive("testdata/my-package.zip") 445 446 signature, err := base64.StdEncoding.DecodeString(test.signature) 447 if err != nil { 448 t.Fatal(err) 449 } 450 451 auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testProviderShaSums), signature, test.keys, nil) 452 _, err = auth.AuthenticatePackage(location) 453 454 if err == nil { 455 t.Errorf("wrong err: got %s, want %s", err, openpgpErrors.ErrKeyExpired) 456 } 457 }) 458 } 459 t.Setenv(enforceGPGExpirationEnvName, "") 460 } 461 462 // Signature authentication can fail for many reasons, most of which are due 463 // to OpenPGP failures from malformed keys or signatures. 464 func TestSignatureAuthentication_failure(t *testing.T) { 465 tests := map[string]struct { 466 signature string 467 keys []SigningKey 468 err string 469 }{ 470 "invalid key": { 471 testHashicorpSignatureGoodBase64, 472 []SigningKey{ 473 { 474 ASCIIArmor: "invalid PGP armor value", 475 }, 476 }, 477 "error decoding signing key: openpgp: invalid argument: no armored data found", 478 }, 479 "invalid signature": { 480 testSignatureBadBase64, 481 []SigningKey{ 482 { 483 ASCIIArmor: testAuthorKeyArmor, 484 }, 485 }, 486 "error checking signature: openpgp: invalid data: signature subpacket truncated", 487 }, 488 "no keys match signature": { 489 testAuthorSignatureGoodBase64, 490 []SigningKey{ 491 { 492 ASCIIArmor: TestingPublicKey, 493 }, 494 }, 495 "authentication signature from unknown issuer", 496 }, 497 } 498 499 for name, test := range tests { 500 t.Run(name, func(t *testing.T) { 501 // Location is unused 502 location := PackageLocalArchive("testdata/my-package.zip") 503 504 signature, err := base64.StdEncoding.DecodeString(test.signature) 505 if err != nil { 506 t.Fatal(err) 507 } 508 509 auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testShaSumsPlaceholder), signature, test.keys, nil) 510 result, err := auth.AuthenticatePackage(location) 511 512 if result != nil { 513 t.Errorf("wrong result: got %#v, want nil", result) 514 } 515 if gotErr := err.Error(); gotErr != test.err { 516 t.Errorf("wrong err: got %s, want %s", gotErr, test.err) 517 } 518 }) 519 } 520 } 521 522 func TestSignatureAuthentication_acceptableHashes(t *testing.T) { 523 auth := NewSignatureAuthentication(PackageMeta{}, []byte(testShaSumsRealistic), nil, nil, nil) 524 authWithHashes, ok := auth.(PackageAuthenticationHashes) 525 if !ok { 526 t.Fatalf("%T does not implement PackageAuthenticationHashes", auth) 527 } 528 got := authWithHashes.AcceptableHashes() 529 want := []Hash{ 530 // These are the hashes encoded in constant testShaSumsRealistic 531 "zh:7d7e888fdd28abfe00894f9055209b9eec785153641de98e6852aa071008d4ee", 532 "zh:f8b6cf9ade087c17826d49d89cef21261cdc22bd27065bbc5b27d7dbf7fbbf6c", 533 "zh:a5ba9945606bb7bfb821ba303957eeb40dd9ee4e706ba8da1eaf7cbeb0356e63", 534 "zh:df3a5a8d6ffff7bacf19c92d10d0d500f98169ea17b3764b01a789f563d1aad7", 535 "zh:086119a26576d06b8281a97e8644380da89ce16197cd955f74ea5ee664e9358b", 536 "zh:1e5f7a5f3ade7b8b1d1d59c5cea2e1a2f8d2f8c3f41962dbbe8647e222be8239", 537 "zh:0e9fd0f3e2254b526a0e81e0cfdfc82583b0cd343778c53ead21aa7d52f776d7", 538 "zh:66a947e7de1c74caf9f584c3ed4e91d2cb1af6fe5ce8abaf1cf8f7ff626a09d1", 539 "zh:def1b73849bec0dc57a04405847921bf9206c75b52ae9de195476facb26bd85e", 540 "zh:48f1826ec31d6f104e46cc2022b41f30cd1019ef48eaec9697654ef9ec37a879", 541 "zh:17e0b496022bc4e4137be15e96d2b051c8acd6e14cb48d9b13b262330464f6cc", 542 "zh:2696c86228f491bc5425561c45904c9ce39b1c676b1e17734cb2ee6b578c4bcd", 543 } 544 if diff := cmp.Diff(want, got); diff != "" { 545 t.Errorf("wrong result\n%s", diff) 546 } 547 } 548 549 const testAuthorKeyID = `37A6AB3BCF2C170A` 550 551 // testAuthorKeyArmor is test key ID 5BFEEC4317E746008621970637A6AB3BCF2C170A. 552 const testAuthorKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK----- 553 554 mQENBF5vhgYBCAC40OcC2hEx3yGiLhHMbt7DAVEQ0nZwAWy6oL98niknLumBa1VO 555 nMYshP+o/FKOFatBl8aXhmDo606P6pD9d4Pg/WNehqT7hGNHcAFlm+8qjQAvE5uX 556 Z/na/Np7dmWasCiL5hYyHEnKU/XFpc9KyicbkS7n8igP1LEb8xDD1pMLULQsQHA4 557 258asvtwjoYTZIij1I6bUE178bGFPNCfj+FzQM8nKzPpDVxZ7njN9c2sB9FEdJ1+ 558 S9mZQNK5PbJuEAOpD5Jp9BnGE16jsLUhDmvGHBjFZAXMBkNSloEMHhs2ty9lEzoF 559 eJmJx7XCGw+ds1SWp4MsHQPWzXxAlrfa4GMlABEBAAG0R1RlcnJhZm9ybSBUZXN0 560 aW5nIChwbHVnaW4vZGlzY292ZXJ5LykgPHRlcnJhZm9ybSt0ZXN0aW5nQGhhc2hp 561 Y29ycC5jb20+iQFOBBMBCAA4FiEEW/7sQxfnRgCGIZcGN6arO88sFwoFAl5vhgYC 562 GwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQN6arO88sFwpWvQf/apaMu4Bm 563 ea8AGjdl9acQhHBpWsyiHLIfZvN11xxN/f3+YITvPXIe2PMgveqNfXxu6PIeZGDb 564 0DBvnBQy/vqmA+sCQ8t8+kIWdfZ1EeM2YcXdmAEtriooLvc85JFYjafLIKSj9N7o 565 V/R/e1BCW/v1/7Je47c+6FSt3HHhwyT5AZ3BCq1zpw6PeCDSQ/gZr3Mvq4CjeLA/ 566 K+8TM3KyOF4qBGDvzGzp/t9umQSS2L0ozd90lxJtf5Q8ozqDaBiDo+f/osXT2EvN 567 VwPP/xh/gABkXiNrPylFbeD+XPAC4N7NmYK5aPDzRYXXknP8e9PDMykoJKZ+bSdz 568 F3IZ4q5RDHmmNbkBDQReb4YGAQgAt15e1F8TPQQm1jK8+scypHgfmPHbp7Qsulo1 569 GTcUd8QmhbR4kayuLDEpJYzq6+IoTM4TPqsdVuq/1Nwey9oyK0wXk/SUR29nRIQh 570 3GBg7JVg1YsObsfVTvEflYOdjk8T/Udqs4I6HnmSbtzsaohzybutpWXPUkW8OzFI 571 ATwfVTrrz70Yxs+ly0nSEH2Yf+kg2uYZvv5KsJ3MNENhXnHnlaTy2IfhsxAX0xOG 572 pa9fXV3NzdEbl0mYaEzMi77qRAyIQ9VrIL5F0yY/LlbpLSl6xk2+BB2v3a1Ey6SJ 573 w4/le6AM0wlH2hKPCTlkvM0IvUWjlzrPzCkeu027iVc+fqdyiQARAQABiQE2BBgB 574 CAAgFiEEW/7sQxfnRgCGIZcGN6arO88sFwoFAl5vhgYCGwwACgkQN6arO88sFwqz 575 nAf/eF4oZG9F8sJX01mVdDm/L7Uthe4xjTdl7jwV4ygNX+pCyWrww3qc3qbd3QKg 576 CFqIt/TAPE/OxHxCFuxalQefpOqfxjKzvcktxzWmpgxaWsvHaXiS4bKBPz78N/Ke 577 MUtcjGHyLeSzYPUfjquqDzQxqXidRYhyHGSy9c0NKZ6wCElLZ6KcmCQb4sZxVwfu 578 ssjwAFbPMp1nr0f5SWCJfhTh7QF7lO2ldJaKMlcBM8aebmqFQ52P7ZWOFcgeerng 579 G7Zdrci1KEd943HhzDCsUFz4gJwbvUyiAYb2ddndpUBkYwCB/XrHWPOSnGxHgZoo 580 1gIqed9OV/+s5wKxZPjL0pCStQ== 581 =mYqJ 582 -----END PGP PUBLIC KEY BLOCK-----` 583 584 // testAuthorEccKeyArmor uses Curve 25519 and has test key ID D01ED5C4BB1ED36A014B0D376540DDA046E5E135 585 const testAuthorEccKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK----- 586 587 mDMEY1B7+hYJKwYBBAHaRw8BAQdAFRDpASP+iDY+QotOBP9DF5CfuhSBD8Dl0hSG 588 D7plEsO0M1RlcnJhZm9ybSBUZXN0aW5nIDx0ZXJyYWZvcm0rdGVzdGluZ0BoYXNo 589 aWNvcnAuY29tPoiTBBMWCgA7FiEE0B7VxLse02oBSw03ZUDdoEbl4TUFAmNQe/oC 590 GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQZUDdoEbl4TWhwwD+N/BR 591 pR9NhRFDm+JRhA3saKmpTSRo9yJnr6tRlumE4KQA/A2cOCDeezf6t3SXltoYUKIt 592 EYmbLxgMDlffVkFyC8IMuDgEY1B7+hIKKwYBBAGXVQEFAQEHQJ7frE76Le1qI1Go 593 dfrVIzEgAcYjDW6T01/V95wgqPIuAwEIB4h4BBgWCgAgFiEE0B7VxLse02oBSw03 594 ZUDdoEbl4TUFAmNQe/oCGwwACgkQZUDdoEbl4TWvsAD/YSQAigAH5hq4OmK4gs0J 595 O74RFokGZzbPtoIvutb8eYoA/1QxxyqE/8A4Z21azYEO0j563LRa8SkZcB5UPDy3 596 7ngJ 597 =Xb0o 598 -----END PGP PUBLIC KEY BLOCK-----` 599 600 // testShaSumsPlaceholder is a string that represents a signed document that 601 // the signature authenticator will check. Some of the signature values in 602 // other constants in this file are signing this string. 603 const testShaSumsPlaceholder = "example shasums data" 604 605 // testShaSumsRealistic is a more realistic SHA256SUMS document that we can use 606 // to test the AcceptableHashes method. The signature values in other constants 607 // in this file do not sign this string. 608 const testShaSumsRealistic = `7d7e888fdd28abfe00894f9055209b9eec785153641de98e6852aa071008d4ee terraform_0.14.0-alpha20200923_darwin_amd64.zip 609 f8b6cf9ade087c17826d49d89cef21261cdc22bd27065bbc5b27d7dbf7fbbf6c terraform_0.14.0-alpha20200923_freebsd_386.zip 610 a5ba9945606bb7bfb821ba303957eeb40dd9ee4e706ba8da1eaf7cbeb0356e63 terraform_0.14.0-alpha20200923_freebsd_amd64.zip 611 df3a5a8d6ffff7bacf19c92d10d0d500f98169ea17b3764b01a789f563d1aad7 terraform_0.14.0-alpha20200923_freebsd_arm.zip 612 086119a26576d06b8281a97e8644380da89ce16197cd955f74ea5ee664e9358b terraform_0.14.0-alpha20200923_linux_386.zip 613 1e5f7a5f3ade7b8b1d1d59c5cea2e1a2f8d2f8c3f41962dbbe8647e222be8239 terraform_0.14.0-alpha20200923_linux_amd64.zip 614 0e9fd0f3e2254b526a0e81e0cfdfc82583b0cd343778c53ead21aa7d52f776d7 terraform_0.14.0-alpha20200923_linux_arm.zip 615 66a947e7de1c74caf9f584c3ed4e91d2cb1af6fe5ce8abaf1cf8f7ff626a09d1 terraform_0.14.0-alpha20200923_openbsd_386.zip 616 def1b73849bec0dc57a04405847921bf9206c75b52ae9de195476facb26bd85e terraform_0.14.0-alpha20200923_openbsd_amd64.zip 617 48f1826ec31d6f104e46cc2022b41f30cd1019ef48eaec9697654ef9ec37a879 terraform_0.14.0-alpha20200923_solaris_amd64.zip 618 17e0b496022bc4e4137be15e96d2b051c8acd6e14cb48d9b13b262330464f6cc terraform_0.14.0-alpha20200923_windows_386.zip 619 2696c86228f491bc5425561c45904c9ce39b1c676b1e17734cb2ee6b578c4bcd terraform_0.14.0-alpha20200923_windows_amd64.zip` 620 621 // testAuthorSignatureGoodBase64 is a signature of testShaSums signed with 622 // testAuthorKeyArmor, which represents the SHA256SUMS.sig file downloaded for 623 // a release. 624 const testAuthorSignatureGoodBase64 = `iQEzBAABCAAdFiEEW/7sQxfnRgCGIZcGN6arO88s` + 625 `FwoFAl5vh7gACgkQN6arO88sFwrAlQf6Al77qzjxNIj+NQNJfBGYUE5jHIgcuWOs1IPRTYUI` + 626 `rHQIUU2RVrdHoAefKTKNzGde653JK/pYTflSV+6ini3/aZZnXlF6t001w3wswmakdwTr0hXx` + 627 `Ez/hHYio72Gpn7+T/L+nl6dKkjeGqd/Kor5x2TY9uYB737ESmAe5T8ZlPaGMFHh0mYlNTeRq` + 628 `4qIKqL6DwddBF4Ju2svn2MeNMGfE358H31mxAl2k4PPrwBTR1sFUCUOzAXVA/g9Ov5Y9ni2G` + 629 `rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` + 630 `n1ayZdaCIw/r4w==` 631 632 // testSignatureBadBase64 is an invalid signature. 633 const testSignatureBadBase64 = `iQEzBAABCAAdFiEEW/7sQxfnRgCGIZcGN6arO88s` + 634 `4qIKqL6DwddBF4Ju2svn2MeNMGfE358H31mxAl2k4PPrwBTR1sFUCUOzAXVA/g9Ov5Y9ni2G` + 635 `rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` + 636 `n1ayZdaCIw/r4w==` 637 638 // testHashiCorpPublicKeyID is the Key ID of the HashiCorpPublicKey. 639 const testHashiCorpPublicKeyID = `34365D9472D7468F` 640 641 const testProviderShaSums = `fea4227271ebf7d9e2b61b89ce2328c7262acd9fd190e1fd6d15a591abfa848e terraform-provider-null_3.1.0_darwin_amd64.zip 642 9ebf4d9704faba06b3ec7242c773c0fbfe12d62db7d00356d4f55385fc69bfb2 terraform-provider-null_3.1.0_darwin_arm64.zip 643 a6576c81adc70326e4e1c999c04ad9ca37113a6e925aefab4765e5a5198efa7e terraform-provider-null_3.1.0_freebsd_386.zip 644 5f9200bf708913621d0f6514179d89700e9aa3097c77dac730e8ba6e5901d521 terraform-provider-null_3.1.0_freebsd_amd64.zip 645 fc39cc1fe71234a0b0369d5c5c7f876c71b956d23d7d6f518289737a001ba69b terraform-provider-null_3.1.0_freebsd_arm.zip 646 c797744d08a5307d50210e0454f91ca4d1c7621c68740441cf4579390452321d terraform-provider-null_3.1.0_linux_386.zip 647 53e30545ff8926a8e30ad30648991ca8b93b6fa496272cd23b26763c8ee84515 terraform-provider-null_3.1.0_linux_amd64.zip 648 cecb6a304046df34c11229f20a80b24b1603960b794d68361a67c5efe58e62b8 terraform-provider-null_3.1.0_linux_arm64.zip 649 e1371aa1e502000d9974cfaff5be4cfa02f47b17400005a16f14d2ef30dc2a70 terraform-provider-null_3.1.0_linux_arm.zip 650 a8a42d13346347aff6c63a37cda9b2c6aa5cc384a55b2fe6d6adfa390e609c53 terraform-provider-null_3.1.0_windows_386.zip 651 02a1675fd8de126a00460942aaae242e65ca3380b5bb192e8773ef3da9073fd2 terraform-provider-null_3.1.0_windows_amd64.zip 652 ` 653 654 // testHashicorpSignatureGoodBase64 is a signature of testProviderShaSums signed with 655 // HashicorpPublicKey, which represents the SHA256SUMS.sig file downloaded for 656 // an official release. 657 const testHashicorpSignatureGoodBase64 = `wsFcBAABCAAQBQJgga+GCRCwtEEJdoW2dgAA` + 658 `o0YQAAW911BGDr2WHLo5NwcZenwHyxL5DX9g+4BknKbc/WxRC1hD8Afi3eygZk1yR6eT4Gp2H` + 659 `yNOwCjGL1PTONBumMfj9udIeuX8onrJMMvjFHh+bORGxBi4FKr4V3b2ZV1IYOjWMEyyTGRDvw` + 660 `SCdxBkp3apH3s2xZLmRoAj84JZ4KaxGF7hlT0j4IkNyQKd2T5cCByN9DV80+x+HtzaOieFwJL` + 661 `97iyGj6aznXfKfslK6S4oIrVTwyLTrQbxSxA0LsdUjRPHnJamL3sFOG77qUEUoXG3r61yi5vW` + 662 `V4P5gCH/+C+VkfGHqaB1s0jHYLxoTEXtwthe66MydDBPe2Hd0J12u9ppOIeK3leeb4uiixWIi` + 663 `rNdpWyjr/LU1KKWPxsDqMGYJ9TexyWkXjEpYmIEiY1Rxar8jrLh+FqVAhxRJajjgSRu5pZj50` + 664 `CNeKmmbyolLhPCmICjYYU/xKPGXSyDFqonVVyMWCSpO+8F38OmwDQHIk5AWyc8hPOAZ+g5N95` + 665 `cfUAzEqlvmNvVHQIU40Y6/Ip2HZzzFCLKQkMP1aDakYHq5w4ZO/ucjhKuoh1HDQMuMnZSu4eo` + 666 `2nMTBzYZnUxwtROrJZF1t103avbmP2QE/GaPvLIQn7o5WMV3ZcPCJ+szzzby7H2e33WIynrY/` + 667 `95ensBxh7mGFbcQ1C59b5o7viwIaaY2` 668 669 // entityString function is used for logging the signing key. 670 func TestEntityString(t *testing.T) { 671 var tests = []struct { 672 name string 673 entity *openpgp.Entity 674 expected string 675 }{ 676 { 677 "nil", 678 nil, 679 "", 680 }, 681 { 682 "testAuthorEccKeyArmor", 683 testReadArmoredEntity(t, testAuthorEccKeyArmor), 684 "6540DDA046E5E135 Terraform Testing <terraform+testing@hashicorp.com>", 685 }, 686 { 687 "testAuthorKeyArmor", 688 testReadArmoredEntity(t, testAuthorKeyArmor), 689 "37A6AB3BCF2C170A Terraform Testing (plugin/discovery/) <terraform+testing@hashicorp.com>", 690 }, 691 { 692 "HashicorpPublicKey", 693 testReadArmoredEntity(t, TestingPublicKey), 694 "34365D9472D7468F HashiCorp Security (hashicorp.com/security) <security@hashicorp.com>", 695 }, 696 { 697 "HashicorpPartnersKey", 698 testReadArmoredEntity(t, anotherPublicKey), 699 "7D72D4268E4660FC HashiCorp Security (Terraform Partner Signing) <security+terraform@hashicorp.com>", 700 }, 701 } 702 703 for _, tt := range tests { 704 t.Run(tt.name, func(t *testing.T) { 705 actual := entityString(tt.entity) 706 if actual != tt.expected { 707 t.Errorf("expected %s, actual %s", tt.expected, actual) 708 } 709 }) 710 } 711 } 712 713 func testReadArmoredEntity(t *testing.T, armor string) *openpgp.Entity { 714 data := strings.NewReader(armor) 715 716 el, err := openpgp.ReadArmoredKeyRing(data) 717 if err != nil { 718 t.Fatal(err) 719 } 720 721 if count := len(el); count != 1 { 722 t.Fatalf("expected 1 entity, got %d", count) 723 } 724 725 return el[0] 726 } 727 728 func TestShouldEnforceGPGValidation(t *testing.T) { 729 tests := []struct { 730 name string 731 providerSource *tfaddr.Provider 732 keys []SigningKey 733 envVarValue string 734 expected bool 735 }{ 736 { 737 name: "default provider registry, no keys", 738 providerSource: &tfaddr.Provider{ 739 Hostname: tfaddr.DefaultProviderRegistryHost, 740 }, 741 keys: []SigningKey{}, 742 envVarValue: "", 743 expected: false, 744 }, 745 { 746 name: "default provider registry, some keys", 747 providerSource: &tfaddr.Provider{ 748 Hostname: tfaddr.DefaultProviderRegistryHost, 749 }, 750 keys: []SigningKey{ 751 { 752 ASCIIArmor: testAuthorKeyArmor, 753 }, 754 }, 755 envVarValue: "", 756 expected: true, 757 }, 758 { 759 name: "non-default provider registry, no keys", 760 providerSource: &tfaddr.Provider{ 761 Hostname: "my-registry.com", 762 }, 763 keys: []SigningKey{}, 764 envVarValue: "", 765 expected: true, 766 }, 767 { 768 name: "non-default provider registry, some keys", 769 providerSource: &tfaddr.Provider{ 770 Hostname: "my-registry.com", 771 }, 772 keys: []SigningKey{ 773 { 774 ASCIIArmor: testAuthorKeyArmor, 775 }, 776 }, 777 envVarValue: "", 778 expected: true, 779 }, 780 // env var "true" 781 { 782 name: "default provider registry, no keys, env var true", 783 providerSource: &tfaddr.Provider{ 784 Hostname: tfaddr.DefaultProviderRegistryHost, 785 }, 786 keys: []SigningKey{}, 787 envVarValue: "true", 788 expected: true, 789 }, 790 { 791 name: "default provider registry, some keys, env var true", 792 providerSource: &tfaddr.Provider{ 793 Hostname: tfaddr.DefaultProviderRegistryHost, 794 }, 795 keys: []SigningKey{ 796 { 797 ASCIIArmor: testAuthorKeyArmor, 798 }, 799 }, 800 envVarValue: "true", 801 expected: true, 802 }, { 803 name: "non-default provider registry, no keys, env var true", 804 providerSource: &tfaddr.Provider{ 805 Hostname: "my-registry.com", 806 }, 807 keys: []SigningKey{}, 808 envVarValue: "true", 809 expected: true, 810 }, 811 { 812 name: "non-default provider registry, some keys, env var true", 813 providerSource: &tfaddr.Provider{ 814 Hostname: "my-registry.com", 815 }, 816 keys: []SigningKey{ 817 { 818 ASCIIArmor: testAuthorKeyArmor, 819 }, 820 }, 821 envVarValue: "true", 822 expected: true, 823 }, 824 // env var "false" 825 { 826 name: "default provider registry, no keys, env var false", 827 providerSource: &tfaddr.Provider{ 828 Hostname: tfaddr.DefaultProviderRegistryHost, 829 }, 830 keys: []SigningKey{}, 831 envVarValue: "false", 832 expected: false, 833 }, 834 { 835 name: "default provider registry, some keys, env var false", 836 providerSource: &tfaddr.Provider{ 837 Hostname: tfaddr.DefaultProviderRegistryHost, 838 }, 839 keys: []SigningKey{ 840 { 841 ASCIIArmor: testAuthorKeyArmor, 842 }, 843 }, 844 envVarValue: "false", 845 expected: true, 846 }, { 847 name: "non-default provider registry, no keys, env var false", 848 providerSource: &tfaddr.Provider{ 849 Hostname: "my-registry.com", 850 }, 851 keys: []SigningKey{}, 852 envVarValue: "false", 853 expected: true, 854 }, 855 { 856 name: "non-default provider registry, some keys, env var false", 857 providerSource: &tfaddr.Provider{ 858 Hostname: "my-registry.com", 859 }, 860 keys: []SigningKey{ 861 { 862 ASCIIArmor: testAuthorKeyArmor, 863 }, 864 }, 865 envVarValue: "false", 866 expected: true, 867 }, 868 } 869 870 for _, tt := range tests { 871 t.Run(tt.name, func(t *testing.T) { 872 873 sigAuth := signatureAuthentication{ 874 ProviderSource: tt.providerSource, 875 Keys: tt.keys, 876 } 877 878 if tt.envVarValue != "" { 879 t.Setenv(enforceGPGValidationEnvName, tt.envVarValue) 880 } 881 882 actual, err := sigAuth.shouldEnforceGPGValidation() 883 if err != nil { 884 t.Fatal(err) 885 } 886 if actual != tt.expected { 887 t.Errorf("expected %t, actual %t", tt.expected, actual) 888 } 889 }) 890 } 891 }