internal/getproviders: Formalize the "ziphash" hashing scheme

This is the pre-existing hashing scheme that was initially built for
releases.hashicorp.com and then later reused for the provider registry
protocol, which takes a SHA256 hash of the official distribution .zip file
and formats it as lowercase hex.

This is a non-ideal hash scheme because it works only for
PackageLocalArchive locations, and so we can't verify package directories
on local disk against such hashes. However, the registry protocol is now
a compatibility constraint and so we're going to need to support this
hashing scheme for the foreseeable future.
This commit is contained in:
Martin Atkins 2020-09-08 16:36:18 -07:00
parent 0b632c872e
commit e843097e52
2 changed files with 62 additions and 15 deletions

View File

@ -1,13 +1,19 @@
package getproviders
import (
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/mod/sumdb/dirhash"
)
const h1Prefix = "h1:"
const zipHashPrefix = "zh:"
// PackageHash computes a hash of the contents of the package at the given
// location, using whichever hash algorithm is the current default.
//
@ -40,7 +46,7 @@ func PackageHash(loc PackageLocation) (string, error) {
// a non-local location this function will always return an error.
func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
switch {
case strings.HasPrefix(want, "h1"):
case strings.HasPrefix(want, h1Prefix):
got, err := PackageHashV1(loc)
if err != nil {
return false, err
@ -61,13 +67,56 @@ func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
// verification in order for a package to be considered valid.
func PreferredHash(given []string) string {
for _, s := range given {
if strings.HasPrefix(s, "h1:") {
if strings.HasPrefix(s, h1Prefix) {
return s
}
}
return ""
}
// PackageHashLegacyZipSHA implements the old provider package hashing scheme
// of taking a SHA256 hash of the containing .zip archive itself, rather than
// of the contents of the archive.
//
// The result is a hash string with the "zh:" prefix, which is intended to
// represent "zip hash". After the prefix is a lowercase-hex encoded SHA256
// checksum, intended to exactly match the formatting used in the registry
// API (apart from the prefix) so that checksums can be more conveniently
// compared by humans.
//
// Because this hashing scheme uses the official provider .zip file as its
// input, it accepts only PackageLocalArchive locations.
func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
archivePath, err := filepath.EvalSymlinks(string(loc))
if err != nil {
return "", err
}
f, err := os.Open(archivePath)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
if err != nil {
return "", err
}
gotHash := h.Sum(nil)
return fmt.Sprintf("%s%x", zipHashPrefix, gotHash), nil
}
// HashLegacyZipSHAFromSHA is a convenience method to produce the schemed-string
// hash format from an already-calculated hash of a provider .zip archive.
//
// This just adds the "zh:" prefix and encodes the string in hex, so that the
// result is in the same format as PackageHashLegacyZipSHA.
func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) string {
return fmt.Sprintf("%s%x", zipHashPrefix, sum[:])
}
// PackageHashV1 computes a hash of the contents of the package at the given
// location using hash algorithm 1.
//

View File

@ -181,25 +181,23 @@ func (a archiveHashAuthentication) AuthenticatePackage(localLocation PackageLoca
return nil, fmt.Errorf("cannot check archive hash for non-archive location %s", localLocation)
}
f, err := os.Open(string(archiveLocation))
gotHash, err := PackageHashLegacyZipSHA(archiveLocation)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to compute checksum for %s: %s", archiveLocation, err)
}
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
if err != nil {
return nil, err
}
gotHash := h.Sum(nil)
if !bytes.Equal(gotHash, a.WantSHA256Sum[:]) {
return nil, fmt.Errorf("archive has incorrect SHA-256 checksum %x (expected %x)", gotHash, a.WantSHA256Sum[:])
wantHash := HashLegacyZipSHAFromSHA(a.WantSHA256Sum)
if gotHash != wantHash {
return nil, fmt.Errorf("archive has incorrect checksum %s (expected %s)", gotHash, wantHash)
}
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
}
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]string {
return map[Platform][]string{
a.Platform: {HashLegacyZipSHAFromSHA(a.WantSHA256Sum)},
}
}
type matchingChecksumAuthentication struct {
Document []byte
Filename string