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:
parent
0b632c872e
commit
e843097e52
|
@ -1,13 +1,19 @@
|
||||||
package getproviders
|
package getproviders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/mod/sumdb/dirhash"
|
"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
|
// PackageHash computes a hash of the contents of the package at the given
|
||||||
// location, using whichever hash algorithm is the current default.
|
// 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.
|
// a non-local location this function will always return an error.
|
||||||
func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
|
func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(want, "h1"):
|
case strings.HasPrefix(want, h1Prefix):
|
||||||
got, err := PackageHashV1(loc)
|
got, err := PackageHashV1(loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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.
|
// verification in order for a package to be considered valid.
|
||||||
func PreferredHash(given []string) string {
|
func PreferredHash(given []string) string {
|
||||||
for _, s := range given {
|
for _, s := range given {
|
||||||
if strings.HasPrefix(s, "h1:") {
|
if strings.HasPrefix(s, h1Prefix) {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
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
|
// PackageHashV1 computes a hash of the contents of the package at the given
|
||||||
// location using hash algorithm 1.
|
// location using hash algorithm 1.
|
||||||
//
|
//
|
||||||
|
|
|
@ -181,25 +181,23 @@ func (a archiveHashAuthentication) AuthenticatePackage(localLocation PackageLoca
|
||||||
return nil, fmt.Errorf("cannot check archive hash for non-archive location %s", localLocation)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to compute checksum for %s: %s", archiveLocation, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
wantHash := HashLegacyZipSHAFromSHA(a.WantSHA256Sum)
|
||||||
|
if gotHash != wantHash {
|
||||||
h := sha256.New()
|
return nil, fmt.Errorf("archive has incorrect checksum %s (expected %s)", gotHash, wantHash)
|
||||||
_, 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[:])
|
|
||||||
}
|
}
|
||||||
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
|
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]string {
|
||||||
|
return map[Platform][]string{
|
||||||
|
a.Platform: {HashLegacyZipSHAFromSHA(a.WantSHA256Sum)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type matchingChecksumAuthentication struct {
|
type matchingChecksumAuthentication struct {
|
||||||
Document []byte
|
Document []byte
|
||||||
Filename string
|
Filename string
|
||||||
|
|
Loading…
Reference in New Issue