1
15
16 package provenance
17
18 import (
19 "crypto"
20 "fmt"
21 "io"
22 "os"
23 "path/filepath"
24 "strings"
25 "testing"
26
27 pgperrors "golang.org/x/crypto/openpgp/errors"
28 )
29
30 const (
31
32
33
34
35 testKeyfile = "testdata/helm-test-key.secret"
36
37
38 testPasswordKeyfile = "testdata/helm-password-key.secret"
39
40
41
42 testPubfile = "testdata/helm-test-key.pub"
43
44
45 testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>`
46
47 testPasswordKeyName = `password key (fake) <fake@helm.sh>`
48
49 testChartfile = "testdata/hashtest-1.2.3.tgz"
50
51
52
53
54 testSigBlock = "testdata/msgblock.yaml.asc"
55
56
57 testTamperedSigBlock = "testdata/msgblock.yaml.tampered"
58
59
60
61
62
63
64 testSumfile = "testdata/hashtest.sha256"
65 )
66
67
68 const testMessageBlock = `apiVersion: v1
69 description: Test chart versioning
70 name: hashtest
71 version: 1.2.3
72
73 ...
74 files:
75 hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888
76 `
77
78 func TestMessageBlock(t *testing.T) {
79 out, err := messageBlock(testChartfile)
80 if err != nil {
81 t.Fatal(err)
82 }
83 got := out.String()
84
85 if got != testMessageBlock {
86 t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got)
87 }
88 }
89
90 func TestParseMessageBlock(t *testing.T) {
91 md, sc, err := parseMessageBlock([]byte(testMessageBlock))
92 if err != nil {
93 t.Fatal(err)
94 }
95
96 if md.Name != "hashtest" {
97 t.Errorf("Expected name %q, got %q", "hashtest", md.Name)
98 }
99
100 if lsc := len(sc.Files); lsc != 1 {
101 t.Errorf("Expected 1 file, got %d", lsc)
102 }
103
104 if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok {
105 t.Errorf("hashtest file not found in Files")
106 } else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" {
107 t.Errorf("Unexpected hash: %q", hash)
108 }
109 }
110
111 func TestLoadKey(t *testing.T) {
112 k, err := loadKey(testKeyfile)
113 if err != nil {
114 t.Fatal(err)
115 }
116
117 if _, ok := k.Identities[testKeyName]; !ok {
118 t.Errorf("Expected to load a key for user %q", testKeyName)
119 }
120 }
121
122 func TestLoadKeyRing(t *testing.T) {
123 k, err := loadKeyRing(testPubfile)
124 if err != nil {
125 t.Fatal(err)
126 }
127
128 if len(k) > 1 {
129 t.Errorf("Expected 1, got %d", len(k))
130 }
131
132 for _, e := range k {
133 if ii, ok := e.Identities[testKeyName]; !ok {
134 t.Errorf("Expected %s in %v", testKeyName, ii)
135 }
136 }
137 }
138
139 func TestDigest(t *testing.T) {
140 f, err := os.Open(testChartfile)
141 if err != nil {
142 t.Fatal(err)
143 }
144 defer f.Close()
145
146 hash, err := Digest(f)
147 if err != nil {
148 t.Fatal(err)
149 }
150
151 sig, err := readSumFile(testSumfile)
152 if err != nil {
153 t.Fatal(err)
154 }
155
156 if !strings.Contains(sig, hash) {
157 t.Errorf("Expected %s to be in %s", hash, sig)
158 }
159 }
160
161 func TestNewFromFiles(t *testing.T) {
162 s, err := NewFromFiles(testKeyfile, testPubfile)
163 if err != nil {
164 t.Fatal(err)
165 }
166
167 if _, ok := s.Entity.Identities[testKeyName]; !ok {
168 t.Errorf("Expected to load a key for user %q", testKeyName)
169 }
170 }
171
172 func TestDigestFile(t *testing.T) {
173 hash, err := DigestFile(testChartfile)
174 if err != nil {
175 t.Fatal(err)
176 }
177
178 sig, err := readSumFile(testSumfile)
179 if err != nil {
180 t.Fatal(err)
181 }
182
183 if !strings.Contains(sig, hash) {
184 t.Errorf("Expected %s to be in %s", hash, sig)
185 }
186 }
187
188 func TestDecryptKey(t *testing.T) {
189 k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
190 if err != nil {
191 t.Fatal(err)
192 }
193
194 if !k.Entity.PrivateKey.Encrypted {
195 t.Fatal("Key is not encrypted")
196 }
197
198
199 if err := k.DecryptKey(func(_ string) ([]byte, error) {
200 return []byte("secret"), nil
201 }); err != nil {
202 t.Fatal(err)
203 }
204
205
206 k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
207 if err != nil {
208 t.Fatal(err)
209 }
210
211 if err := k.DecryptKey(func(_ string) ([]byte, error) {
212 return []byte("secrets_and_lies"), nil
213 }); err == nil {
214 t.Fatal("Expected an error when giving a bogus passphrase")
215 }
216 }
217
218 func TestClearSign(t *testing.T) {
219 signer, err := NewFromFiles(testKeyfile, testPubfile)
220 if err != nil {
221 t.Fatal(err)
222 }
223
224 sig, err := signer.ClearSign(testChartfile)
225 if err != nil {
226 t.Fatal(err)
227 }
228 t.Logf("Sig:\n%s", sig)
229
230 if !strings.Contains(sig, testMessageBlock) {
231 t.Errorf("expected message block to be in sig: %s", sig)
232 }
233 }
234
235
236 type failSigner struct{}
237
238 func (s failSigner) Public() crypto.PublicKey {
239 return nil
240 }
241
242 func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
243 return nil, fmt.Errorf("always fails")
244 }
245
246 func TestClearSignError(t *testing.T) {
247 signer, err := NewFromFiles(testKeyfile, testPubfile)
248 if err != nil {
249 t.Fatal(err)
250 }
251
252
253 signer.Entity.PrivateKey.PrivateKey = failSigner{}
254
255 sig, err := signer.ClearSign(testChartfile)
256 if err == nil {
257 t.Fatal("didn't get an error from ClearSign but expected one")
258 }
259
260 if sig != "" {
261 t.Fatalf("expected an empty signature after failed ClearSign but got %q", sig)
262 }
263 }
264
265 func TestDecodeSignature(t *testing.T) {
266
267
268
269 signer, err := NewFromFiles(testKeyfile, testPubfile)
270 if err != nil {
271 t.Fatal(err)
272 }
273
274 sig, err := signer.ClearSign(testChartfile)
275 if err != nil {
276 t.Fatal(err)
277 }
278
279 f, err := os.CreateTemp("", "helm-test-sig-")
280 if err != nil {
281 t.Fatal(err)
282 }
283
284 tname := f.Name()
285 defer func() {
286 os.Remove(tname)
287 }()
288 f.WriteString(sig)
289 f.Close()
290
291 sig2, err := signer.decodeSignature(tname)
292 if err != nil {
293 t.Fatal(err)
294 }
295
296 by, err := signer.verifySignature(sig2)
297 if err != nil {
298 t.Fatal(err)
299 }
300
301 if _, ok := by.Identities[testKeyName]; !ok {
302 t.Errorf("Expected identity %q", testKeyName)
303 }
304 }
305
306 func TestVerify(t *testing.T) {
307 signer, err := NewFromFiles(testKeyfile, testPubfile)
308 if err != nil {
309 t.Fatal(err)
310 }
311
312 if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil {
313 t.Errorf("Failed to pass verify. Err: %s", err)
314 } else if len(ver.FileHash) == 0 {
315 t.Error("Verification is missing hash.")
316 } else if ver.SignedBy == nil {
317 t.Error("No SignedBy field")
318 } else if ver.FileName != filepath.Base(testChartfile) {
319 t.Errorf("FileName is unexpectedly %q", ver.FileName)
320 }
321
322 if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil {
323 t.Errorf("Expected %s to fail.", testTamperedSigBlock)
324 }
325
326 switch err.(type) {
327 case pgperrors.SignatureError:
328 t.Logf("Tampered sig block error: %s (%T)", err, err)
329 default:
330 t.Errorf("Expected invalid signature error, got %q (%T)", err, err)
331 }
332 }
333
334
335 func readSumFile(sumfile string) (string, error) {
336 data, err := os.ReadFile(sumfile)
337 if err != nil {
338 return "", err
339 }
340
341 sig := string(data)
342 parts := strings.SplitN(sig, " ", 2)
343 return parts[0], nil
344 }
345
View as plain text