Skip to content

Commit dece275

Browse files
committed
Fix parsing of in-toto for string predicates
ee3d9fe changed the parsing of an in-toto Statement from using the deprecated in-toto/in-toto-golang Statement[1] to the recommended in-toto/attestation Statement[2]. This type is more strict about the Predicate field. Attestations that were created with `--type spdxjson` correctly become a regular JSON object under the "predicate" field in the Statement object, but attestations that were created with `--type spdx` become a string, which cosign was unable to Unmarshal with the new stricter type. This change addresses the issue by pre-parsing of the predicate to ensure it is properly formatted before unmarshalling it. [1] https://pkg.go.dev/github.com/in-toto/in-toto-golang/in_toto#Statement [2] https://pkg.go.dev/github.com/in-toto/attestation/go/v1#Statement Signed-off-by: Colleen Murphy <colleenmurphy@google.com>
1 parent bd4f0fd commit dece275

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

pkg/cosign/verifiers.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotati
7272
return err
7373
}
7474

75-
st := in_toto.Statement{}
76-
if err := json.Unmarshal(stBytes, &st); err != nil {
75+
st, err := toStatement(stBytes)
76+
if err != nil {
7777
return err
7878
}
7979
for _, subj := range st.Subject {
@@ -92,3 +92,38 @@ func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotati
9292
}
9393
return errors.New("no matching subject digest found")
9494
}
95+
96+
func toStatement(stBytes []byte) (*in_toto.Statement, error) {
97+
st := in_toto.Statement{}
98+
if err := json.Unmarshal(stBytes, &st); err == nil {
99+
return &st, nil
100+
}
101+
// The predicate might be a string instead of a JSON object, which the in-toto library can't parse.
102+
// Convert it to a JSON object and try again.
103+
stmtMap := make(map[string]any)
104+
if err := json.Unmarshal(stBytes, &stmtMap); err != nil {
105+
return nil, err
106+
}
107+
predicate, ok := stmtMap["predicate"]
108+
if !ok {
109+
return nil, fmt.Errorf("could not parse statement, could not find predicate")
110+
}
111+
p, ok := predicate.(string)
112+
if !ok {
113+
return nil, fmt.Errorf("could not parse predicate with type %T", predicate)
114+
}
115+
pMap := make(map[string]any)
116+
if err := json.Unmarshal([]byte(p), &pMap); err != nil {
117+
return nil, fmt.Errorf("could not parse statement as JSON: %w", err)
118+
}
119+
stmtMap["predicate"] = pMap
120+
remarshaled, err := json.Marshal(stmtMap)
121+
if err != nil {
122+
return nil, fmt.Errorf("failed to marshal statement: %w", err)
123+
}
124+
if err := json.Unmarshal(remarshaled, &st); err != nil {
125+
return nil, err
126+
}
127+
128+
return &st, nil
129+
}

pkg/cosign/verifiers_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,33 @@ The following JSON is the payload in valid attestation:
4141
}
4242
*/
4343

