Skip to content

Commit 1dad9ff

Browse files
committed
fix: add tests
Signed-off-by: SequeI <asiek@redhat.com>
1 parent b0e7325 commit 1dad9ff

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

internal/attestation/attestation_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package attestation
2020

2121
import (
2222
"crypto/x509"
23+
"encoding/base64"
2324
"fmt"
2425
"testing"
2526

@@ -220,6 +221,91 @@ func TestProvenance_Signatures(t *testing.T) {
220221
}
221222
}
222223

224+
func TestProvenanceFromBundlePayload(t *testing.T) {
225+
sig1 := `{"keyid": "key-id-1", "sig": "sig-1"}`
226+
227+
payloadJson := `{
228+
"_type": "https://in-toto.io/Statement/v0.1",
229+
"predicateType": "https://cool-type.example.io/Amazing/v2.0",
230+
"predicate": {
231+
"secure": "very",
232+
"hacks": "none"
233+
}
234+
}`
235+
236+
fullAtt := fmt.Sprintf(`{"payloadType":"application/vnd.in-toto+json","signatures": [%s], "payload": "%s"}`, sig1, encode(payloadJson))
237+
238+
cases := []struct {
239+
name string
240+
setup func(l *mockSignature)
241+
dsseJSON string
242+
expectErr string
243+
}{
244+
{
245+
name: "valid bundle attestation with signature from payload",
246+
setup: func(l *mockSignature) {
247+
l.On("Base64Signature").Return("", nil)
248+
l.On("Cert").Return(&x509.Certificate{}, nil)
249+
l.On("Chain").Return([]*x509.Certificate{}, nil)
250+
},
251+
dsseJSON: fullAtt,
252+
},
253+
{
254+
name: "valid bundle attestation with signature from certificate",
255+
setup: func(l *mockSignature) {
256+
l.On("Base64Signature").Return("sig-from-cert", nil)
257+
l.On("Cert").Return(signature.ParseChainguardReleaseCert(), nil)
258+
l.On("Chain").Return(signature.ParseSigstoreChainCert(), nil)
259+
},
260+
dsseJSON: fullAtt,
261+
},
262+
{
263+
name: "malformed JSON",
264+
setup: func(l *mockSignature) {},
265+
dsseJSON: `{not json`,
266+
expectErr: "malformed bundle attestation",
267+
},
268+
{
269+
name: "empty payload field",
270+
setup: func(l *mockSignature) {},
271+
dsseJSON: `{"signatures": [], "payload": ""}`,
272+
expectErr: "no `payload` data found in bundle attestation",
273+
},
274+
{
275+
name: "invalid base64 payload",
276+
setup: func(l *mockSignature) {},
277+
dsseJSON: `{"signatures": [], "payload": "not-valid-base64!@#"}`,
278+
expectErr: "malformed attestation data",
279+
},
280+
{
281+
name: "invalid statement JSON in payload",
282+
setup: func(l *mockSignature) {},
283+
dsseJSON: fmt.Sprintf(`{"signatures": [], "payload": "%s"}`,
284+
base64.StdEncoding.EncodeToString([]byte(`not-json`))),
285+
expectErr: "malformed bundle attestation",
286+
},
287+
}
288+
289+
for _, c := range cases {
290+
t.Run(c.name, func(t *testing.T) {
291+
sig := mockSignature{&mock.Mock{}}
292+
c.setup(&sig)
293+
294+
p, err := ProvenanceFromBundlePayload(sig, []byte(c.dsseJSON))
295+
if c.expectErr != "" {
296+
assert.ErrorContains(t, err, c.expectErr)
297+
assert.Nil(t, p)
298+
return
299+
}
300+
301+
assert.NoError(t, err)
302+
assert.JSONEq(t, payloadJson, string(p.Statement()))
303+
assert.Equal(t, "https://cool-type.example.io/Amazing/v2.0", p.PredicateType())
304+
assert.NotEmpty(t, p.Signatures())
305+
})
306+
}
307+
}
308+
223309
func TestProvenance_Subject(t *testing.T) {
224310
//nolint:staticcheck
225311
mockSubject1 := in_toto.Subject{

internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,169 @@ func TestAttestationDataMarshalJSON(t *testing.T) {
914914
}
915915
}
916916

917+
func TestParseAttestationsFromBundles(t *testing.T) {
918+
ref := name.MustParseReference("registry.io/repository/image:tag")
919+
920+
slsaV02Statement := in_toto.ProvenanceStatementSLSA02{
921+
//nolint:staticcheck
922+
StatementHeader: in_toto.StatementHeader{
923+
Type: in_toto.StatementInTotoV01,
924+
PredicateType: v02.PredicateSLSAProvenance,
925+
//nolint:staticcheck
926+
Subject: []in_toto.Subject{
927+
{Name: "test-image", Digest: common.DigestSet{"sha256": "abc123"}},
928+
},
929+
},
930+
Predicate: v02.ProvenancePredicate{
931+
BuildType: pipelineRunBuildType,
932+
Builder: common.ProvenanceBuilder{ID: "https://tekton.dev/chains/v2"},
933+
},
934+
}
935+
936+
cases := []struct {
937+
name string
938+
layers []oci.Signature
939+
expectErr bool
940+
errContains string
941+
expectedAttCount int
942+
expectedPredTypes []string
943+
}{
944+
{
945+
name: "empty layers",
946+
layers: []oci.Signature{},
947+
expectedAttCount: 0,
948+
},
949+
{
950+
name: "single valid bundle attestation",
951+
layers: []oci.Signature{createBundleDSSESignature(t, slsaV02Statement)},
952+
expectedAttCount: 1,
953+
expectedPredTypes: []string{v02.PredicateSLSAProvenance},
954+
},
955+
{
956+
name: "multiple valid bundle attestations",
957+
layers: []oci.Signature{
958+
createBundleDSSESignature(t, slsaV02Statement),
959+
createBundleDSSESignature(t, slsaV02Statement),
960+
},
961+
expectedAttCount: 2,
962+
expectedPredTypes: []string{v02.PredicateSLSAProvenance, v02.PredicateSLSAProvenance},
963+
},
964+
{
965+
name: "skips non-intoto payload type",
966+
layers: func() []oci.Signature {
967+
payload := `{"payloadType":"application/octet-stream","payload":"aGVsbG8="}`
968+
sig, err := static.NewSignature([]byte(payload), "test-sig")
969+
require.NoError(t, err)
970+
return []oci.Signature{sig}
971+
}(),
972+
expectedAttCount: 0,
973+
},
974+
{
975+
name: "skips invalid JSON payload",
976+
layers: func() []oci.Signature {
977+
sig, err := static.NewSignature([]byte(`not-json`), "test-sig")
978+
require.NoError(t, err)
979+
return []oci.Signature{sig}
980+
}(),
981+
expectedAttCount: 0,
982+
},
983+
{
984+
name: "valid attestation with populated signatures",
985+
layers: []oci.Signature{createBundleDSSESignature(t, slsaV02Statement)},
986+
expectedAttCount: 1,
987+
},
988+
}
989+
990+
for _, tc := range cases {
991+
t.Run(tc.name, func(t *testing.T) {
992+
a := ApplicationSnapshotImage{reference: ref}
993+
994+
err := a.parseAttestationsFromBundles(tc.layers)
995+
996+
if tc.expectErr {
997+
require.Error(t, err)
998+
if tc.errContains != "" {
999+
assert.Contains(t, err.Error(), tc.errContains)
1000+
}
1001+
} else {
1002+
require.NoError(t, err)
1003+
assert.Equal(t, tc.expectedAttCount, len(a.attestations))
1004+
1005+
for i, expectedType := range tc.expectedPredTypes {
1006+
assert.Equal(t, expectedType, a.attestations[i].PredicateType())
1007+
}
1008+
1009+
for _, att := range a.attestations {
1010+
assert.NotNil(t, att.Signatures(), "bundle attestations should have signatures populated")
1011+
}
1012+
}
1013+
})
1014+
}
1015+
}
1016+
1017+
func TestParseAttestationsFromBundlesPopulatesSignatures(t *testing.T) {
1018+
ref := name.MustParseReference("registry.io/repository/image:tag")
1019+
1020+
//nolint:staticcheck
1021+
statement := in_toto.Statement{
1022+
//nolint:staticcheck
1023+
StatementHeader: in_toto.StatementHeader{
1024+
Type: in_toto.StatementInTotoV01,
1025+
PredicateType: "https://example.com/test/v1",
1026+
//nolint:staticcheck
1027+
Subject: []in_toto.Subject{
1028+
{Name: "test", Digest: common.DigestSet{"sha256": "abc"}},
1029+
},
1030+
},
1031+
Predicate: json.RawMessage(`{"test":"data"}`),
1032+
}
1033+
1034+
a := ApplicationSnapshotImage{reference: ref}
1035+
layers := []oci.Signature{createBundleDSSESignature(t, statement)}
1036+
1037+
err := a.parseAttestationsFromBundles(layers)
1038+
require.NoError(t, err)
1039+
require.Len(t, a.attestations, 1)
1040+
1041+
sigs := a.attestations[0].Signatures()
1042+
assert.NotEmpty(t, sigs, "attestation signatures should be populated for bundle attestations")
1043+
assert.NotEmpty(t, sigs[0].Certificate, "attestation signature should have certificate")
1044+
}
1045+
1046+
// createBundleDSSESignature creates a test signature mimicking the bundle
1047+
// format: a raw DSSE envelope accessible via Payload().
1048+
func createBundleDSSESignature(t *testing.T, statement any) oci.Signature {
1049+
t.Helper()
1050+
1051+
statementJSON, err := json.Marshal(statement)
1052+
require.NoError(t, err)
1053+
1054+
encodedStatement := base64.StdEncoding.EncodeToString(statementJSON)
1055+
1056+
dsseEnvelope := dsse.Envelope{
1057+
Payload: encodedStatement,
1058+
PayloadType: "application/vnd.in-toto+json",
1059+
Signatures: []dsse.Signature{
1060+
{KeyID: "test-key", Sig: "dGVzdC1zaWduYXR1cmU="},
1061+
},
1062+
}
1063+
1064+
payload, err := json.Marshal(dsseEnvelope)
1065+
require.NoError(t, err)
1066+
1067+
sig, err := static.NewSignature(
1068+
payload,
1069+
"test-signature",
1070+
static.WithCertChain(
1071+
signature.ChainguardReleaseCert,
1072+
signature.SigstoreChainCert,
1073+
),
1074+
)
1075+
require.NoError(t, err)
1076+
1077+
return sig
1078+
}
1079+
9171080
// createDSSESignature creates a test signature with a DSSE envelope containing the given statement
9181081
func createDSSESignature(t *testing.T, statement any) oci.Signature {
9191082
t.Helper()

0 commit comments

Comments
 (0)