Skip to content

Commit

Permalink
feat: Verify provenance by build type (#632)
Browse files Browse the repository at this point in the history
Fixes #473

Updates handling of provenance by providing implementations based on
[buildType](https://slsa.dev/provenance/v1#buildType) since this
determines how to interpret parameters and dependencies. This is done
because we need a way to interpret parameters not just based on the
predicateType. The 3 major build types with format differences are:
  - non-BYOB SLSA v0.2
  - BYOB SLSA v0.2
  - BYOB SLSA v1.0

---------

Signed-off-by: Ian Lewis <ianlewis@google.com>
  • Loading branch information
ianlewis authored Jun 16, 2023
1 parent 7aa6533 commit d2dc819
Show file tree
Hide file tree
Showing 14 changed files with 710 additions and 655 deletions.
5 changes: 3 additions & 2 deletions verifiers/internal/gha/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)

Expand Down Expand Up @@ -114,7 +115,7 @@ func extractAttestations(attestations []attestation) (*attestation, *attestation
for i := range attestations {
att := attestations[i]
// Provenance type verification.
if att.PredicateType == slsaprovenance.ProvenanceV02Type {
if att.PredicateType == common.ProvenanceV02Type {
provenanceAttestation = &att
}
// Publish type verification.
Expand Down Expand Up @@ -196,7 +197,7 @@ func (n *Npm) verifyPublishAttesttationSignature() error {

func (n *Npm) verifyIntotoHeaders() error {
if err := verifyIntotoTypes(n.verifiedProvenanceAtt,
slsaprovenance.ProvenanceV02Type, intoto.PayloadType, false); err != nil {
common.ProvenanceV02Type, intoto.PayloadType, false); err != nil {
return err
}
if err := verifyIntotoTypes(n.verifiedPublishAtt,
Expand Down
8 changes: 4 additions & 4 deletions verifiers/internal/gha/npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
intoto "github.com/in-toto/in-toto-golang/in_toto"
dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
)

func Test_verifyName(t *testing.T) {
Expand Down Expand Up @@ -671,7 +671,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
}{
{
name: "prov correct",
predicateType: slsaprovenance.ProvenanceV02Type,
predicateType: common.ProvenanceV02Type,
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
Expand All @@ -682,7 +682,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
},
{
name: "prov mismatch payload type",
predicateType: slsaprovenance.ProvenanceV02Type,
predicateType: common.ProvenanceV02Type,
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
Expand All @@ -694,7 +694,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
},
{
name: "prov mismatch predicate type",
predicateType: slsaprovenance.ProvenanceV02Type + "a",
predicateType: common.ProvenanceV02Type + "a",
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
Expand Down
34 changes: 19 additions & 15 deletions verifiers/internal/gha/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ import (
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/options"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"

// Load provenance types.

_ "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)

// SignedAttestation contains a signed DSSE envelope
Expand All @@ -34,6 +32,7 @@ type SignedAttestation struct {
RekorEntry *models.LogEntryAnon
}

// EnvelopeFromBytes reads a DSSE envelope from the given payload.
func EnvelopeFromBytes(payload []byte) (env *dsselib.Envelope, err error) {
env = &dsselib.Envelope{}
err = json.Unmarshal(payload, env)
Expand All @@ -43,7 +42,7 @@ func EnvelopeFromBytes(payload []byte) (env *dsselib.Envelope, err error) {
// Verify Builder ID in provenance statement.
// This function does an exact comparison, and expects expectedBuilderID to be the full
// `name@refs/tags/<name>`.
func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
func verifyBuilderIDExactMatch(prov iface.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
Expand All @@ -62,7 +61,7 @@ func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, expectedBuilderID
// Verify Builder ID in provenance statement.
// This function verifies the names match. If the expected builder ID contains a version,
// it also verifies the versions match.
func verifyBuilderIDLooseMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
func verifyBuilderIDLooseMatch(prov iface.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
Expand Down Expand Up @@ -91,7 +90,7 @@ func asURI(s string) string {
}

// Verify source URI in provenance statement.
func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
source := asURI(expectedSourceURI)

// We expect github.com URIs only.
Expand Down Expand Up @@ -172,7 +171,7 @@ func sourceFromURI(uri string, allowNoRef bool) (string, error) {
}

// Verify Subject Digest from the provenance statement.
func verifyDigest(prov slsaprovenance.Provenance, expectedHash string) error {
func verifyDigest(prov iface.Provenance, expectedHash string) error {
subjects, err := prov.Subjects()
if err != nil {
return err
Expand Down Expand Up @@ -221,6 +220,7 @@ func VerifyProvenanceSignature(ctx context.Context, trustedRoot *TrustedRoot,
provenance, rClient, trustedRoot)
}

// VerifyNpmPackageProvenance verifies provenance for an npm package.
func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentity,
provenanceOpts *options.ProvenanceOpts, isTrustedBuilder bool,
) error {
Expand Down Expand Up @@ -282,7 +282,7 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentit
return nil
}

func isValidDelegatorBuilderID(prov slsaprovenance.Provenance) error {
func isValidDelegatorBuilderID(prov iface.Provenance) error {
// Verify the TRW was referenced at a proper tag by the user.
id, err := prov.BuilderID()
if err != nil {
Expand All @@ -295,6 +295,7 @@ func isValidDelegatorBuilderID(prov slsaprovenance.Provenance) error {
return utils.IsValidBuilderTag(parts[1], false)
}

// VerifyProvenance verifies the provenance for the given DSSE envelope.
func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, byob bool,
) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(env)
Expand Down Expand Up @@ -322,7 +323,8 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO
return VerifyProvenanceCommonOptions(prov, provenanceOpts, false)
}

func VerifyProvenanceCommonOptions(prov slsaprovenance.Provenance, provenanceOpts *options.ProvenanceOpts,
// VerifyProvenanceCommonOptions verifies the given provenance.
func VerifyProvenanceCommonOptions(prov iface.Provenance, provenanceOpts *options.ProvenanceOpts,
allowNoMaterialRef bool,
) error {
// Verify source.
Expand Down Expand Up @@ -366,15 +368,17 @@ func VerifyProvenanceCommonOptions(prov slsaprovenance.Provenance, provenanceOpt
return nil
}

func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]string) error {
// VerifyWorkflowInputs verifies that the workflow inputs in the provenance
// match the expected values.
func VerifyWorkflowInputs(prov iface.Provenance, inputs map[string]string) error {
pyldInputs, err := prov.GetWorkflowInputs()
if err != nil {
return err
}

// Verify all inputs.
for k, v := range inputs {
value, err := slsaprovenance.GetAsString(pyldInputs, k)
value, err := common.GetAsString(pyldInputs, k)
if err != nil {
return fmt.Errorf("%w: cannot retrieve value of '%s'", serrors.ErrorMismatchWorkflowInputs, k)
}
Expand All @@ -390,7 +394,7 @@ func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]stri

// VerifyBranch verifies that the source branch in the provenance matches the
// expected value.
func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error {
func VerifyBranch(prov iface.Provenance, expectedBranch string) error {
ref, err := prov.GetBranch()
if err != nil {
return err
Expand All @@ -410,7 +414,7 @@ func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error {

// VerifyTag verifies that the source tag in the provenance matches the
// expected value.
func VerifyTag(prov slsaprovenance.Provenance, expectedTag string) error {
func VerifyTag(prov iface.Provenance, expectedTag string) error {
ref, err := prov.GetTag()
if err != nil {
return err
Expand All @@ -430,7 +434,7 @@ func VerifyTag(prov slsaprovenance.Provenance, expectedTag string) error {

// VerifyVersionedTag verifies that the source tag in the provenance matches the
// expected semver value.
func VerifyVersionedTag(prov slsaprovenance.Provenance, expectedTag string) error {
func VerifyVersionedTag(prov iface.Provenance, expectedTag string) error {
// Retrieve, validate and canonicalize the provenance tag.
// Note: prerelease is validated as part of patch validation
// and must be equal. Build is discarded as per https://semver.org/:
Expand Down
63 changes: 33 additions & 30 deletions verifiers/internal/gha/provenance_forgeable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import (
"strings"

serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance"

// Load provenance types.
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
slsav02 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2"
_ "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)

func verifyProvenanceMatchesCertificate(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyProvenanceMatchesCertificate(prov iface.Provenance, workflow *WorkflowIdentity) error {
// See the generation at https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/provenance.js.
// Verify systemParameters.
if err := verifySystemParameters(prov, workflow); err != nil {
Expand Down Expand Up @@ -65,7 +63,7 @@ func verifyProvenanceMatchesCertificate(prov slsaprovenance.Provenance, workflow
return nil
}

func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string) error {
func verifySubjectDigestName(prov iface.Provenance, digestName string) error {
subjects, err := prov.Subjects()
if err != nil {
return err
Expand All @@ -84,7 +82,7 @@ func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string)
return nil
}

func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyBuildConfig(prov iface.Provenance, workflow *WorkflowIdentity) error {
triggerPath, err := prov.GetBuildTriggerPath()
if err != nil {
// If the field is not available in the provenance,
Expand All @@ -98,7 +96,7 @@ func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentit
return equalCertificateValue(workflow.BuildConfigPath, triggerPath, "trigger workflow")
}

func verifyResolvedDependencies(prov slsaprovenance.Provenance) error {
func verifyResolvedDependencies(prov iface.Provenance) error {
n, err := prov.GetNumberResolvedDependencies()
if err != nil {
return err
Expand All @@ -110,7 +108,7 @@ func verifyResolvedDependencies(prov slsaprovenance.Provenance) error {
return nil
}

func verifyMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyMetadata(prov iface.Provenance, workflow *WorkflowIdentity) error {
if err := verifyCommonMetadata(prov, workflow); err != nil {
return err
}
Expand All @@ -125,7 +123,7 @@ func verifyMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity)
return nil
}

func verifyCommonMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyCommonMetadata(prov iface.Provenance, workflow *WorkflowIdentity) error {
// Verify build invocation ID.
invocationID, err := prov.GetBuildInvocationID()
if err != nil {
Expand Down Expand Up @@ -169,7 +167,7 @@ func verifyCommonMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIden
return nil
}

func verifyV02Metadata(prov slsaprovenance.Provenance) error {
func verifyV02Metadata(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
/*
v0.2:
Expand All @@ -181,21 +179,23 @@ func verifyV02Metadata(prov slsaprovenance.Provenance) error {
},
"reproducible": false
*/
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
if prov02.Predicate.Metadata == nil {
predicate := prov02.Predicate()

if predicate.Metadata == nil {
return nil
}

if prov02.Predicate.Metadata.Reproducible {
if predicate.Metadata.Reproducible {
return fmt.Errorf("%w: reproducible: %v",
serrors.ErrorNonVerifiableClaim,
prov02.Predicate.Metadata.Reproducible)
predicate.Metadata.Reproducible)
}

completeness := prov02.Predicate.Metadata.Completeness
completeness := predicate.Metadata.Completeness
if completeness.Parameters || completeness.Materials ||
completeness.Environment {
return fmt.Errorf("%w: completeness: %v",
Expand All @@ -205,44 +205,47 @@ func verifyV02Metadata(prov slsaprovenance.Provenance) error {
return nil
}

func verifyV02Parameters(prov slsaprovenance.Provenance) error {
func verifyV02Parameters(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
if prov02.Predicate.Invocation.Parameters == nil {
predicate := prov02.Predicate()

if predicate.Invocation.Parameters == nil {
return nil
}
m, ok := prov02.Predicate.Invocation.Parameters.(map[string]any)
m, ok := predicate.Invocation.Parameters.(map[string]any)
if !ok || len(m) > 0 {
return fmt.Errorf("%w: parameters: %v",
serrors.ErrorNonVerifiableClaim, prov02.Predicate.Invocation.Parameters)
serrors.ErrorNonVerifiableClaim, predicate.Invocation.Parameters)
}

return nil
}

func verifyV02BuildConfig(prov slsaprovenance.Provenance) error {
func verifyV02BuildConfig(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
predicate := prov02.Predicate()

if prov02.Predicate.BuildConfig == nil {
if predicate.BuildConfig == nil {
return nil
}
m, ok := prov02.Predicate.BuildConfig.(map[string]any)
m, ok := predicate.BuildConfig.(map[string]any)
if !ok || len(m) > 0 {
return fmt.Errorf("%w: buildConfig: %v",
serrors.ErrorNonVerifiableClaim, prov02.Predicate.BuildConfig)
serrors.ErrorNonVerifiableClaim, predicate.BuildConfig)
}

return nil
}

func verifySystemParameters(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifySystemParameters(prov iface.Provenance, workflow *WorkflowIdentity) error {
/*
"environment": {
"GITHUB_EVENT_NAME": "workflow_dispatch",
Expand Down Expand Up @@ -338,7 +341,7 @@ func getRunIDs(workflow *WorkflowIdentity) (string, string, error) {

func verifySystemRun(params map[string]any, workflow *WorkflowIdentity) error {
// Verify only if the values are provided in the provenance.
if !slsaprovenance.Exists(params, "GITHUB_RUN_ID") && !slsaprovenance.Exists(params, "GITHUB_RUN_ATTEMPT") {
if !common.Exists(params, "GITHUB_RUN_ID") && !common.Exists(params, "GITHUB_RUN_ATTEMPT") {
return nil
}
// The certificate contains runID as '4757060009/attempts/1'.
Expand All @@ -364,11 +367,11 @@ func verifySystemRun(params map[string]any, workflow *WorkflowIdentity) error {

func verifySystemParameter(params map[string]any, name string, certValue *string) error {
// If the provenance does not contain an env variable.
if !slsaprovenance.Exists(params, name) {
if !common.Exists(params, name) {
return nil
}
// Provenance contains the field, we must verify it.
provValue, err := slsaprovenance.GetAsString(params, name)
provValue, err := common.GetAsString(params, name)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit d2dc819

Please sign in to comment.