1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package signature
17
18 import (
19 "bytes"
20 "encoding/base64"
21 "errors"
22 "fmt"
23 "io"
24 "strings"
25 "testing"
26
27 "github.com/google/go-cmp/cmp"
28 v1 "github.com/google/go-containerregistry/pkg/v1"
29 "github.com/google/go-containerregistry/pkg/v1/random"
30 "github.com/google/go-containerregistry/pkg/v1/types"
31 "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
32 )
33
34 func mustDecode(s string) []byte {
35 b, err := base64.StdEncoding.DecodeString(s)
36 if err != nil {
37 panic(err.Error())
38 }
39 return b
40 }
41
42 func TestSignature(t *testing.T) {
43 layer, err := random.Layer(300 , types.DockerLayer)
44 if err != nil {
45 t.Fatalf("random.Layer() = %v", err)
46 }
47 digest, err := layer.Digest()
48 if err != nil {
49 t.Fatalf("Digest() = %v", err)
50 }
51
52 tests := []struct {
53 name string
54 l *sigLayer
55 env map[string]string
56 wantPayloadErr error
57 wantSig string
58 wantSigErr error
59 wantCert bool
60 wantCertErr error
61 wantChain int
62 wantChainErr error
63 wantBundle *bundle.RekorBundle
64 wantBundleErr error
65 }{{
66 name: "just payload and signature",
67 l: &sigLayer{
68 Layer: layer,
69 desc: v1.Descriptor{
70 Digest: digest,
71 Annotations: map[string]string{
72 sigkey: "blah",
73 },
74 },
75 },
76 wantSig: "blah",
77 }, {
78 name: "with empty other keys",
79 l: &sigLayer{
80 Layer: layer,
81 desc: v1.Descriptor{
82 Digest: digest,
83 Annotations: map[string]string{
84 sigkey: "blah",
85 certkey: "",
86 chainkey: "",
87 BundleKey: "",
88 },
89 },
90 },
91 wantSig: "blah",
92 }, {
93 name: "missing signature",
94 l: &sigLayer{
95 Layer: layer,
96 desc: v1.Descriptor{
97 Digest: digest,
98 },
99 },
100 wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey),
101 }, {
102 name: "min plus bad bundle",
103 l: &sigLayer{
104 Layer: layer,
105 desc: v1.Descriptor{
106 Digest: digest,
107 Annotations: map[string]string{
108 sigkey: "blah",
109 BundleKey: `}`,
110 },
111 },
112 },
113 wantSig: "blah",
114 wantBundleErr: errors.New(`unmarshaling bundle: invalid character '}' looking for beginning of value`),
115 }, {
116 name: "min plus bad cert",
117 l: &sigLayer{
118 Layer: layer,
119 desc: v1.Descriptor{
120 Digest: digest,
121 Annotations: map[string]string{
122 sigkey: "blah",
123 certkey: `GARBAGE`,
124 },
125 },
126 },
127 wantSig: "blah",
128 wantCertErr: errors.New(`error during PEM decoding`),
129 }, {
130 name: "min plus bad chain",
131 l: &sigLayer{
132 Layer: layer,
133 desc: v1.Descriptor{
134 Digest: digest,
135 Annotations: map[string]string{
136 sigkey: "blah",
137 chainkey: `GARBAGE`,
138 },
139 },
140 },
141 wantSig: "blah",
142 wantChainErr: errors.New(`error during PEM decoding`),
143 }, {
144 name: "min plus bundle",
145 l: &sigLayer{
146 Layer: layer,
147 desc: v1.Descriptor{
148 Digest: digest,
149 Annotations: map[string]string{
150 sigkey: "blah",
151
152
153 BundleKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`,
154 },
155 },
156 },
157 wantSig: "blah",
158 wantBundle: &bundle.RekorBundle{
159 SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
160 Payload: bundle.RekorPayload{
161 Body: "REMOVED",
162 IntegratedTime: 1631646761,
163 LogIndex: 693591,
164 LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
165 },
166 },
167 }, {
168 name: "min plus good cert",
169 l: &sigLayer{
170 Layer: layer,
171 desc: v1.Descriptor{
172 Digest: digest,
173 Annotations: map[string]string{
174 sigkey: "blah",
175
176 certkey: `
177 -----BEGIN CERTIFICATE-----
178 MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq
179 MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
180 MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49
181 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt
182 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw
183 EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G
184 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG
185 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl
186 Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu
187 c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j
188 cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj
189 ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM
190 wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx
191 uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY=
192 -----END CERTIFICATE-----
193 `,
194 },
195 },
196 },
197 wantSig: "blah",
198 wantCert: true,
199 }, {
200 name: "min plus bad chain",
201 l: &sigLayer{
202 Layer: layer,
203 desc: v1.Descriptor{
204 Digest: digest,
205 Annotations: map[string]string{
206 sigkey: "blah",
207
208 chainkey: `
209 -----BEGIN CERTIFICATE-----
210 MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
211 MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
212 MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
213 ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
214 A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
215 taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
216 MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
217 FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
218 Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
219 Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
220 Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
221 -----END CERTIFICATE-----
222 `,
223 },
224 },
225 },
226 wantSig: "blah",
227 wantChain: 1,
228 }, {
229 name: "payload size exceeds default limit",
230 l: &sigLayer{
231 Layer: &mockLayer{size: 134217728 + 42},
232 },
233 wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"),
234 }, {
235 name: "payload size exceeds overridden limit",
236 l: &sigLayer{
237 Layer: &mockLayer{size: 1000000000 + 42},
238 },
239 env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"},
240 wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"),
241 }, {
242 name: "payload size is within overridden limit",
243 l: &sigLayer{
244 Layer: layer,
245 desc: v1.Descriptor{
246 Digest: digest,
247 Annotations: map[string]string{
248 sigkey: "blah",
249 },
250 },
251 },
252 env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"},
253 wantSig: "blah",
254 }}
255
256 for _, test := range tests {
257 t.Run(test.name, func(t *testing.T) {
258 for k, v := range test.env {
259 t.Setenv(k, v)
260 }
261 b, err := test.l.Payload()
262 switch {
263 case (err != nil) != (test.wantPayloadErr != nil):
264 t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
265 case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error():
266 t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
267 case err == nil:
268 if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil {
269 t.Errorf("v1.SHA256() = %v", err)
270 } else if want := digest; want != got {
271 t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
272 }
273 }
274 if err != nil {
275 return
276 }
277
278 switch got, err := test.l.Base64Signature(); {
279 case (err != nil) != (test.wantSigErr != nil):
280 t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
281 case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error():
282 t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
283 case got != test.wantSig:
284 t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig)
285 }
286
287 switch got, err := test.l.Cert(); {
288 case (err != nil) != (test.wantCertErr != nil):
289 t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
290 case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error():
291 t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
292 case (got != nil) != test.wantCert:
293 t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert)
294 }
295
296 switch got, err := test.l.Chain(); {
297 case (err != nil) != (test.wantChainErr != nil):
298 t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
299 case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error():
300 t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
301 case len(got) != test.wantChain:
302 t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain)
303 }
304
305 switch got, err := test.l.Bundle(); {
306 case (err != nil) != (test.wantBundleErr != nil):
307 t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr)
308 case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error():
309 t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr)
310 case !cmp.Equal(got, test.wantBundle):
311 t.Errorf("Bundle() %s", cmp.Diff(got, test.wantBundle))
312 }
313 })
314 }
315 }
316
317 func TestSignatureWithTSAAnnotation(t *testing.T) {
318 layer, err := random.Layer(300 , types.DockerLayer)
319 if err != nil {
320 t.Fatalf("random.Layer() = %v", err)
321 }
322 digest, err := layer.Digest()
323 if err != nil {
324 t.Fatalf("Digest() = %v", err)
325 }
326
327 tests := []struct {
328 name string
329 l *sigLayer
330 wantPayloadErr error
331 wantSig string
332 wantSigErr error
333 wantCert bool
334 wantCertErr error
335 wantChain int
336 wantChainErr error
337 wantBundle *bundle.RFC3161Timestamp
338 wantBundleErr error
339 }{{
340 name: "just payload and signature",
341 l: &sigLayer{
342 Layer: layer,
343 desc: v1.Descriptor{
344 Digest: digest,
345 Annotations: map[string]string{
346 sigkey: "blah",
347 },
348 },
349 },
350 wantSig: "blah",
351 }, {
352 name: "with empty other keys",
353 l: &sigLayer{
354 Layer: layer,
355 desc: v1.Descriptor{
356 Digest: digest,
357 Annotations: map[string]string{
358 sigkey: "blah",
359 certkey: "",
360 chainkey: "",
361 RFC3161TimestampKey: "",
362 },
363 },
364 },
365 wantSig: "blah",
366 }, {
367 name: "missing signature",
368 l: &sigLayer{
369 Layer: layer,
370 desc: v1.Descriptor{
371 Digest: digest,
372 },
373 },
374 wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey),
375 }, {
376 name: "min plus bad bundle",
377 l: &sigLayer{
378 Layer: layer,
379 desc: v1.Descriptor{
380 Digest: digest,
381 Annotations: map[string]string{
382 sigkey: "blah",
383 RFC3161TimestampKey: `}`,
384 },
385 },
386 },
387 wantSig: "blah",
388 wantBundleErr: errors.New(`unmarshaling RFC3161 timestamp bundle: invalid character '}' looking for beginning of value`),
389 }, {
390 name: "min plus bad cert",
391 l: &sigLayer{
392 Layer: layer,
393 desc: v1.Descriptor{
394 Digest: digest,
395 Annotations: map[string]string{
396 sigkey: "blah",
397 certkey: `GARBAGE`,
398 },
399 },
400 },
401 wantSig: "blah",
402 wantCertErr: errors.New(`error during PEM decoding`),
403 }, {
404 name: "min plus bad chain",
405 l: &sigLayer{
406 Layer: layer,
407 desc: v1.Descriptor{
408 Digest: digest,
409 Annotations: map[string]string{
410 sigkey: "blah",
411 chainkey: `GARBAGE`,
412 },
413 },
414 },
415 wantSig: "blah",
416 wantChainErr: errors.New(`error during PEM decoding`),
417 }, {
418 name: "min plus RFC3161 timestamp bundle",
419 l: &sigLayer{
420 Layer: layer,
421 desc: v1.Descriptor{
422 Digest: digest,
423 Annotations: map[string]string{
424 sigkey: "TSA blah",
425
426
427 RFC3161TimestampKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`,
428 },
429 },
430 },
431 wantSig: "TSA blah",
432 wantBundle: &bundle.RFC3161Timestamp{
433 SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
434 },
435 }}
436
437 for _, test := range tests {
438 t.Run(test.name, func(t *testing.T) {
439 b, err := test.l.Payload()
440 switch {
441 case (err != nil) != (test.wantPayloadErr != nil):
442 t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
443 case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error():
444 t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr)
445 case err == nil:
446 if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil {
447 t.Errorf("v1.SHA256() = %v", err)
448 } else if want := digest; want != got {
449 t.Errorf("v1.SHA256() = %v, wanted %v", got, want)
450 }
451 }
452
453 switch got, err := test.l.Base64Signature(); {
454 case (err != nil) != (test.wantSigErr != nil):
455 t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
456 case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error():
457 t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr)
458 case got != test.wantSig:
459 t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig)
460 }
461
462 switch got, err := test.l.Cert(); {
463 case (err != nil) != (test.wantCertErr != nil):
464 t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
465 case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error():
466 t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr)
467 case (got != nil) != test.wantCert:
468 t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert)
469 }
470
471 switch got, err := test.l.Chain(); {
472 case (err != nil) != (test.wantChainErr != nil):
473 t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
474 case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error():
475 t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr)
476 case len(got) != test.wantChain:
477 t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain)
478 }
479
480 switch got, err := test.l.RFC3161Timestamp(); {
481 case (err != nil) != (test.wantBundleErr != nil):
482 t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr)
483 case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error():
484 t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr)
485 case !cmp.Equal(got, test.wantBundle):
486 t.Errorf("RFC3161Timestamp() %s", cmp.Diff(got, test.wantBundle))
487 }
488 })
489 }
490 }
491
492 type mockLayer struct {
493 size int64
494 }
495
496 func (m *mockLayer) Size() (int64, error) {
497 return m.size, nil
498 }
499
500 func (m *mockLayer) Compressed() (io.ReadCloser, error) {
501 return io.NopCloser(strings.NewReader("data")), nil
502 }
503
504 func (m *mockLayer) Digest() (v1.Hash, error) { panic("not implemented") }
505 func (m *mockLayer) DiffID() (v1.Hash, error) { panic("not implemented") }
506 func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") }
507 func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") }
508
View as plain text