11package debutils
22
33import (
4+ "bufio"
45 "fmt"
56 "path/filepath"
67 "strings"
78 "sync"
89 "time"
910
11+ "bytes"
1012 "crypto/sha256"
1113 "io"
1214 "os"
1315
16+ "github.com/ProtonMail/go-crypto/openpgp"
17+ "github.com/ProtonMail/go-crypto/openpgp/packet"
1418 "github.com/schollz/progressbar/v3"
1519 "go.uber.org/zap"
1620)
@@ -23,9 +27,78 @@ type Result struct {
2327 Error error // any error (signature fail, I/O, etc)
2428}
2529
26- // VerifyAll takes a slice of RPM file paths, verifies each one in parallel,
30+ func VerifyPackagegz (relPath string , pkggzPath string ) (bool , error ) {
31+ logger := zap .L ().Sugar ()
32+ logger .Infof ("Verifying package %s" , pkggzPath )
33+
34+ // Get expected checksum from Release file
35+ checksum , err := findChecksumInRelease (relPath , "SHA256" , "main/binary-amd64/Packages.gz" )
36+ logger .Infof ("Checksum from Release file (%s): %s Err:%s" , relPath , checksum , err )
37+ if err != nil {
38+ return false , fmt .Errorf ("failed to get checksum from Release: %w" , err )
39+ }
40+
41+ // Compute actual checksum of Packages.gz
42+ actual , err := computeFileSHA256 (pkggzPath )
43+ if err != nil {
44+ return false , fmt .Errorf ("failed to compute checksum for %s: %w" , pkggzPath , err )
45+ }
46+
47+ // Compare
48+ if ! strings .EqualFold (actual , checksum ) {
49+ logger .Errorf ("Checksum mismatch: expected %s, got %s" , checksum , actual )
50+ return false , fmt .Errorf ("checksum mismatch: expected %s, got %s" , checksum , actual )
51+ }
52+
53+ logger .Infof ("Checksum verified successfully for %s" , pkggzPath )
54+ return true , nil
55+ }
56+
57+ func VerifyRelease (relPath string , relSignPath string , pKeyPath string ) (bool , error ) {
58+ logger := zap .L ().Sugar ()
59+
60+ // Read the public key
61+ keyringBytes , err := os .ReadFile (pKeyPath )
62+ if err != nil {
63+ return false , fmt .Errorf ("failed to read public key: %w" , err )
64+ }
65+
66+ // Read the Release file and its signature
67+ release , err := os .ReadFile (relPath )
68+ if err != nil {
69+ return false , fmt .Errorf ("failed to read Release file: %w" , err )
70+ }
71+ signature , err := os .ReadFile (relSignPath )
72+ if err != nil {
73+ return false , fmt .Errorf ("failed to read Release signature: %w" , err )
74+ }
75+
76+ // Import the public key
77+ keyring , err := openpgp .ReadArmoredKeyRing (bytes .NewReader (keyringBytes ))
78+ if err != nil {
79+ return false , fmt .Errorf ("failed to parse public key: %w" , err )
80+ }
81+
82+ // Verify the signature
83+ sigReader := bytes .NewReader (signature )
84+ releaseReader := bytes .NewReader (release )
85+ _ , err = openpgp .CheckArmoredDetachedSignature (
86+ openpgp .EntityList (keyring ), // cast to KeyRing
87+ releaseReader ,
88+ sigReader ,
89+ & packet.Config {}, // pass a config, or nil
90+ )
91+ if err != nil {
92+ return false , fmt .Errorf ("signature verification failed: %w" , err )
93+ }
94+
95+ logger .Infof ("Release file verified successfully" )
96+ return true , nil
97+ }
98+
99+ // VerifyAll takes a slice of DEB file paths, verifies each one in parallel,
27100// and returns a slice of results in the same order.
28- func VerifyAll (paths []string , pkgChecksum map [string ]string , pubkeyPath string , workers int ) []Result {
101+ func VerifyDEBs (paths []string , pkgChecksum map [string ]string , workers int ) []Result {
29102 logger := zap .L ().Sugar ()
30103
31104 logger .Infof ("Verifying %d packages with %d workers" , len (paths ), workers )
@@ -153,3 +226,51 @@ func computeFileSHA256(path string) (string, error) {
153226 }
154227 return fmt .Sprintf ("%x" , hasher .Sum (nil )), nil
155228}
229+
230+ // FindChecksumInRelease parses the Release file and returns the checksum for the given file and checksum type.
231+ // Example: findChecksumInRelease("Release", "SHA256", "main/binary-amd64/Packages.gz")
232+ func findChecksumInRelease (releasePath , checksumType , fileName string ) (string , error ) {
233+ f , err := os .Open (releasePath )
234+ if err != nil {
235+ return "" , fmt .Errorf ("failed to open release file: %v" , err )
236+ }
237+ defer f .Close ()
238+
239+ scanner := bufio .NewScanner (f )
240+ inChecksumSection := false
241+
242+ for scanner .Scan () {
243+ line := scanner .Text ()
244+ line = strings .TrimSpace (line )
245+
246+ // Check for the start of the checksum section
247+ if strings .HasSuffix (line , ":" ) && strings .EqualFold (strings .TrimSuffix (line , ":" ), checksumType ) {
248+ inChecksumSection = true
249+ continue
250+ }
251+
252+ // If we are in the checksum section, look for the file
253+ if inChecksumSection {
254+ // End of section if we hit a new section header or blank line
255+ if line == "" || strings .HasSuffix (line , ":" ) {
256+ break
257+ }
258+
259+ parts := strings .Fields (line )
260+ if len (parts ) < 3 {
261+ continue
262+ }
263+ if parts [2 ] == fileName {
264+ return parts [0 ], nil
265+ }
266+ }
267+ }
268+
269+ if err := scanner .Err (); err != nil {
270+ return "" , fmt .Errorf ("error reading release file: %v" , err )
271+ }
272+
273+ logger := zap .L ().Sugar ()
274+ logger .Warnf ("Could not find %s in section %s of %s" , fileName , checksumType , releasePath )
275+ return "" , fmt .Errorf ("checksum for %s (%s) not found" , fileName , checksumType )
276+ }
0 commit comments