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.
This commit is contained in:
Brandon Croft 2023-07-28 13:09:45 -06:00
parent 919e62089b
commit 6daaec5ce1
No known key found for this signature in database
GPG key ID: B01E32423322EB9D
13 changed files with 616 additions and 0 deletions

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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-----`

11
internal/releaseauth/testdata/sample.md vendored Normal file
View file

@ -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`

View file

@ -0,0 +1,106 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
fA==
=ILcU
-----END PGP PRIVATE KEY BLOCK-----

View file

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
hLUwcdtC5eh/H3w=
=FKJH
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1 @@
22db2f0c70b50cff42afd4878fea9f6848a63f1b6532bd8b64b899f574acb35d sample_0.1.0_darwin_amd64.zip