From 602680511a6ea43b0611d7c45266e4e1af3cc0e1 Mon Sep 17 00:00:00 2001 From: Georg Welzel Date: Tue, 12 Sep 2023 13:18:57 -0500 Subject: [PATCH 1/2] verify checksums against sha256sum files when available Many projects offer a single asset in form of a sha256sum file, e.g. [k9s](https://github.com/derailed/k9s/releases). This PR checks the asset list for such files and verifies a checksum against it. --- eget.go | 57 +++++++++++++++++++++++-------------------- verify.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/eget.go b/eget.go index bd46219..81b997e 100644 --- a/eget.go +++ b/eget.go @@ -60,17 +60,6 @@ func IsDirectory(path string) bool { return fileInfo.IsDir() } -// searches for an asset thaat has the same name as the requested one but -// ending with .sha256 or .sha256sum -func checksumAsset(asset string, assets []string) string { - for _, a := range assets { - if a == asset+".sha256sum" || a == asset+".sha256" { - return a - } - } - return "" -} - // Determine the appropriate Finder to use. If opts.URL is provided, we use // a DirectAssetFinder. Otherwise we use a GithubAssetFinder. When a Github // repo is provided, we assume the repo name is the 'tool' name (for direct @@ -135,19 +124,39 @@ func getFinder(project string, opts *Flags) (finder Finder, tool string) { return finder, tool } -func getVerifier(sumAsset string, opts *Flags) (verifier Verifier, err error) { +func getVerifier(asset string, assets []string, opts *Flags) (verifier Verifier, err error) { if opts.Verify != "" { verifier, err = NewSha256Verifier(opts.Verify) - } else if sumAsset != "" { - verifier = &Sha256AssetVerifier{ - AssetURL: sumAsset, + if err != nil { + return nil, fmt.Errorf("create Sha256Verifier: %w", err) } - } else if opts.Hash { - verifier = &Sha256Printer{} - } else { - verifier = &NoVerifier{} + return verifier, nil + } + + for _, a := range assets { + if a == asset+".sha256sum" || a == asset+".sha256" { + return &Sha256AssetVerifier{ + AssetURL: a, + }, nil + } + if strings.Contains(a, "checksum") { + binaryUrl, err := url.Parse(asset) + if err != nil { + return nil, fmt.Errorf("extract binary name from asset url: %s: %w", asset, err) + } + binaryName := path.Base(binaryUrl.Path) + return &Sha256SumFileAssetVerifier{ + Sha256SumAssetURL: a, + BinaryName: binaryName, + }, nil + } + } + + if opts.Hash { + return &Sha256Printer{}, nil } - return verifier, err + + return &NoVerifier{}, nil } // Determine the appropriate detector. If the --system is 'all', we use an @@ -479,19 +488,15 @@ func main() { body := buf.Bytes() - sumAsset := checksumAsset(url, assets) - verifier, err := getVerifier(sumAsset, &opts) + verifier, err := getVerifier(url, assets, &opts) if err != nil { fatal(err) } err = verifier.Verify(body) if err != nil { fatal(err) - } else if opts.Verify == "" && sumAsset != "" { - fmt.Fprintf(output, "Checksum verified with %s\n", path.Base(sumAsset)) - } else if opts.Verify != "" { - fmt.Fprintf(output, "Checksum verified\n") } + fmt.Fprintf(output, "%s\n", verifier) extractor, err := getExtractor(url, tool, &opts) if err != nil { diff --git a/verify.go b/verify.go index 923b03d..9e0d1dd 100644 --- a/verify.go +++ b/verify.go @@ -1,15 +1,18 @@ package main import ( + "bufio" "bytes" "crypto/sha256" "encoding/hex" "fmt" "io" + "regexp" ) type Verifier interface { Verify(b []byte) error + String() string } type NoVerifier struct{} @@ -18,6 +21,10 @@ func (n *NoVerifier) Verify(b []byte) error { return nil } +func (n *NoVerifier) String() string { + return fmt.Sprintf("checksum verification skipped.\n") +} + type Sha256Error struct { Expected []byte Got []byte @@ -52,6 +59,10 @@ func (s256 *Sha256Verifier) Verify(b []byte) error { } } +func (n *Sha256Verifier) String() string { + return fmt.Sprintf("checksum verified: %s\n", n.Expected) +} + type Sha256Printer struct{} func (s256 *Sha256Printer) Verify(b []byte) error { @@ -60,6 +71,10 @@ func (s256 *Sha256Printer) Verify(b []byte) error { return nil } +func (n *Sha256Printer) String() string { + return "" +} + type Sha256AssetVerifier struct { AssetURL string } @@ -88,3 +103,61 @@ func (s256 *Sha256AssetVerifier) Verify(b []byte) error { Got: sum[:], } } + +func (n *Sha256AssetVerifier) String() string { + return fmt.Sprintf("checksum verified with %s", n.AssetURL) +} + +type Sha256SumFileAssetVerifier struct { + Sha256SumAssetURL string + BinaryName string +} + +func (s256 *Sha256SumFileAssetVerifier) Verify(b []byte) error { + resp, err := Get(s256.Sha256SumAssetURL) + if err != nil { + return err + } + defer resp.Body.Close() + + var expected []byte + expectedFound := false + scanner := bufio.NewScanner(resp.Body) // f is the *os.File + sha256sumLinePattern := regexp.MustCompile(fmt.Sprintf("([a-f0-9]+)\\s+(%s)", s256.BinaryName)) + for scanner.Scan() { + matches := sha256sumLinePattern.FindSubmatch(scanner.Bytes()) + if matches == nil { + continue + } + decoded := make([]byte, sha256.Size) + n, err := hex.Decode(decoded, matches[1]) + if err != nil { + return fmt.Errorf("decode expected sha256sum %s: %w", matches[1], err) + } + if n < sha256.Size { + return fmt.Errorf("sha256sum (%s) too small: %d bytes decoded", matches[1], n) + } + expected = decoded[:n] + expectedFound = true + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("read sha256sum %s: %w", s256.Sha256SumAssetURL, err) + } + if !expectedFound { + return &Sha256Error{ + Expected: expected[:], + } + } + got := sha256.Sum256(b) + if !bytes.Equal(got[:], expected[:]) { + return &Sha256Error{ + Expected: expected[:], + Got: got[:], + } + } + return nil +} + +func (n *Sha256SumFileAssetVerifier) String() string { + return fmt.Sprintf("checksum verified with %s", n.Sha256SumAssetURL) +} From ed06b85fdfb99c449cc36d642039a5f63baa8050 Mon Sep 17 00:00:00 2001 From: Georg Welzel Date: Tue, 27 Feb 2024 15:01:35 -0600 Subject: [PATCH 2/2] use exact match in sha256fileverifier --- eget.go | 2 ++ verify.go | 32 +++++++++++--------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/eget.go b/eget.go index 81b997e..1602e33 100644 --- a/eget.go +++ b/eget.go @@ -135,6 +135,7 @@ func getVerifier(asset string, assets []string, opts *Flags) (verifier Verifier, for _, a := range assets { if a == asset+".sha256sum" || a == asset+".sha256" { + fmt.Printf("verify against %s\n", a) return &Sha256AssetVerifier{ AssetURL: a, }, nil @@ -145,6 +146,7 @@ func getVerifier(asset string, assets []string, opts *Flags) (verifier Verifier, return nil, fmt.Errorf("extract binary name from asset url: %s: %w", asset, err) } binaryName := path.Base(binaryUrl.Path) + fmt.Printf("verify against %s\n", a) return &Sha256SumFileAssetVerifier{ Sha256SumAssetURL: a, BinaryName: binaryName, diff --git a/verify.go b/verify.go index 9e0d1dd..6cd1021 100644 --- a/verify.go +++ b/verify.go @@ -91,6 +91,9 @@ func (s256 *Sha256AssetVerifier) Verify(b []byte) error { } expected := make([]byte, sha256.Size) n, err := hex.Decode(expected, data) + if err != nil { + return fmt.Errorf("decode data: %w", err) + } if n < sha256.Size { return fmt.Errorf("sha256sum (%s) too small: %d bytes decoded", string(data), n) } @@ -114,45 +117,32 @@ type Sha256SumFileAssetVerifier struct { } func (s256 *Sha256SumFileAssetVerifier) Verify(b []byte) error { + got := sha256.Sum256(b) + resp, err := Get(s256.Sha256SumAssetURL) if err != nil { return err } defer resp.Body.Close() - var expected []byte expectedFound := false - scanner := bufio.NewScanner(resp.Body) // f is the *os.File - sha256sumLinePattern := regexp.MustCompile(fmt.Sprintf("([a-f0-9]+)\\s+(%s)", s256.BinaryName)) + scanner := bufio.NewScanner(resp.Body) + sha256sumLinePattern := regexp.MustCompile(fmt.Sprintf("(%x)\\s+(%s)", got, s256.BinaryName)) for scanner.Scan() { - matches := sha256sumLinePattern.FindSubmatch(scanner.Bytes()) + line := scanner.Bytes() + matches := sha256sumLinePattern.FindSubmatch(line) if matches == nil { continue } - decoded := make([]byte, sha256.Size) - n, err := hex.Decode(decoded, matches[1]) - if err != nil { - return fmt.Errorf("decode expected sha256sum %s: %w", matches[1], err) - } - if n < sha256.Size { - return fmt.Errorf("sha256sum (%s) too small: %d bytes decoded", matches[1], n) - } - expected = decoded[:n] expectedFound = true + break } if err := scanner.Err(); err != nil { return fmt.Errorf("read sha256sum %s: %w", s256.Sha256SumAssetURL, err) } if !expectedFound { return &Sha256Error{ - Expected: expected[:], - } - } - got := sha256.Sum256(b) - if !bytes.Equal(got[:], expected[:]) { - return &Sha256Error{ - Expected: expected[:], - Got: got[:], + Got: got[:], } } return nil