feat: handle dssev001 tlog entry types (#799)

re: https://github.com/slsa-framework/slsa-github-generator/issues/3750

Rekor TLog entries can now be of the type dsse v0.0.1, as when what's
returned when using sigstore-go's `Bundle()`.

This is to support eventual Sigstore Bundles produced by
slsa-github-generator's "generic" generator, which will likely use
sigstore-go's Bundle to produce attestations

-
https://github.com/slsa-framework/slsa-github-generator/compare/main...ramonpetgrave64-internal-builder-sigstore-bundlev2#diff-b186a0c5d9ae459b11b694f05455568453699670926d21cad06cafec3dbf895eR101
-
https://github.com/slsa-framework/slsa-github-generator/actions/runs/10359750833

## Tesing

- Added unit tests with stub data
- manual invocations to very both new and old attestations and bundles,
with some modifications for testing purposes
-
https://github.com/slsa-framework/slsa-verifier/compare/main...verify-sigstore-go-Bundlev3#diff-94741068472ee694a12811cd704179dd478a9fa20a3bf45cf6ea2d4406214dc2R179

## Followup

Finish the work to produce bundles from the generic generators
-
https://github.com/slsa-framework/slsa-github-generator/compare/main...ramonpetgrave64-internal-builder-sigstore-bundlev2#diff-b186a0c5d9ae459b11b694f05455568453699670926d21cad06cafec3dbf895eR101

---------

Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
Signed-off-by: Ramon Petgrave <32398091+ramonpetgrave64@users.noreply.github.com>
This commit is contained in:
Ramon Petgrave
2024-08-24 03:31:43 +00:00
committed by GitHub
parent b92dabfb1c
commit 767ecf9e0a
2 changed files with 327 additions and 16 deletions
+67 -16
View File
@@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse"
bundle_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
@@ -20,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")
ErrorParsingEntryBody = 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")
)
@@ -110,29 +114,41 @@ 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 {
kindVersion := tlogEntry.GetKindVersion()
if kindVersion.Kind != "intoto" &&
kindVersion.Version != "0.0.2" {
return fmt.Errorf("%w: expected intoto:0.0.2, got %s:%s", ErrorUnexpectedEntryType,
kindVersion.Kind, kindVersion.Version)
if len(env.Signatures) == 0 {
return ErrorNoSignatures
}
kindVersion := tlogEntry.GetKindVersion()
if kindVersion.Kind == "intoto" && kindVersion.Version == "0.0.2" {
return matchRekorEntryWithEnvelopeIntotov002(tlogEntry, env)
}
if kindVersion.Kind == "dsse" && kindVersion.Version == "0.0.1" {
return matchRekorEntryWithEnvelopeDSSEv001(tlogEntry, env)
}
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.
func matchRekorEntryWithEnvelopeIntotov002(tlogEntry *v1.TransparencyLogEntry, env *dsselib.Envelope) error {
canonicalBody := tlogEntry.GetCanonicalizedBody()
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", ErrorParsingEntryBody, err)
}
specMarshal, err := json.Marshal(toto.Spec)
if err != nil {
return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err)
return fmt.Errorf("%w: %s", ErrorParsingEntryBody, err)
}
if err := json.Unmarshal(specMarshal, &intotoObj); err != nil {
return fmt.Errorf("%w: %s", ErrorUnexpectedEntryType, err)
return fmt.Errorf("%w: %s", ErrorParsingEntryBody, 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))
}
@@ -142,13 +158,12 @@ func matchRekorEntryWithEnvelope(tlogEntry *v1.TransparencyLogEntry, env *dsseli
// The signature in the canonical body is double base64-encoded.
encodedEnvSig := base64.StdEncoding.EncodeToString(
[]byte(sig.Sig))
var matchCanonical bool
for _, canonicalSig := range intotoObj.Content.Envelope.Signatures {
if canonicalSig.Sig.String() == encodedEnvSig {
matchCanonical = true
}
}
if !matchCanonical {
if !slices.ContainsFunc(
intotoObj.Content.Envelope.Signatures,
func(canonicalSig *models.IntotoV002SchemaContentEnvelopeSignaturesItems0) bool {
return canonicalSig.Sig.String() == encodedEnvSig
},
) {
return ErrorMismatchSignature
}
}
@@ -156,6 +171,42 @@ func matchRekorEntryWithEnvelope(tlogEntry *v1.TransparencyLogEntry, env *dsseli
return nil
}
// matchRekorEntryWithEnvelopeDSSEv001 handles matchRekorEntryWithEnvelope for the dsse v0.0.1 type version.
func matchRekorEntryWithEnvelopeDSSEv001(tlogEntry *v1.TransparencyLogEntry, env *dsselib.Envelope) error {
canonicalBody := tlogEntry.GetCanonicalizedBody()
var dsseObj models.DSSE
if err := json.Unmarshal(canonicalBody, &dsseObj); err != nil {
return fmt.Errorf("%w: %s", ErrorParsingEntryBody, err)
}
var dsseSchemaObj models.DSSEV001Schema
specMarshal, err := json.Marshal(dsseObj.Spec)
if err != nil {
return fmt.Errorf("%w: %s", ErrorParsingEntryBody, err)
}
if err := json.Unmarshal(specMarshal, &dsseSchemaObj); err != nil {
return fmt.Errorf("%w: %s", ErrorParsingEntryBody, err)
}
if len(env.Signatures) != len(dsseSchemaObj.Signatures) {
return fmt.Errorf("%w: wanted %d, got %d", ErrorUnequalSignatures,
len(env.Signatures),
len(dsseSchemaObj.Signatures))
}
// TODO(#487): verify the certs match.
for _, sig := range env.Signatures {
if !slices.ContainsFunc(
dsseSchemaObj.Signatures,
func(canonicalSig *models.DSSEV001SchemaSignaturesItems0) bool {
return *canonicalSig.Signature == sig.Sig
},
) {
return ErrorMismatchSignature
}
}
return nil
}
// VerifyProvenanceBundle verifies the DSSE envelope using the offline Rekor bundle and
// returns the verified DSSE envelope containing the provenance
// and the signing certificate given the provenance.
+260
View File
@@ -2,12 +2,17 @@ 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"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
@@ -76,3 +81,258 @@ 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{
KindVersion: &rekorpbv1.KindVersion{
Kind: "dsse",
Version: "0.0.1",
},
CanonicalizedBody: goodDSSEV001Body,
},
env: &dsselib.Envelope{
Signatures: []dsselib.Signature{
{
Sig: goodDSSEV001Sig,
},
},
},
err: nil,
},
{
name: "success: intoto v0.0.2",
tlog: &rekorpbv1.TransparencyLogEntry{
KindVersion: &rekorpbv1.KindVersion{
Kind: "intoto",
Version: "0.0.2",
},
CanonicalizedBody: goodIntotoV002Body,
},
env: &dsselib.Envelope{
Signatures: []dsselib.Signature{
{
Sig: goodIntotoV001Sig,
},
},
},
err: nil,
},
{
name: "faiulure: dsse v0.0.1: mismatch 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")),
},
},
},
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{
KindVersion: &rekorpbv1.KindVersion{
Kind: "intoto",
Version: "0.0.2",
},
CanonicalizedBody: goodIntotoV002Body,
},
env: &dsselib.Envelope{
Signatures: []dsselib.Signature{
{
Sig: base64.StdEncoding.EncodeToString([]byte("mysig")),
},
{
Sig: base64.StdEncoding.EncodeToString([]byte("othersig")),
},
},
},
err: ErrorUnequalSignatures,
},
{
name: "failure: unknown type",
tlog: &rekorpbv1.TransparencyLogEntry{
KindVersion: &rekorpbv1.KindVersion{
Kind: "slsa",
Version: "0.0.x",
},
},
env: &dsselib.Envelope{
Signatures: []dsselib.Signature{
{
Sig: base64.StdEncoding.EncodeToString([]byte("mysig")),
},
},
},
err: ErrorUnexpectedEntryType,
},
{
name: "failure: unknown dsse type version",
tlog: &rekorpbv1.TransparencyLogEntry{
KindVersion: &rekorpbv1.KindVersion{
Kind: "dsse",
Version: "0.0.x",
},
},
env: &dsselib.Envelope{
Signatures: []dsselib.Signature{
{
Sig: base64.StdEncoding.EncodeToString([]byte("mysig")),
},
},
},
err: ErrorUnexpectedEntryType,
},
{
name: "failure: unknown intoto type version",
tlog: &rekorpbv1.TransparencyLogEntry{
KindVersion: &rekorpbv1.KindVersion{
Kind: "dsse",
Version: "0.0.x",
},
},
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: ErrorParsingEntryBody,
},
{
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: ErrorParsingEntryBody,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := matchRekorEntryWithEnvelope(tt.tlog, tt.env)
if errorDiff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); errorDiff != "" {
t.Errorf("unexpected error (-want +got):\n%s", errorDiff)
}
})
}
}