Skip to content

Commit b281d6a

Browse files
Verify download checksums during packaging (#9226) (#9555)
* Verify download checksums * Add tests for checksum verification (cherry picked from commit d5d0175) Co-authored-by: Mikołaj Świątek <[email protected]>
1 parent 4713e2d commit b281d6a

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

dev-tools/mage/downloads/utils.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import (
99
"io"
1010
"os"
1111
"path/filepath"
12+
"regexp"
1213
"strings"
1314

15+
devtools "github.com/elastic/elastic-agent/dev-tools/mage"
16+
1417
"github.com/cenkalti/backoff/v4"
1518
"github.com/gofrs/uuid/v5"
1619
)
1720

21+
var checksumFileRegex = regexp.MustCompile(`^([0-9a-f]{128})\s+(\w.*)$`)
22+
1823
// downloadRequest struct contains download details ad path and URL
1924
type downloadRequest struct {
2025
URL string
@@ -88,3 +93,32 @@ func downloadFile(downloadRequest *downloadRequest) error {
8893

8994
return nil
9095
}
96+
97+
// verifyChecksum verifies a checksum file, with the content generated by the sha512sum program.
98+
// The format is the hex encoded checksum, followed by a space, and then the filename.
99+
// It is assumed that the files are in the same directory.
100+
func verifyChecksum(checksumFile string) error {
101+
checksumFileContent, err := os.ReadFile(checksumFile)
102+
if err != nil {
103+
return fmt.Errorf("failed to read checksum file %s: %w", checksumFile, err)
104+
}
105+
strippedChecksumFileContent := strings.TrimSpace(string(checksumFileContent))
106+
matches := checksumFileRegex.FindStringSubmatch(strippedChecksumFileContent)
107+
if len(matches) != 3 {
108+
return fmt.Errorf("checksum file %s has invalid format, expected `{checksum} {filename}`", checksumFile)
109+
}
110+
expectedChecksum := matches[1]
111+
fileName := matches[2]
112+
113+
filePath := filepath.Join(filepath.Dir(checksumFile), fileName)
114+
actualChecksum, err := devtools.GetSHA512Hash(filePath)
115+
if err != nil {
116+
return fmt.Errorf("failed to compute checksum of file %s: %w", fileName, err)
117+
}
118+
119+
if expectedChecksum != actualChecksum {
120+
return fmt.Errorf("checksum of file %s does not match expected checksum of %s", fileName, actualChecksum)
121+
}
122+
123+
return nil
124+
}

dev-tools/mage/downloads/utils_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
package downloads
66

77
import (
8+
"crypto/sha512"
9+
"encoding/hex"
810
"fmt"
911
"net/http"
1012
"net/http/httptest"
1113
"os"
1214
"path/filepath"
1315
"testing"
1416

17+
"github.com/stretchr/testify/require"
18+
1519
"github.com/stretchr/testify/assert"
1620
)
1721

@@ -30,3 +34,59 @@ func TestDownloadFile(t *testing.T) {
3034
assert.NotEmpty(t, dRequest.UnsanitizedFilePath)
3135
defer os.Remove(filepath.Dir(dRequest.UnsanitizedFilePath))
3236
}
37+
38+
func TestVerifyChecksum(t *testing.T) {
39+
tmpDir := t.TempDir()
40+
content := "hello world"
41+
hashBytes := sha512.Sum512([]byte(content))
42+
hashHex := hex.EncodeToString(hashBytes[:])
43+
44+
t.Run("valid checksum", func(t *testing.T) {
45+
// Write the file to be verified
46+
fileName := "testfile.txt"
47+
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, fileName), []byte(content), 0644))
48+
49+
// Write the checksum file
50+
checksumContent := fmt.Sprintf("%s %s", hashHex, fileName)
51+
checksumPath := filepath.Join(tmpDir, "checksum.txt")
52+
require.NoError(t, os.WriteFile(checksumPath, []byte(checksumContent), 0644))
53+
54+
// Run test
55+
err := verifyChecksum(checksumPath)
56+
assert.NoError(t, err)
57+
})
58+
59+
t.Run("missing checksum file", func(t *testing.T) {
60+
err := verifyChecksum(filepath.Join(tmpDir, "missing.txt"))
61+
assert.ErrorContains(t, err, "failed to read checksum file")
62+
})
63+
64+
t.Run("malformed checksum content", func(t *testing.T) {
65+
checksumPath := filepath.Join(tmpDir, "badchecksum.txt")
66+
require.NoError(t, os.WriteFile(checksumPath, []byte("invalid-format-line"), 0644))
67+
68+
err := verifyChecksum(checksumPath)
69+
assert.ErrorContains(t, err, "invalid format")
70+
})
71+
72+
t.Run("missing target file", func(t *testing.T) {
73+
checksumContent := fmt.Sprintf("%s %s", hashHex, "nonexistent.txt")
74+
checksumPath := filepath.Join(tmpDir, "checksum_missing_target.txt")
75+
require.NoError(t, os.WriteFile(checksumPath, []byte(checksumContent), 0644))
76+
77+
err := verifyChecksum(checksumPath)
78+
assert.ErrorContains(t, err, "failed to open file for sha512 summing")
79+
})
80+
81+
t.Run("checksum mismatch", func(t *testing.T) {
82+
invalidContent := content + "x"
83+
fileName := "file.txt"
84+
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, fileName), []byte(invalidContent), 0644))
85+
checksumContent := fmt.Sprintf("%s %s", hashHex, fileName)
86+
checksumPath := filepath.Join(tmpDir, "badhash.txt")
87+
require.NoError(t, os.WriteFile(checksumPath, []byte(checksumContent), 0644))
88+
89+
err := verifyChecksum(checksumPath)
90+
assert.ErrorContains(t, err, "does not match expected checksum")
91+
})
92+
}

dev-tools/mage/downloads/versions.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,14 @@ func FetchProjectBinaryForSnapshots(ctx context.Context, useCISnapshots bool, pr
478478
return "", err
479479
}
480480
if downloadSHAFile && downloadShaURL != "" {
481-
downloadLocation, err = handleDownload(downloadShaURL)
481+
checksumFileLocation, err := handleDownload(downloadShaURL)
482+
if err != nil {
483+
return "", err
484+
}
485+
err = verifyChecksum(checksumFileLocation)
486+
if err != nil {
487+
return "", err
488+
}
482489
}
483490
return downloadLocation, err
484491
}

0 commit comments

Comments
 (0)