From a7406dbf749cc467de2e8e8db4953354103eb49d Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 15 Aug 2024 21:40:45 +0000 Subject: [PATCH] better errors Signed-off-by: Ramon Petgrave --- verifiers/internal/gha/bundle.go | 25 +-- verifiers/internal/gha/bundle_test.go | 214 +++++++++++++++++--------- verifiers/internal/gha/rekor.go | 2 +- 3 files changed, 159 insertions(+), 82 deletions(-) diff --git a/verifiers/internal/gha/bundle.go b/verifiers/internal/gha/bundle.go index a213d57..a83368d 100644 --- a/verifiers/internal/gha/bundle.go +++ b/verifiers/internal/gha/bundle.go @@ -21,7 +21,10 @@ import ( // Bundle specific errors. var ( ErrorMismatchSignature = errors.New("bundle tlog entry does not match signature") + ErrorUnequalSignatures = errors.New("bundle tlog entry and envelope have an unequal number of signatures") + ErrorNoSignatures = errors.New("envolope has no signatures") ErrorUnexpectedEntryType = errors.New("unexpected tlog entry type") + ErorrParsingEntryBody = errors.New("unexpected layout of the bundle tlog entry body") ErrorMissingCertInBundle = errors.New("missing signing certificate in bundle") ErrorUnexpectedBundleContent = errors.New("expected DSSE bundle content") ) @@ -111,6 +114,10 @@ func getLeafCertFromBundle(bundle *bundle_v1.Bundle) (*x509.Certificate, error) // DSSE envelope. It MUST verify that the signatures match to ensure that the // tlog timestamp attests to the signature creation time. func matchRekorEntryWithEnvelope(tlogEntry *v1.TransparencyLogEntry, env *dsselib.Envelope) error { + if len(env.Signatures) == 0 { + return ErrorNoSignatures + } + kindVersion := tlogEntry.GetKindVersion() if kindVersion.Kind == "intoto" && kindVersion.Version == "0.0.2" { @@ -121,7 +128,7 @@ func matchRekorEntryWithEnvelope(tlogEntry *v1.TransparencyLogEntry, env *dsseli return matchRekorEntryWithEnvelopeDSSEv001(tlogEntry, env) } - return fmt.Errorf("%w: %s: %s", ErrorUnexpectedEntryType, kindVersion.Kind, kindVersion.Version) + return fmt.Errorf("%w: wanted either intoto v0.0.2 or dsse v0.0.1, got: %s %s", ErrorUnexpectedEntryType, kindVersion.Kind, kindVersion.Version) } // matchRekorEntryWithEnvelopeDSSEv001 handles matchRekorEntryWithEnvelope for the intoto v0.0.1 type version. @@ -130,18 +137,18 @@ func matchRekorEntryWithEnvelopeIntotov002(tlogEntry *v1.TransparencyLogEntry, e var toto models.Intoto var intotoObj models.IntotoV002Schema if err := json.Unmarshal(canonicalBody, &toto); err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } specMarshal, err := json.Marshal(toto.Spec) if err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } if err := json.Unmarshal(specMarshal, &intotoObj); err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } if len(env.Signatures) != len(intotoObj.Content.Envelope.Signatures) { - return fmt.Errorf("expected %d sigs in canonical body, got %d", + return fmt.Errorf("%w: wanted %d, got %d", ErrorUnequalSignatures, len(env.Signatures), len(intotoObj.Content.Envelope.Signatures)) } @@ -169,20 +176,20 @@ func matchRekorEntryWithEnvelopeDSSEv001(tlogEntry *v1.TransparencyLogEntry, env canonicalBody := tlogEntry.GetCanonicalizedBody() var dsseObj models.DSSE if err := json.Unmarshal(canonicalBody, &dsseObj); err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } var dsseSchemaObj models.DSSEV001Schema specMarshal, err := json.Marshal(dsseObj.Spec) if err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } if err := json.Unmarshal(specMarshal, &dsseSchemaObj); err != nil { - return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err) + return fmt.Errorf("%w: %s", ErorrParsingEntryBody, err) } if len(env.Signatures) != len(dsseSchemaObj.Signatures) { - return fmt.Errorf("expected %d sigs in canonical body, got %d", + return fmt.Errorf("%w: wanted %d, got %d", ErrorUnequalSignatures, len(env.Signatures), len(dsseSchemaObj.Signatures)) } diff --git a/verifiers/internal/gha/bundle_test.go b/verifiers/internal/gha/bundle_test.go index 5dffc8e..a944d11 100644 --- a/verifiers/internal/gha/bundle_test.go +++ b/verifiers/internal/gha/bundle_test.go @@ -2,11 +2,13 @@ package gha import ( "context" + "encoding/base64" "fmt" "os" "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse" rekorpbv1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" @@ -83,12 +85,60 @@ func Test_verifyBundle(t *testing.T) { func Test_matchRekorEntryWithEnvelope(t *testing.T) { t.Parallel() + goodDSSEV001Body := []byte(` + { + "apiVersion": "0.0.1", + "kind": "dsse", + "spec": { + "signatures": [ + { + "signature": "MEUCIHiah7zQLL9LK9m9/0JH3rHIaYlvcus4h84KOdaR3iAlAiEAio+tnbpkW+V+FPYxpuiJBY0MuD43RVX5QmMwk3sgnUE=" + } + ] + } + } + `) + goodDSSEV001Sig := "MEUCIHiah7zQLL9LK9m9/0JH3rHIaYlvcus4h84KOdaR3iAlAiEAio+tnbpkW+V+FPYxpuiJBY0MuD43RVX5QmMwk3sgnUE=" + goodIntotoV002Body := []byte(` + { + "apiVersion": "0.0.2", + "kind": "intoto", + "spec": { + "content": { + "envelope": { + "signatures": [ + { + "publicKey": "mypubkey", + "sig": "TUVVQ0lRRGVoVlQ0MFRLUDNxWnlVN3BDcXhwMzRyeGt1Wk1ZRTVBWGhBb0x4NTdzdmdJZ0xSa3NCV1hPamUyMCtrKzh4M3ViZkZzNlpxQ2dLNXc3eXlITFB6SC9tcGs9" + } + ] + } + } + } + } + `) + goodIntotoV001Sig := "MEUCIQDehVT40TKP3qZyU7pCqxp34rxkuZMYE5AXhAoLx57svgIgLRksBWXOje20+k+8x3ubfFs6ZqCgK5w7yyHLPzH/mpk=" + tests := []struct { name string tlog *rekorpbv1.TransparencyLogEntry env *dsselib.Envelope err error }{ + { + name: "failure: no signtures in envelope", + tlog: &rekorpbv1.TransparencyLogEntry{ + KindVersion: &rekorpbv1.KindVersion{ + Kind: "intoto", + Version: "0.0.2", + }, + CanonicalizedBody: goodIntotoV002Body, + }, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{}, + }, + err: ErrorNoSignatures, + }, { name: "success: dsse v0.0.1", tlog: &rekorpbv1.TransparencyLogEntry{ @@ -96,24 +146,12 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Kind: "dsse", Version: "0.0.1", }, - CanonicalizedBody: []byte(` - { - "apiVersion": "0.0.1", - "kind": "dsse", - "spec": { - "signatures": [ - { - "signature": "MEUCIHiah7zQLL9LK9m9/0JH3rHIaYlvcus4h84KOdaR3iAlAiEAio+tnbpkW+V+FPYxpuiJBY0MuD43RVX5QmMwk3sgnUE=" - } - ] - } - } - `), + CanonicalizedBody: goodDSSEV001Body, }, env: &dsselib.Envelope{ Signatures: []dsselib.Signature{ { - Sig: "MEUCIHiah7zQLL9LK9m9/0JH3rHIaYlvcus4h84KOdaR3iAlAiEAio+tnbpkW+V+FPYxpuiJBY0MuD43RVX5QmMwk3sgnUE=", + Sig: goodDSSEV001Sig, }, }, }, @@ -126,29 +164,12 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Kind: "intoto", Version: "0.0.2", }, - CanonicalizedBody: []byte(` - { - "apiVersion": "0.0.2", - "kind": "intoto", - "spec": { - "content": { - "envelope": { - "signatures": [ - { - "publicKey": "mypubkey", - "sig": "TUVVQ0lRRGVoVlQ0MFRLUDNxWnlVN3BDcXhwMzRyeGt1Wk1ZRTVBWGhBb0x4NTdzdmdJZ0xSa3NCV1hPamUyMCtrKzh4M3ViZkZzNlpxQ2dLNXc3eXlITFB6SC9tcGs9" - } - ] - } - } - } - } - `), + CanonicalizedBody: goodIntotoV002Body, }, env: &dsselib.Envelope{ Signatures: []dsselib.Signature{ { - Sig: "MEUCIQDehVT40TKP3qZyU7pCqxp34rxkuZMYE5AXhAoLx57svgIgLRksBWXOje20+k+8x3ubfFs6ZqCgK5w7yyHLPzH/mpk=", + Sig: goodIntotoV001Sig, }, }, }, @@ -161,29 +182,38 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Kind: "dsse", Version: "0.0.1", }, - CanonicalizedBody: []byte(` - { - "apiVersion": "0.0.1", - "kind": "dsse", - "spec": { - "signatures": [ - { - "signature": "MEUCIHiah7zQLL9LK9m9/0JH3rHIaYlvcus4h84KOdaR3iAlAiEAio+tnbpkW+V+FPYxpuiJBY0MuD43RVX5QmMwk3sgnUE=" - } - ] - } - } - `), + CanonicalizedBody: goodDSSEV001Body, }, env: &dsselib.Envelope{ Signatures: []dsselib.Signature{ { - Sig: "d3Jvbmcgc2lnCg==", + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), }, }, }, err: ErrorMismatchSignature, }, + { + name: "faiulure: dsse v0.0.1: unequal number of signatures", + tlog: &rekorpbv1.TransparencyLogEntry{ + KindVersion: &rekorpbv1.KindVersion{ + Kind: "dsse", + Version: "0.0.1", + }, + CanonicalizedBody: goodDSSEV001Body, + }, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + { + Sig: base64.StdEncoding.EncodeToString([]byte("othersig")), + }, + }, + }, + err: ErrorUnequalSignatures, + }, { name: "faiulure: intoto v0.0.2: mismatch signatures", tlog: &rekorpbv1.TransparencyLogEntry{ @@ -191,33 +221,19 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Kind: "intoto", Version: "0.0.2", }, - CanonicalizedBody: []byte(` - { - "apiVersion": "0.0.2", - "kind": "intoto", - "spec": { - "content": { - "envelope": { - "signatures": [ - { - "publicKey": "mypubkey", - "sig": "TUVVQ0lRRGVoVlQ0MFRLUDNxWnlVN3BDcXhwMzRyeGt1Wk1ZRTVBWGhBb0x4NTdzdmdJZ0xSa3NCV1hPamUyMCtrKzh4M3ViZkZzNlpxQ2dLNXc3eXlITFB6SC9tcGs9" - } - ] - } - } - } - } - `), + CanonicalizedBody: goodIntotoV002Body, }, env: &dsselib.Envelope{ Signatures: []dsselib.Signature{ { - Sig: "d3Jvbmcgc2lnCg==", + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + { + Sig: base64.StdEncoding.EncodeToString([]byte("othersig")), }, }, }, - err: ErrorMismatchSignature, + err: ErrorUnequalSignatures, }, { name: "failure: unknown type", @@ -227,7 +243,13 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Version: "0.0.x", }, }, - env: &dsselib.Envelope{}, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + }, + }, err: ErrorUnexpectedEntryType, }, { @@ -238,7 +260,13 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Version: "0.0.x", }, }, - env: &dsselib.Envelope{}, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + }, + }, err: ErrorUnexpectedEntryType, }, { @@ -249,9 +277,51 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { Version: "0.0.x", }, }, - env: &dsselib.Envelope{}, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + }, + }, err: ErrorUnexpectedEntryType, }, + { + name: "failure: parse error: dsse kind, intoto body", + tlog: &rekorpbv1.TransparencyLogEntry{ + KindVersion: &rekorpbv1.KindVersion{ + Kind: "dsse", + Version: "0.0.1", + }, + CanonicalizedBody: goodIntotoV002Body, + }, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + }, + }, + err: ErorrParsingEntryBody, + }, + { + name: "failure: parse error: intoto kind, dsse body", + tlog: &rekorpbv1.TransparencyLogEntry{ + KindVersion: &rekorpbv1.KindVersion{ + Kind: "intoto", + Version: "0.0.2", + }, + CanonicalizedBody: goodDSSEV001Body, + }, + env: &dsselib.Envelope{ + Signatures: []dsselib.Signature{ + { + Sig: base64.StdEncoding.EncodeToString([]byte("mysig")), + }, + }, + }, + err: ErorrParsingEntryBody, + }, } for _, tt := range tests { tt := tt // Re-initializing variable so it is not changed while executing the closure below @@ -260,8 +330,8 @@ func Test_matchRekorEntryWithEnvelope(t *testing.T) { err := matchRekorEntryWithEnvelope(tt.tlog, tt.env) - if !errCmp(err, tt.err) { - t.Errorf(cmp.Diff(err, tt.err)) + if errorDiff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); errorDiff != "" { + t.Errorf("unexpected error (-want +got):\n%s", errorDiff) } }) } diff --git a/verifiers/internal/gha/rekor.go b/verifiers/internal/gha/rekor.go index 4c3e17f..b911a95 100644 --- a/verifiers/internal/gha/rekor.go +++ b/verifiers/internal/gha/rekor.go @@ -267,7 +267,7 @@ func GetValidSignedAttestationWithCert(rClient *rekorGenClient.Rekor, } if len(resp.GetPayload()) != 1 { - return nil, fmt.Errorf("%w: %s", serrors.ErrorRekorSearch, "no matching rekor entries") + return nil, fmt.Errorf("%w: %s", serrors.ErrorRekorSearch, "f") } logEntry := resp.Payload[0]