44+
/* payload for a valid attestation with a string as a predicate:
45+
{
46+
"_type": "https://in-toto.io/Statement/v0.1",
47+
"predicateType": "https://spdx.dev/Document",
48+
"subject": [
49+
{
50+
"name": "localhost:5000/wolfi-base4",
51+
"digest": {
52+
"sha256": "9925d3017788558fa8f27e8bb160b791e56202b60c91fbcc5c867de3175986c8"
53+
}
54+
}
55+
],
56+
"predicate": "{\"spdxVersion\":\"SPDX-2.2\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"SBOM-SPDX-34f1a7f5-03ff-4277-9021-8c04f8777803\",\"documentNamespace\":\"https://spdx.org/spdxdocs/k8s-releng-bom-16f4e288-6bdf-4b89-a79a-9ffd56ad33e0\",\"creationInfo\":{\"licenseListVersion\":\"\",\"creators\":[\"Organization: Kubernetes Release Engineering\",\"Tool: sigs.k8s.io/bom/pkg/spdx\"],\"created\":\"2022-06-07T22:14:56Z\",\"comment\":\"\"},\"packages\":[]}\n"
57+
}
58+
*/
59+
4460
const (
4561
validIntotoStatement = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNVoifX0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}`
4662
invalidIntotoStatementBadEncoding = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNV=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}`
4763
// Start with valid, but change subject.Digest.sha256 to subject.Digest.999
48-
validIntotoStatementMissingSubject = `{"payloadType":"application/vnd.in-toto+json","payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImNvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJyZWdpc3RyeS5sb2NhbDo1MDAwL2tuYXRpdmUvZGVtbyIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgIjk5OSI6ICI2YzZmZDZhNDExNWM2ZTk5OGZmMzU3Y2Q5MTQ2ODA5MzFiYjlhNmMxYTdjZDVmNWNiMmY1ZTFjMDkzMmFiNmVkIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlIjogewogICAgIkRhdGEiOiAiZm9vYmFyIHRlc3QgYXR0ZXN0YXRpb24iLAogICAgIlRpbWVzdGFtcCI6ICIyMDIyLTA0LTA3VDE5OjIyOjI1WiIKICB9Cn0K","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}`
64+
validIntotoStatementMissingSubject = `{"payloadType":"application/vnd.in-toto+json","payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImNvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJyZWdpc3RyeS5sb2NhbDo1MDAwL2tuYXRpdmUvZGVtbyIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgIjk5OSI6ICI2YzZmZDZhNDExNWM2ZTk5OGZmMzU3Y2Q5MTQ2ODA5MzFiYjlhNmMxYTdjZDVmNWNiMmY1ZTFjMDkzMmFiNmVkIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlIjogewogICAgIkRhdGEiOiAiZm9vYmFyIHRlc3QgYXR0ZXN0YXRpb24iLAogICAgIlRpbWVzdGFtcCI6ICIyMDIyLTA0LTA3VDE5OjIyOjI1WiIKICB9Cn0K","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}`
65+
validIntotoStatementStringPredicate = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3NwZHguZGV2L0RvY3VtZW50Iiwic3ViamVjdCI6W3sibmFtZSI6ImxvY2FsaG9zdDo1MDAwL3dvbGZpLWJhc2U0IiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk5MjVkMzAxNzc4ODU1OGZhOGYyN2U4YmIxNjBiNzkxZTU2MjAyYjYwYzkxZmJjYzVjODY3ZGUzMTc1OTg2YzgifX1dLCJwcmVkaWNhdGUiOiJ7XCJzcGR4VmVyc2lvblwiOlwiU1BEWC0yLjJcIixcImRhdGFMaWNlbnNlXCI6XCJDQzAtMS4wXCIsXCJTUERYSURcIjpcIlNQRFhSZWYtRE9DVU1FTlRcIixcIm5hbWVcIjpcIlNCT00tU1BEWC0zNGYxYTdmNS0wM2ZmLTQyNzctOTAyMS04YzA0Zjg3Nzc4MDNcIixcImRvY3VtZW50TmFtZXNwYWNlXCI6XCJodHRwczovL3NwZHgub3JnL3NwZHhkb2NzL2s4cy1yZWxlbmctYm9tLTE2ZjRlMjg4LTZiZGYtNGI4OS1hNzlhLTlmZmQ1NmFkMzNlMFwiLFwiY3JlYXRpb25JbmZvXCI6e1wibGljZW5zZUxpc3RWZXJzaW9uXCI6XCJcIixcImNyZWF0b3JzXCI6W1wiT3JnYW5pemF0aW9uOiBLdWJlcm5ldGVzIFJlbGVhc2UgRW5naW5lZXJpbmdcIixcIlRvb2w6IHNpZ3MuazhzLmlvL2JvbS9wa2cvc3BkeFwiXSxcImNyZWF0ZWRcIjpcIjIwMjItMDYtMDdUMjI6MTQ6NTZaXCIsXCJjb21tZW50XCI6XCJcIn0sXCJwYWNrYWdlc1wiOltdfVxuIn0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}`
4966
)
5067

5168
var validDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932ab6ed"}
5269
var invalidDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932xxxxx"}
70+
var validDigestStringPredicate = v1.Hash{Algorithm: "sha256", Hex: "9925d3017788558fa8f27e8bb160b791e56202b60c91fbcc5c867de3175986c8"}
5371

5472
func Test_IntotoSubjectClaimVerifier(t *testing.T) {
5573
tests := []struct {
@@ -63,6 +81,7 @@ func Test_IntotoSubjectClaimVerifier(t *testing.T) {
6381
{payload: validIntotoStatement, digest: invalidDigest, shouldFail: true},
6482
{payload: validIntotoStatementMissingSubject, digest: validDigest, shouldFail: true},
6583
{payload: validIntotoStatement, digest: validDigest, shouldFail: false},
84+
{payload: validIntotoStatementStringPredicate, digest: validDigestStringPredicate, shouldFail: false},
6685
}
6786
for _, tc := range tests {
6887
ociSig, err := static.NewSignature([]byte(tc.payload), "")

0 commit comments

Comments
 (0)