From 6daaec5ce1a730fd44c43ae7b13a219dbddf613a Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Fri, 28 Jul 2023 13:09:45 -0600 Subject: [PATCH] Package releaseauth Package releaseauth helps authenticates archives downloaded from a service like releases.hashicorp.com by providing some simple authentication tools: 1. Matching reported SHA-256 hash against a standard SHA256SUMS file. 2. Calculates the SHA-256 checksum of an archive and compares it against a reported hash. 3. Ensures the checksums were signed by HashiCorp. --- internal/releaseauth/all.go | 35 ++++ internal/releaseauth/all_test.go | 43 +++++ internal/releaseauth/checksum.go | 56 ++++++ internal/releaseauth/doc.go | 7 + internal/releaseauth/hash.go | 73 +++++++ internal/releaseauth/matching_sums.go | 52 +++++ internal/releaseauth/signature.go | 180 ++++++++++++++++++ internal/releaseauth/testdata/sample.md | 11 ++ .../releaseauth/testdata/sample.private.key | 106 +++++++++++ .../releaseauth/testdata/sample.public.key | 52 +++++ .../sample_release/sample_0.1.0_SHA256SUMS | 1 + .../sample_0.1.0_SHA256SUMS.sig | Bin 0 -> 566 bytes .../sample_0.1.0_darwin_amd64.zip | Bin 0 -> 377 bytes 13 files changed, 616 insertions(+) create mode 100644 internal/releaseauth/all.go create mode 100644 internal/releaseauth/all_test.go create mode 100644 internal/releaseauth/checksum.go create mode 100644 internal/releaseauth/doc.go create mode 100644 internal/releaseauth/hash.go create mode 100644 internal/releaseauth/matching_sums.go create mode 100644 internal/releaseauth/signature.go create mode 100644 internal/releaseauth/testdata/sample.md create mode 100644 internal/releaseauth/testdata/sample.private.key create mode 100644 internal/releaseauth/testdata/sample.public.key create mode 100644 internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS create mode 100644 internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS.sig create mode 100644 internal/releaseauth/testdata/sample_release/sample_0.1.0_darwin_amd64.zip diff --git a/internal/releaseauth/all.go b/internal/releaseauth/all.go new file mode 100644 index 0000000000..2b69bac4c5 --- /dev/null +++ b/internal/releaseauth/all.go @@ -0,0 +1,35 @@ +package releaseauth + +// Authenticator is a generic interface for interacting with types that authenticate +// an archive. +type Authenticator interface { + Authenticate() error +} + +// All is a meta Authenticator that wraps other Authenticators and ensures they all +// return without failure. +type All struct { + Authenticator + authenticators []Authenticator +} + +var _ Authenticator = All{} + +// AllAuthenticators creates a meta Authenticator that ensures all the +// given Authenticators return without failure. +func AllAuthenticators(authenticators ...Authenticator) All { + return All{ + authenticators: authenticators, + } +} + +// Authenticate returns the first archive authentication failure from +// the list of Authenticators given. +func (a All) Authenticate() error { + for _, auth := range a.authenticators { + if err := auth.Authenticate(); err != nil { + return err + } + } + return nil +} diff --git a/internal/releaseauth/all_test.go b/internal/releaseauth/all_test.go new file mode 100644 index 0000000000..bd8dc38032 --- /dev/null +++ b/internal/releaseauth/all_test.go @@ -0,0 +1,43 @@ +package releaseauth + +import ( + "os" + "testing" +) + +func TestAll(t *testing.T) { + // `sha256sum testdata/sample_release/sample_0.1.0_darwin_amd64.zip | cut -d' ' -f1` + actualChecksum, err := SHA256FromHex("22db2f0c70b50cff42afd4878fea9f6848a63f1b6532bd8b64b899f574acb35d") + if err != nil { + t.Fatal(err) + } + sums, err := os.ReadFile("testdata/sample_release/sample_0.1.0_SHA256SUMS") + if err != nil { + t.Fatal(err) + } + signature, err := os.ReadFile("testdata/sample_release/sample_0.1.0_SHA256SUMS.sig") + if err != nil { + t.Fatal(err) + } + publicKey, err := os.ReadFile("testdata/sample.public.key") + if err != nil { + t.Fatal(err) + } + checksums, err := ParseChecksums(sums) + if err != nil { + t.Fatal(err) + } + + sigAuth := NewSignatureAuthentication(signature, sums) + sigAuth.PublicKey = string(publicKey) + + all := AllAuthenticators( + NewMatchingChecksumsAuthentication(actualChecksum, "sample_0.1.0_darwin_amd64.zip", checksums), + NewChecksumAuthentication(actualChecksum, "testdata/sample_release/sample_0.1.0_darwin_amd64.zip"), + sigAuth, + ) + + if err := all.Authenticate(); err != nil { + t.Fatal(err) + } +} diff --git a/internal/releaseauth/checksum.go b/internal/releaseauth/checksum.go new file mode 100644 index 0000000000..43ebebd160 --- /dev/null +++ b/internal/releaseauth/checksum.go @@ -0,0 +1,56 @@ +package releaseauth + +import ( + "bytes" + "crypto/sha256" + "errors" + "fmt" + "io" + "log" + "os" +) + +// ChecksumAuthentication is an archive Authenticator that ensures a given file +// matches a SHA-256 checksum. It is important to verify the authenticity of the +// given checksum prior to using this Authenticator. +type ChecksumAuthentication struct { + Authenticator + + expected SHA256Hash + archiveLocation string +} + +// ErrChecksumDoesNotMatch is the error returned when the archive checksum does +// not match the given checksum. +var ErrChecksumDoesNotMatch = errors.New("failed to authenticate that the downloaded archive matches the release checksum") + +// NewChecksumAuthentication creates an instance of ChecksumAuthentication with the given +// checksum and file location. +func NewChecksumAuthentication(expected SHA256Hash, archiveLocation string) *ChecksumAuthentication { + return &ChecksumAuthentication{ + expected: expected, + archiveLocation: archiveLocation, + } +} + +func (a ChecksumAuthentication) Authenticate() error { + f, err := os.Open(a.archiveLocation) + if err != nil { + return fmt.Errorf("failed to open downloaded archive: %w", err) + } + defer f.Close() + + h := sha256.New() + _, err = io.Copy(h, f) + if err != nil { + return fmt.Errorf("failed to hash downloaded archive: %w", err) + } + + gotHash := h.Sum(nil) + log.Printf("[TRACE] checksummed %q; got hash %x, expected %x", f.Name(), gotHash, a.expected) + if !bytes.Equal(gotHash, a.expected[:]) { + return ErrChecksumDoesNotMatch + } + + return nil +} diff --git a/internal/releaseauth/doc.go b/internal/releaseauth/doc.go new file mode 100644 index 0000000000..a6ad34f4fd --- /dev/null +++ b/internal/releaseauth/doc.go @@ -0,0 +1,7 @@ +// Package releaseauth helps authenticates archives downloaded from a service +// like releases.hashicorp.com by providing some simple authentication tools: +// +// 1. Matching reported SHA-256 hash against a standard SHA256SUMS file. +// 2. Calculates the SHA-256 checksum of an archive and compares it against a reported hash. +// 3. Ensures the checksums were signed by HashiCorp. +package releaseauth diff --git a/internal/releaseauth/hash.go b/internal/releaseauth/hash.go new file mode 100644 index 0000000000..ed95743485 --- /dev/null +++ b/internal/releaseauth/hash.go @@ -0,0 +1,73 @@ +package releaseauth + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "log" +) + +// SHA256Hash represents a 256-bit SHA hash +type SHA256Hash [sha256.Size]byte + +// ErrInvalidSHA256Hash is returned when the hash is invalid +var ErrInvalidSHA256Hash = errors.New("the value was not a valid SHA-256 hash") + +// SHA256FromHex decodes a SHA256Hash from a hex string dump +func SHA256FromHex(hashHex string) (SHA256Hash, error) { + var result [sha256.Size]byte + hash, err := hex.DecodeString(hashHex) + if err != nil || len(hash) != sha256.Size { + return result, ErrInvalidSHA256Hash + } + + if copy(result[:], hash) != sha256.Size { + panic("could not copy hash value") + } + + return result, nil +} + +// SHA256Checksums decodes a file generated by the sha256sum program +type SHA256Checksums map[string]SHA256Hash + +func ParseChecksums(data []byte) (SHA256Checksums, error) { + items := bytes.Split(data, []byte("\n")) + result := make(map[string]SHA256Hash, len(items)) + + for _, line := range items { + parts := bytes.SplitN(line, []byte(" "), 2) + + if len(parts) != 2 { + break + } + + log.Printf("[TRACE] parsing SHA256SUMS %q = %q", parts[0], parts[1]) + hash, err := SHA256FromHex(string(parts[0])) + if err != nil { + return result, fmt.Errorf("failed to parse checksums: %w", err) + } + + result[string(parts[1])] = hash + } + + return result, nil +} + +// Validate retrieves a SHA256Hash for the a filename and compares it +// to the specified hash. Validate returns an error if the hash is not found +// or if it does not match. +func (c SHA256Checksums) Validate(filename string, hash SHA256Hash) error { + sum, ok := c[filename] + if !ok { + return fmt.Errorf("no checksum found for filename %q", filename) + } + + if sum != hash { + return fmt.Errorf("checksums do not match") + } + + return nil +} diff --git a/internal/releaseauth/matching_sums.go b/internal/releaseauth/matching_sums.go new file mode 100644 index 0000000000..a44a41fab6 --- /dev/null +++ b/internal/releaseauth/matching_sums.go @@ -0,0 +1,52 @@ +package releaseauth + +import "fmt" + +// ErrChecksumMismatch is the error returned when a reported checksum does not match +// what is stored in a SHA256SUMS file +type ErrChecksumMismatch struct { + Inner error +} + +func (e ErrChecksumMismatch) Error() string { + return fmt.Sprintf("failed to authenticate that release checksum matches checksum provided by the manifest: %v", e.Inner) +} + +func (e ErrChecksumMismatch) Unwrap() error { + return e.Inner +} + +// MatchingChecksumsAuthentication is an archive Authenticator that checks if a reported checksum +// matches the checksum that was stored in a SHA256SUMS file +type MatchingChecksumsAuthentication struct { + Authenticator + + expected SHA256Hash + sums SHA256Checksums + baseName string +} + +var _ Authenticator = MatchingChecksumsAuthentication{} + +// NewMatchingChecksumsAuthentication creates the Authenticator given an expected hash, +// the parsed SHA256SUMS data, and a filename. +func NewMatchingChecksumsAuthentication(expected SHA256Hash, baseName string, sums SHA256Checksums) *MatchingChecksumsAuthentication { + return &MatchingChecksumsAuthentication{ + expected: expected, + sums: sums, + baseName: baseName, + } +} + +// Authenticate ensures that the given hash matches what is found in the SHA256SUMS file +// for the corresponding filename +func (a MatchingChecksumsAuthentication) Authenticate() error { + err := a.sums.Validate(a.baseName, a.expected) + if err != nil { + return ErrChecksumMismatch{ + Inner: err, + } + } + + return nil +} diff --git a/internal/releaseauth/signature.go b/internal/releaseauth/signature.go new file mode 100644 index 0000000000..f1124b65c6 --- /dev/null +++ b/internal/releaseauth/signature.go @@ -0,0 +1,180 @@ +package releaseauth + +import ( + "bytes" + "errors" + "fmt" + "log" + "strings" + + "github.com/ProtonMail/go-crypto/openpgp" +) + +// SignatureAuthentication is an archive Authenticator that validates that SHA256SUMS data +// was signed by the given signing key. +type SignatureAuthentication struct { + Authenticator + + // This can be overridden by tests to check arbitrary keys, rather than the HashiCorp public key + PublicKey string + signature []byte + signed []byte +} + +var _ Authenticator = SignatureAuthentication{} + +// ErrNotSignedByHashiCorp is the error returned when there is a mismatch between the SHA256SUMS +// signature data and the data itself. +var ErrNotSignedByHashiCorp = errors.New("failed to authenticate that the archive was signed by HashiCorp") + +// NewSignatureAuthentication creates a new Authenticator given some signature data +// (the SHA256SUMS.sig file), the signed data (the SHA256SUMS file), and a public key +func NewSignatureAuthentication(signature []byte, signed []byte) *SignatureAuthentication { + return &SignatureAuthentication{ + signature: signature, + signed: signed, + PublicKey: HashicorpPublicKey, + } +} + +func (a SignatureAuthentication) Authenticate() error { + // Verify the signature using the HashiCorp public key. If this succeeds, + // this is an official provider. + hashicorpKeyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(a.PublicKey)) + if err != nil { + return fmt.Errorf("error creating HashiCorp keyring: %s", err) + } + + _, err = openpgp.CheckDetachedSignature(hashicorpKeyring, bytes.NewReader(a.signed), bytes.NewReader(a.signature), nil) + if err != nil { + log.Printf("[DEBUG] GPG reported an error while verifying detached signature: %s", err) + return ErrNotSignedByHashiCorp + } + + return nil +} + +// HashicorpPublicKey is the HashiCorp public key, also available at +// https://www.hashicorp.com/security +const HashicorpPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ZF5q4h4I33PSGDdSvGXn9UMY5Isjpg== +=7pIB +-----END PGP PUBLIC KEY BLOCK-----` diff --git a/internal/releaseauth/testdata/sample.md b/internal/releaseauth/testdata/sample.md new file mode 100644 index 0000000000..5d90462ed4 --- /dev/null +++ b/internal/releaseauth/testdata/sample.md @@ -0,0 +1,11 @@ +# Package releaseauth Test Data Signing Key + +This directory contains a private key that is only used for signing the test data, along with the public key that the package uses to verfify the signing. Here are the steps to reproduce the test data, which would be necessary if the archive and checksum changes. + +1. Import the secret key + +`gpg --import sample.private.key` + +2. Sign the sample_release SHA256SUMS file using the sample key: + +`gpg -u 200BDA882C95B80A --output sample_release/sample_0.1.0_SHA256SUMS.sig --detach-sig sample_release/sample_0.1.0_SHA256SUMS` diff --git a/internal/releaseauth/testdata/sample.private.key b/internal/releaseauth/testdata/sample.private.key new file mode 100644 index 0000000000..b83c2b0295 --- /dev/null +++ b/internal/releaseauth/testdata/sample.private.key @@ -0,0 +1,106 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +fA== +=ILcU +-----END PGP PRIVATE KEY BLOCK----- diff --git a/internal/releaseauth/testdata/sample.public.key b/internal/releaseauth/testdata/sample.public.key new file mode 100644 index 0000000000..d5ae2f3bc0 --- /dev/null +++ b/internal/releaseauth/testdata/sample.public.key @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +hLUwcdtC5eh/H3w= +=FKJH +-----END PGP PUBLIC KEY BLOCK----- diff --git a/internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS b/internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS new file mode 100644 index 0000000000..08cb8872c8 --- /dev/null +++ b/internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS @@ -0,0 +1 @@ +22db2f0c70b50cff42afd4878fea9f6848a63f1b6532bd8b64b899f574acb35d sample_0.1.0_darwin_amd64.zip diff --git a/internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS.sig b/internal/releaseauth/testdata/sample_release/sample_0.1.0_SHA256SUMS.sig new file mode 100644 index 0000000000000000000000000000000000000000..ddc4e1738da674009bc5e79c915c3d9096ffcc62 GIT binary patch literal 566 zcmV-60?GY}0y6{v0SEvc79j*TO)@n^%=DKS_z~A23)+Y*mADE80%XJTiU0};5FiWM zh%A-33M(!T|5UB(omDF3iF6>M2b~gR9zUz@si~5NA=1|uK0a_KfCZ8}fn4TfH4;D{ zt1&lHZq`I`-JM~iCyNl`WNSGd(CWNF?o6gkD;=?Oy~U}eCN7gGE+dqw2>-UcaR(_P zDkO<54=Cl9;^rUr!4iE2hlCkc|K|`>*|%J=v;nAF`-u$n`j>#yUWZSwL2`%06C{0P zJp^2vhgHECH+`{gAbWmYwq<_^N~%WIbCQZPv8Mx}vw@|K1qv-V(wkq%U^3F%D4IWM z1D#RLg%_Xaj$n2h!!VL2+*0(bSLBU&M*;EocsmbfI0xGHZ(@RGd6)-^h@ORSW+>Y0 z1=20H>_M^*J!G#w@qz{(b%l)bF}$Be%uI7xQiHz&~zofD!TRcH=^F z)OGVkv@x{#!{5}@2>p?Lp#QAjX6%Q5dYx{I5vH;cTzaw}n#Nl~iZAvf#^zrYqV56N zeN~4^8-1g!voVlEqNU+0w6hK@hbxa!W>_x3hEL5`=<4%|G@s}_nj0U98O}_hD3oDv zTW0N_85pa^rbZ3zknJ@v?s0EbB74W-j_NUrkjzqgcQ7EJYKAFIO4;17OiKZ7Z`8E9 zF1@|HV{UG!4F27*Gsa32VawVC@Cd5x&QzG literal 0 HcmV?d00001 diff --git a/internal/releaseauth/testdata/sample_release/sample_0.1.0_darwin_amd64.zip b/internal/releaseauth/testdata/sample_release/sample_0.1.0_darwin_amd64.zip new file mode 100644 index 0000000000000000000000000000000000000000..58dfbabb213f74eeecda9ecf97002bc017d5ee16 GIT binary patch literal 377 zcmWIWW@Zs#-~ht8cE7_Ipdf^Wfq{!bfuSV7G&wO(ucV?RG=!IdeOK9`ls!NUqDw2d z85miy>WA>`<0UXi|x|+_Z%K-9pb5D_EaNTPR&~j@hu_MMG;HbI=+EcOPamVRZpX zPoP+aqKLMz;TlK2^L&3inTfu~S&GaWMl!fHynGR9*L7n}N;; rc{sqEkx7IZ5g5pFpum8EC5<2!@j(;d&B_MS!3cyAK$;sEW(*7fttM|U literal 0 HcmV?d00001