From da38613f0a4f3ccf6cff7127cf04c1b817e87414 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 20 Dec 2024 13:30:37 -0600 Subject: [PATCH] Fix intoto unmarshal (#366) * Fix intoto statement marshal/unmarshal (see in-toto/attestation#363) Signed-off-by: Andrew Gillis * Apply modified tests from be320de8a15be73fdf525172387897942b2b4f84 Co-authored-by: Andrew Gillis Signed-off-by: Cody Soyland * Simplify custom Marshal, remove Unmarshal Signed-off-by: Cody Soyland * Add custom UnmarshalJSON to VerificationResult Signed-off-by: Cody Soyland --------- Signed-off-by: Andrew Gillis Signed-off-by: Cody Soyland Co-authored-by: Andrew Gillis --- pkg/verify/signed_entity.go | 36 ++++++++++++++++++++++ pkg/verify/signed_entity_test.go | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/pkg/verify/signed_entity.go b/pkg/verify/signed_entity.go index fcdda92..857596d 100644 --- a/pkg/verify/signed_entity.go +++ b/pkg/verify/signed_entity.go @@ -17,6 +17,7 @@ package verify import ( "crypto/x509" "encoding/asn1" + "encoding/json" "errors" "fmt" "io" @@ -26,6 +27,7 @@ import ( "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" + "google.golang.org/protobuf/encoding/protojson" ) const ( @@ -221,6 +223,40 @@ func NewVerificationResult() *VerificationResult { } } +// MarshalJSON deals with protojson needed for the Statement. +// Can be removed when https://github.com/in-toto/attestation/pull/403 is merged. +func (b *VerificationResult) MarshalJSON() ([]byte, error) { + statement, err := protojson.Marshal(b.Statement) + if err != nil { + return nil, err + } + // creating a type alias to avoid infinite recursion, as MarshalJSON is + // not copied into the alias. + type Alias VerificationResult + return json.Marshal(struct { + Alias + Statement json.RawMessage `json:"statement,omitempty"` + }{ + Alias: Alias(*b), + Statement: statement, + }) +} + +func (b *VerificationResult) UnmarshalJSON(data []byte) error { + b.Statement = &in_toto.Statement{} + type Alias VerificationResult + aux := &struct { + Alias + Statement json.RawMessage `json:"statement,omitempty"` + }{ + Alias: Alias(*b), + } + if err := json.Unmarshal(data, aux); err != nil { + return err + } + return protojson.Unmarshal(aux.Statement, b.Statement) +} + type PolicyOption func(*PolicyConfig) error type ArtifactPolicyOption func(*PolicyConfig) error diff --git a/pkg/verify/signed_entity_test.go b/pkg/verify/signed_entity_test.go index 70ca15a..d32de6b 100644 --- a/pkg/verify/signed_entity_test.go +++ b/pkg/verify/signed_entity_test.go @@ -23,9 +23,11 @@ import ( "encoding/hex" "encoding/json" + in_toto "github.com/in-toto/attestation/go/v1" "github.com/sigstore/sigstore-go/pkg/testing/data" "github.com/sigstore/sigstore-go/pkg/verify" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/structpb" ) func TestSignedEntityVerifierInitialization(t *testing.T) { @@ -422,3 +424,53 @@ func TestSigstoreBundle2Sig(t *testing.T) { assert.True(t, errors.Is(err, verify.ErrDSSEInvalidSignatureCount)) assert.Nil(t, res) } + +func TestStatementSerializesToValidInTotoStatement(t *testing.T) { + statement := in_toto.Statement{} + statement.Type = "https://in-toto.io/Statement/v0.1" + statement.PredicateType = "https://example.org/predicate" + statement.Subject = []*in_toto.ResourceDescriptor{ + { + Name: "artifact-name", + Digest: map[string]string{ + "sha256": "artifact-digest", + }, + }, + } + statement.Predicate = &structpb.Struct{ + Fields: map[string]*structpb.Value{}, + } + + result := verify.NewVerificationResult() + result.Statement = &statement + + // marshal the statement to JSON + resultJSON, err := json.Marshal(result) + assert.NoError(t, err) + want := ` + { + "mediaType": "application/vnd.dev.sigstore.verificationresult+json;version=0.1", + "verifiedTimestamps": null, + "statement": { + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://example.org/predicate", + "subject": [ + { + "name": "artifact-name", + "digest": { + "sha256": "artifact-digest" + } + } + ], + "predicate": {} + } + }` + assert.JSONEq(t, want, string(resultJSON)) + + // unmarshal the JSON back to a VerificationResult + result2 := verify.NewVerificationResult() + err = json.Unmarshal(resultJSON, result2) + assert.NoError(t, err) + assert.Equal(t, result.MediaType, result2.MediaType) + assert.Equal(t, result.Statement, result2.Statement) +}