Skip to content

Commit 0ea5df3

Browse files
authored
Merge pull request #11 from open-edge-platform/pg-dev
Implementing trust chain for Debian Packages.gz
2 parents 9d32612 + c6889b0 commit 0ea5df3

5 files changed

Lines changed: 209 additions & 42 deletions

File tree

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/intel-innersource/os.linux.tiberos.os-curation-tool
33
go 1.22.12
44

55
require (
6+
github.com/ProtonMail/go-crypto v1.2.0
67
github.com/cavaliergopher/rpm v1.3.0
78
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
89
github.com/schollz/progressbar/v3 v3.12.0
@@ -13,13 +14,14 @@ require (
1314
)
1415

1516
require (
17+
github.com/cloudflare/circl v1.6.0 // indirect
1618
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1719
github.com/mattn/go-runewidth v0.0.14 // indirect
1820
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
1921
github.com/rivo/uniseg v0.4.2 // indirect
2022
github.com/spf13/pflag v1.0.6 // indirect
2123
go.uber.org/multierr v1.11.0 // indirect
22-
golang.org/x/crypto v0.6.0 // indirect
23-
golang.org/x/sys v0.5.0 // indirect
24-
golang.org/x/term v0.5.0 // indirect
24+
golang.org/x/crypto v0.33.0 // indirect
25+
golang.org/x/sys v0.30.0 // indirect
26+
golang.org/x/term v0.29.0 // indirect
2527
)

go.sum

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
2+
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
13
github.com/cavaliergopher/rpm v1.3.0 h1:UHX46sasX8MesUXXQ+UbkFLUX4eUWTlEcX8jcnRBIgI=
24
github.com/cavaliergopher/rpm v1.3.0/go.mod h1:vEumo1vvtrHM1Ov86f6+k8j7zNKOxQfHDCAIcR/36ZI=
5+
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
6+
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
37
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
48
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
59
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -36,16 +40,16 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
3640
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
3741
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
3842
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
39-
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
40-
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
43+
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
44+
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
4145
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4246
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4347
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
44-
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
45-
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
48+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
49+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
4650
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
47-
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
48-
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
51+
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
52+
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
4953
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
5054
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5155
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

internal/debutils/resolver.go

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,63 @@ func ResolvePackageInfos(requested []provider.PackageInfo, all []provider.Packag
9696
}
9797

9898
// ParsePrimary parses the Packages.gz file from gzHref.
99-
func ParsePrimary(baseURL, gzHref string) ([]provider.PackageInfo, error) {
99+
func ParsePrimary(baseURL, pkggz string, releaseFile string, releaseSign string, pbGPGKey string) ([]provider.PackageInfo, error) {
100100
logger := zap.L().Sugar()
101101

102-
// Download the debian repo .gz file with all components meta data
103-
pkgMetaDir := "./builds"
104-
PkgMetaFile := "./builds/elxr12/Packages.gz"
105-
if dir := os.DirFS(PkgMetaFile); dir != nil {
106-
pkgMetaDir = filepath.Dir(PkgMetaFile)
102+
// Ensure pkgMetaDir exists, create if not
103+
pkgMetaDir := "./builds/elxr12"
104+
if err := os.MkdirAll(pkgMetaDir, 0755); err != nil {
105+
return nil, fmt.Errorf("failed to create pkgMetaDir: %v", err)
107106
}
108-
err := pkgfetcher.FetchPackages([]string{gzHref}, pkgMetaDir, 1)
107+
108+
//verify release file
109+
localPkggzFile := filepath.Join(pkgMetaDir, filepath.Base(pkggz))
110+
localReleaseFile := filepath.Join(pkgMetaDir, filepath.Base(releaseFile))
111+
localReleaseSign := filepath.Join(pkgMetaDir, filepath.Base(releaseSign))
112+
localPBGPGKey := filepath.Join(pkgMetaDir, filepath.Base(pbGPGKey))
113+
114+
// Remove any existing local files to ensure fresh downloads
115+
localFiles := []string{localPkggzFile, localReleaseFile, localReleaseSign, localPBGPGKey}
116+
for _, f := range localFiles {
117+
if _, err := os.Stat(f); err == nil {
118+
if remErr := os.Remove(f); remErr != nil {
119+
return nil, fmt.Errorf("failed to remove old file %s: %v", f, remErr)
120+
}
121+
}
122+
}
123+
124+
// Download the debian repo files
125+
err := pkgfetcher.FetchPackages([]string{pkggz, releaseFile, releaseSign, pbGPGKey}, pkgMetaDir, 1)
126+
if err != nil {
127+
return nil, fmt.Errorf("failed to fetch critical repo config packages: %v", err)
128+
}
129+
// Verify the release file
130+
relVryResult, err := VerifyRelease(localReleaseFile, localReleaseSign, localPBGPGKey)
109131
if err != nil {
110-
return nil, fmt.Errorf("failed to fetch packages: %v", err)
132+
return nil, fmt.Errorf("failed to verify release file: %v", err)
111133
}
134+
if !relVryResult {
135+
return nil, fmt.Errorf("release file verification failed")
136+
}
137+
138+
// verify the sham256 checksum of the Packages.gz file
139+
pkggzVryResult, err := VerifyPackagegz(localReleaseFile, localPkggzFile)
140+
if err != nil {
141+
return nil, fmt.Errorf("failed to verify pkg file: %v", err)
142+
}
143+
if !pkggzVryResult {
144+
return nil, fmt.Errorf("package file verification failed")
145+
}
146+
147+
// Getting sha256sum of the Packages.gz file from the release file
148+
// and verifying it with the local Packages.gz file
149+
// localPkgMetaFile := filepath.Join(pkgMetaDir, filepath.Base(pkggz))
150+
localPkgMetaFile := filepath.Join(pkgMetaDir, "Packages.gz")
151+
logger.Infof("localPkgMetaFile: %s", localPkgMetaFile)
112152

153+
//Decompress the Packages.gz file
154+
// The decompressed file will be named Packages (without .gz)
155+
PkgMetaFile := pkgMetaDir + "/Packages.gz"
113156
pkgMetaFileNoExt := filepath.Join(filepath.Dir(PkgMetaFile), strings.TrimSuffix(filepath.Base(PkgMetaFile), filepath.Ext(PkgMetaFile)))
114157

115158
files, err := Decompress(PkgMetaFile, pkgMetaFileNoExt)
@@ -118,14 +161,13 @@ func ParsePrimary(baseURL, gzHref string) ([]provider.PackageInfo, error) {
118161
}
119162
logger.Infof("decompressed files: %v", files)
120163

121-
// Parse the decompressed file
164+
//Parse the decompressed file
122165
f, err := os.Open(files[0])
123166
if err != nil {
124167
return nil, fmt.Errorf("failed to open decompressed file: %v", err)
125168
}
126169
defer f.Close()
127170

128-
// packages file parser
129171
var pkgs []provider.PackageInfo
130172
pkg := provider.PackageInfo{}
131173
reader := bufio.NewReader(f)

internal/debutils/verify.go

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package debutils
22

33
import (
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

Comments
 (0)