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