From 3a4e9924447060356fd37967fc5cb34b4ab19187 Mon Sep 17 00:00:00 2001 From: laurentsimon <64505099+laurentsimon@users.noreply.github.com> Date: Tue, 9 May 2023 16:52:36 -0700 Subject: [PATCH] feat: verify claims in provenance match the certificate (#572) * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon --------- Signed-off-by: laurentsimon --- .golangci.yml | 1 - Makefile | 2 +- errors/errors.go | 3 + go.mod | 2 +- verifiers/internal/gha/builder.go | 315 ++++- verifiers/internal/gha/builder_test.go | 677 +++++++-- verifiers/internal/gha/bundle_test.go | 2 +- verifiers/internal/gha/npm.go | 13 +- verifiers/internal/gha/npm_test.go | 8 +- verifiers/internal/gha/provenance.go | 59 +- .../internal/gha/provenance_forgeable.go | 382 +++++ .../internal/gha/provenance_forgeable_test.go | 1232 +++++++++++++++++ verifiers/internal/gha/provenance_test.go | 71 +- .../internal/gha/slsaprovenance/common.go | 5 + .../gha/slsaprovenance/slsaprovenance.go | 27 +- .../gha/slsaprovenance/v0.2/provenance.go | 38 + .../gha/slsaprovenance/v1.0/provenance.go | 51 + .../testdata/dsse-branch3-ref-v1.intoto.jsonl | 11 +- .../dsse-no-subject-hash-v1.intoto.jsonl | 11 +- .../testdata/dsse-no-subject-v1.intoto.jsonl | 11 +- .../dsse-valid-multi-subjects-v1.intoto.jsonl | 11 +- .../gha/testdata/dsse-valid-v1.intoto.jsonl | 11 +- verifiers/internal/gha/trusted_root.go | 20 +- verifiers/internal/gha/verifier.go | 50 +- 24 files changed, 2807 insertions(+), 206 deletions(-) create mode 100644 verifiers/internal/gha/provenance_forgeable.go create mode 100644 verifiers/internal/gha/provenance_forgeable_test.go diff --git a/.golangci.yml b/.golangci.yml index ab2d03664..8480a9dc7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,7 +21,6 @@ linters: disable-all: true enable: - asciicheck - - deadcode - depguard - dogsled # TODO(https://github.com/slsa-framework/slsa-verifier/issues/363): Restore linter diff --git a/Makefile b/Makefile index c2c23ac76..bf70c1e6c 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ unit-test: ## Runs all unit tests. regression-test: ## Runs all regression and unit tests. go mod vendor # NOTE: go test builds packages even if there are no tests. - go test -mod=vendor -tags=regression -v -timeout=20m ./... + go test -mod=vendor -tags=regression -v -timeout=25m ./... ## Linters ##################################################################### diff --git a/errors/errors.go b/errors/errors.go index 403004a8c..eda57be28 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -12,12 +12,15 @@ var ( ErrorMismatchSource = errors.New("source used to generate the binary does not match provenance") ErrorMismatchWorkflowInputs = errors.New("workflow input does not match") ErrorMalformedURI = errors.New("URI is malformed") + ErrorMismatchCertificate = errors.New("certificate and provenance mismatch") + ErrorInvalidCertificate = errors.New("invalid certificate") ErrorMismatchTag = errors.New("tag used to generate the binary does not match provenance") ErrorInvalidRecipe = errors.New("the recipe is invalid") ErrorMismatchVersionedTag = errors.New("tag used to generate the binary does not match provenance") ErrorInvalidSemver = errors.New("invalid semantic version") ErrorRekorSearch = errors.New("error searching rekor entries") ErrorMismatchHash = errors.New("artifact hash does not match provenance subject") + ErrorNonVerifiableClaim = errors.New("provenance claim cannot be verified") ErrorMismatchIntoto = errors.New("verified intoto provenance does not match text provenance") ErrorInvalidRef = errors.New("invalid ref") ErrorUntrustedReusableWorkflow = errors.New("untrusted reusable workflow") diff --git a/go.mod b/go.mod index 836bd5f43..cb6302070 100644 --- a/go.mod +++ b/go.mod @@ -147,7 +147,7 @@ require ( github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/sigstore/fulcio v1.2.0 // indirect + github.com/sigstore/fulcio v1.2.0 github.com/sigstore/protobuf-specs v0.1.1-0.20230503063121-91485b44360d github.com/sirupsen/logrus v1.9.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect diff --git a/verifiers/internal/gha/builder.go b/verifiers/internal/gha/builder.go index fa6f8b741..8d0fa815e 100644 --- a/verifiers/internal/gha/builder.go +++ b/verifiers/internal/gha/builder.go @@ -2,12 +2,13 @@ package gha import ( "crypto/x509" - "errors" + "encoding/asn1" "fmt" "strings" "golang.org/x/mod/semver" + fulcio "github.com/sigstore/fulcio/pkg/certificate" 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/utils" @@ -34,8 +35,10 @@ var defaultContainerTrustedReusableWorkflows = map[string]bool{ trustedBuilderRepository + "/.github/workflows/generator_container_slsa3.yml": true, } -var delegatorGenericReusableWorkflow = trustedBuilderRepository + "/.github/workflows/delegator_generic_slsa3.yml" -var delegatorLowPermsGenericReusableWorkflow = trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml" +var ( + delegatorGenericReusableWorkflow = trustedBuilderRepository + "/.github/workflows/delegator_generic_slsa3.yml" + delegatorLowPermsGenericReusableWorkflow = trustedBuilderRepository + "/.github/workflows/delegator_lowperms-generic_slsa3.yml" +) var defaultBYOBReusableWorkflows = map[string]bool{ delegatorGenericReusableWorkflow: true, @@ -50,9 +53,9 @@ func VerifyCertficateSourceRepository(id *WorkflowIdentity, // {org}/{repository}. expectedSource := strings.TrimPrefix(sourceRepo, "git+https://") expectedSource = strings.TrimPrefix(expectedSource, githubCom) - if id.CallerRepository != expectedSource { + if id.SourceRepository != expectedSource { return fmt.Errorf("%w: expected source '%s', got '%s'", serrors.ErrorMismatchSource, - expectedSource, id.CallerRepository) + expectedSource, id.SourceRepository) } return nil } @@ -72,9 +75,9 @@ func VerifyBuilderIdentity(id *WorkflowIdentity, } // cert URI path is /org/repo/path/to/workflow@ref - workflowPath := strings.SplitN(id.JobWobWorkflowRef, "@", 2) + workflowPath := strings.SplitN(id.SubjectWorkflowRef, "@", 2) if len(workflowPath) < 2 { - return nil, fmt.Errorf("%w: workflow uri: %s", serrors.ErrorMalformedURI, id.JobWobWorkflowRef) + return nil, fmt.Errorf("%w: workflow uri: %s", serrors.ErrorMalformedURI, id.SubjectWorkflowRef) } // Verify trusted workflow. @@ -135,8 +138,8 @@ func verifyTrustedBuilderID(certPath, certTag string, expectedBuilderID *string, // This lets us use the pre-build builder binary generated during release (release happen at main). // For other projects, we only allow semantic versions that map to a release. func verifyTrustedBuilderRef(id *WorkflowIdentity, ref string) error { - if (id.CallerRepository == trustedBuilderRepository || - id.CallerRepository == e2eTestRepository) && + if (id.SourceRepository == trustedBuilderRepository || + id.SourceRepository == e2eTestRepository) && options.TestingEnabled() { // Allow verification on the main branch to support e2e tests. if ref == "refs/heads/main" { @@ -177,39 +180,295 @@ func verifyTrustedBuilderRef(id *WorkflowIdentity, ref string) error { return nil } -func getExtension(cert *x509.Certificate, oid string) string { +func getExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier, encoded bool) (string, error) { for _, ext := range cert.Extensions { - if strings.Contains(ext.Id.String(), oid) { - return string(ext.Value) + if !ext.Id.Equal(oid) { + continue + } + if !encoded { + return string(ext.Value), nil + } + + // Decode first. + var decoded string + rest, err := asn1.Unmarshal(ext.Value, &decoded) + if err != nil { + return "", fmt.Errorf("%w", err) + } + if len(rest) != 0 { + return "", fmt.Errorf("decoding has rest for oid %v", oid) } + return decoded, nil } - return "" + return "", nil } +type Hosted int + +const ( + HostedSelf Hosted = iota + HostedGitHub +) + +// See https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md. type WorkflowIdentity struct { - // The caller repository - CallerRepository string `json:"caller"` - // The commit SHA where the workflow was triggered - CallerHash string `json:"commit"` - // Current workflow (reuseable workflow) ref - JobWobWorkflowRef string `json:"job_workflow_ref"` - // Trigger - Trigger string `json:"trigger"` + // The source repository + SourceRepository string + // The commit SHA where the workflow was triggered. + SourceSha1 string + // Ref of the source. + SourceRef *string + // ID of the source repository. + SourceID *string + // Source owner ID of repository. + SourceOwnerID *string + + // Workflow path OIDC subject - ref of reuseable workflow or trigger workflow. + SubjectWorkflowRef string + // Subject commit sha1. + SubjectSha1 *string + // Hosted status of the subject. + SubjectHosted *Hosted + + // BuildTrigger + BuildTrigger string + // Build config path, i.e. the trigger workflow. + BuildConfigPath *string + + // Run ID + RunID *string // Issuer - Issuer string `json:"issuer"` + Issuer string +} + +func getHosted(cert *x509.Certificate) (*Hosted, error) { + runnerEnv, err := getExtension(cert, fulcio.OIDRunnerEnvironment, true) + if err != nil { + return nil, err + } + if runnerEnv == "github-hosted" { + r := HostedGitHub + return &r, nil + } + if runnerEnv == "self-hosted" { + r := HostedSelf + return &r, nil + } + return nil, nil +} + +func validateClaimsEqual(deprecated, existing string) error { + if deprecated != "" && existing != "" && deprecated != existing { + return fmt.Errorf("%w: '%v' != '%v'", serrors.ErrorInvalidFormat, deprecated, existing) + } + if deprecated == "" && existing == "" { + return fmt.Errorf("%w: claims are empty", serrors.ErrorInvalidFormat) + } + return nil +} + +func getAndValidateEqualClaims(cert *x509.Certificate, deprecatedOid, oid asn1.ObjectIdentifier) (string, error) { + deprecatedValue, err := getExtension(cert, deprecatedOid, false) + if err != nil { + return "", err + } + value, err := getExtension(cert, oid, true) + if err != nil { + return "", err + } + if err := validateClaimsEqual(deprecatedValue, value); err != nil { + return "", err + } + // New certificates. + if value != "" { + return value, nil + } + // Old certificates. + if deprecatedValue != "" { + return deprecatedValue, nil + } + // Both values are empty. + return "", fmt.Errorf("%w: empty fields %v and %v", serrors.ErrorInvalidCertificate, + deprecatedOid, oid) } // GetWorkflowFromCertificate gets the workflow identity from the Fulcio authenticated content. +// See https://github.com/sigstore/fulcio/blob/e763d76e3f7786b52db4b27ab87dc446da24895a/pkg/certificate/extensions.go. +// https://github.com/golangci/golangci-lint/issues/741#issuecomment-784171870. +// +//nolint:staticcheck // we want to disable SA1019 only to use deprecated methods but there is a bug in golangci-lint. func GetWorkflowInfoFromCertificate(cert *x509.Certificate) (*WorkflowIdentity, error) { if len(cert.URIs) == 0 { - return nil, errors.New("missing URI information from certificate") + return nil, fmt.Errorf("%w: missing URI information from certificate", serrors.ErrorInvalidFormat) + } + + // 1.3.6.1.4.1.57264.1.2: DEPRECATED. + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726412--github-workflow-BuildTrigger-deprecated + // 1.3.6.1.4.1.57264.1.20 | Build Trigger + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264120--build-trigger + buildTrigger, err := getAndValidateEqualClaims(cert, fulcio.OIDGitHubWorkflowTrigger, fulcio.OIDBuildTrigger) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.3: DEPRECATED. + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726413--github-workflow-sha-deprecated + // 1.3.6.1.4.1.57264.1.13 | Source Repository Digest + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264113--source-repository-digest + sourceSha1, err := getAndValidateEqualClaims(cert, fulcio.OIDGitHubWorkflowSHA, fulcio.OIDSourceRepositoryDigest) + if err != nil { + return nil, err + } + // 1.3.6.1.4.1.57264.1.19 | Build Config Digest + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264119--build-config-digest + buildConfigSha1, err := getExtension(cert, fulcio.OIDBuildConfigDigest, true) + if err != nil { + return nil, err + } + if err := validateClaimsEqual(sourceSha1, buildConfigSha1); err != nil { + return nil, err + } + + // IssuerV1: 1.3.6.1.4.1.57264.1.1 + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726411--issuer + // IssuerV2: 1.3.6.1.4.1.57264.1.8 + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726418--issuer-v2 + issuer, err := getAndValidateEqualClaims(cert, fulcio.OIDIssuer, fulcio.OIDIssuerV2) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.5: DEPRECATED. + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726415--github-workflow-repository-deprecated + deprecatedSourceRepository, err := getExtension(cert, fulcio.OIDGitHubWorkflowRepository, false) + if err != nil { + return nil, err + } + // 1.3.6.1.4.1.57264.1.12 | Source Repository URI + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264112--source-repository-uri + sourceURI, err := getExtension(cert, fulcio.OIDSourceRepositoryURI, true) + if err != nil { + return nil, err + } + if deprecatedSourceRepository != "" && sourceURI != "" && + "https://github.com/"+deprecatedSourceRepository != sourceURI { + return nil, fmt.Errorf("%w: '%v' != '%v'", + serrors.ErrorInvalidFormat, "https://github.com/"+deprecatedSourceRepository, sourceURI) + } + sourceRepository := strings.TrimPrefix(sourceURI, "https://github.com/") + // Handle old certifcates. + if sourceRepository == "" { + sourceRepository = deprecatedSourceRepository + } + + // 1.3.6.1.4.1.57264.1.10 | Build Signer Digest + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264110--build-signer-digest + subjectSha1, err := getExtension(cert, fulcio.OIDBuildSignerDigest, true) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.11 | Runner Environment + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264111--runner-environment + subjectHosted, err := getHosted(cert) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.14 | Source Repository Ref + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264114--source-repository-ref + sourceRef, err := getExtension(cert, fulcio.OIDSourceRepositoryRef, true) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.15 | Source Repository Identifier + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264115--source-repository-identifier + sourceID, err := getExtension(cert, fulcio.OIDSourceRepositoryIdentifier, true) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.17 | Source Repository Owner Identifier + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264117--source-repository-owner-identifier + sourceOwnerID, err := getExtension(cert, fulcio.OIDSourceRepositoryOwnerIdentifier, true) + if err != nil { + return nil, err + } + + // 1.3.6.1.4.1.57264.1.18 | Build Config URI + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264118--build-config-uri + var buildConfigPath string + buildConfigURI, err := getExtension(cert, fulcio.OIDBuildConfigURI, true) + if err != nil { + return nil, err + } + if buildConfigURI != "" { + parts := strings.Split(buildConfigURI, "@") + if len(parts) != 2 { + return nil, fmt.Errorf("%w: %v", + serrors.ErrorInvalidFormat, buildConfigURI) + } + prefix := fmt.Sprintf("https://github.com/%v/", sourceRepository) + if !strings.HasPrefix(parts[0], prefix) { + return nil, fmt.Errorf("%w: prefix: %v", + serrors.ErrorInvalidFormat, parts[0]) + } + buildConfigPath = strings.TrimPrefix(parts[0], prefix) + } + + // 1.3.6.1.4.1.57264.1.21 | Run Invocation URI + // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264121--run-invocation-uri + runURI, err := getExtension(cert, fulcio.OIDRunInvocationURI, true) + if err != nil { + return nil, err + } + runID := strings.TrimPrefix(runURI, fmt.Sprintf("https://github.com/%s/actions/runs/", sourceRepository)) + + // Subject path. + if !strings.HasPrefix(cert.URIs[0].Path, "/") { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidFormat, cert.URIs[0].Path) + } + // Remove the starting '/'. + subjectWorkflowRef := cert.URIs[0].Path[1:] + + var pSubjectSha1, pSourceID, pSourceRef, pSourceOwnerID, pBuildConfigPath, pRunID *string + if subjectSha1 != "" { + pSubjectSha1 = &subjectSha1 + } + if sourceID != "" { + pSourceID = &sourceID + } + if sourceRef != "" { + pSourceRef = &sourceRef + } + if sourceOwnerID != "" { + pSourceOwnerID = &sourceOwnerID + } + if buildConfigPath != "" { + pBuildConfigPath = &buildConfigPath + } + if runID != "" { + pRunID = &runID } return &WorkflowIdentity{ - CallerRepository: getExtension(cert, "1.3.6.1.4.1.57264.1.5"), - Issuer: getExtension(cert, "1.3.6.1.4.1.57264.1.1"), - Trigger: getExtension(cert, "1.3.6.1.4.1.57264.1.2"), - CallerHash: getExtension(cert, "1.3.6.1.4.1.57264.1.3"), - JobWobWorkflowRef: cert.URIs[0].Path, + // Issuer. + Issuer: issuer, + // Subject + SubjectWorkflowRef: subjectWorkflowRef, + SubjectSha1: pSubjectSha1, + SubjectHosted: subjectHosted, + // Source. + SourceRepository: sourceRepository, + SourceSha1: sourceSha1, + SourceRef: pSourceRef, + SourceID: pSourceID, + SourceOwnerID: pSourceOwnerID, + // Build. + BuildTrigger: buildTrigger, + BuildConfigPath: pBuildConfigPath, + // Other. + RunID: pRunID, }, nil } diff --git a/verifiers/internal/gha/builder_test.go b/verifiers/internal/gha/builder_test.go index 4c3abb380..b3ac030dd 100644 --- a/verifiers/internal/gha/builder_test.go +++ b/verifiers/internal/gha/builder_test.go @@ -1,11 +1,16 @@ package gha import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "net/url" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + fulcio "github.com/sigstore/fulcio/pkg/certificate" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" "github.com/slsa-framework/slsa-verifier/v2/options" ) @@ -23,11 +28,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "invalid job workflow ref", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: "random/workflow/ref", - Trigger: "workflow_dispatch", - Issuer: "https://token.actions.githubusercontent.com", + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: "random/workflow/ref", + BuildTrigger: "workflow_dispatch", + Issuer: "https://token.actions.githubusercontent.com", }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorMalformedURI, @@ -35,11 +40,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "untrusted job workflow ref", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: "/malicious/slsa-go/.github/workflows/builder.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: "https://token.actions.githubusercontent.com", + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: "/malicious/slsa-go/.github/workflows/builder.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: "https://token.actions.githubusercontent.com", }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorUntrustedReusableWorkflow, @@ -47,11 +52,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "untrusted job workflow ref for general repos", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/heads/main", - Trigger: "workflow_dispatch", - Issuer: "https://token.actions.githubusercontent.com", + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/heads/main", + BuildTrigger: "workflow_dispatch", + Issuer: "https://token.actions.githubusercontent.com", }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorInvalidRef, @@ -59,11 +64,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "untrusted cert issuer for general repos", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: "https://bad.issuer.com", + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: "https://bad.issuer.com", }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorInvalidOIDCIssuer, @@ -71,11 +76,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid trusted builder without tag", workflow: &WorkflowIdentity{ - CallerRepository: trustedBuilderRepository, - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: "https://token.actions.githubusercontent.com", + SourceRepository: trustedBuilderRepository, + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: "https://token.actions.githubusercontent.com", }, defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", @@ -83,11 +88,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid main ref for e2e test", workflow: &WorkflowIdentity{ - CallerRepository: e2eTestRepository, - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: e2eTestRepository, + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", @@ -95,11 +100,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid main ref for e2e test - match builderID", workflow: &WorkflowIdentity{ - CallerRepository: e2eTestRepository, - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: e2eTestRepository, + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), @@ -110,11 +115,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid main ref for e2e test - mismatch builderID", workflow: &WorkflowIdentity{ - CallerRepository: e2eTestRepository, - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: e2eTestRepository, + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), @@ -125,11 +130,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity - match builderID", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), @@ -140,11 +145,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity - mismatch builderID", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), @@ -155,11 +160,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "invalid workflow identity with prerelease", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3-alpha", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3-alpha", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, err: serrors.ErrorInvalidRef, defaults: defaultArtifactTrustedReusableWorkflows, @@ -168,11 +173,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "invalid workflow identity with build", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3+123", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3+123", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorInvalidRef, @@ -180,11 +185,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "invalid workflow identity with metadata", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3-alpha+123", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3-alpha+123", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, defaults: defaultArtifactTrustedReusableWorkflows, err: serrors.ErrorInvalidRef, @@ -192,11 +197,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity with fully qualified source", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", @@ -204,11 +209,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity with fully qualified source - no default", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), @@ -218,11 +223,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity with fully qualified source - match builderID", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), @@ -233,11 +238,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity with fully qualified source - mismatch builderID", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), @@ -248,11 +253,11 @@ func Test_VerifyBuilderIdentity(t *testing.T) { { name: "valid workflow identity with fully qualified source - mismatch defaults", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, defaults: defaultContainerTrustedReusableWorkflows, err: serrors.ErrorUntrustedReusableWorkflow, @@ -292,22 +297,22 @@ func Test_VerifyCertficateSourceRepository(t *testing.T) { { name: "repo match", workflow: &WorkflowIdentity{ - CallerRepository: "asraa/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "asraa/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, source: "github.com/asraa/slsa-on-github-test", }, { name: "unexpected source for e2e test", workflow: &WorkflowIdentity{ - CallerRepository: e2eTestRepository, - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: e2eTestRepository, + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, source: "malicious/source", err: serrors.ErrorMismatchSource, @@ -315,10 +320,10 @@ func Test_VerifyCertficateSourceRepository(t *testing.T) { { name: "valid main ref for builder", workflow: &WorkflowIdentity{ - CallerRepository: trustedBuilderRepository, - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: trustedBuilderRepository, + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, source: "malicious/source", err: serrors.ErrorMismatchSource, @@ -326,11 +331,11 @@ func Test_VerifyCertficateSourceRepository(t *testing.T) { { name: "unexpected source", workflow: &WorkflowIdentity{ - CallerRepository: "malicious/slsa-on-github-test", - CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", - JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", - Trigger: "workflow_dispatch", - Issuer: certOidcIssuer, + SourceRepository: "malicious/slsa-on-github-test", + SourceSha1: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + SubjectWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + BuildTrigger: "workflow_dispatch", + Issuer: certOidcIssuer, }, source: "asraa/slsa-on-github-test", err: serrors.ErrorMismatchSource, @@ -667,7 +672,7 @@ func Test_verifyTrustedBuilderRef(t *testing.T) { tt := tt // Re-initializing variable so it is not changed while executing the closure below t.Run(tt.name, func(t *testing.T) { wf := WorkflowIdentity{ - CallerRepository: tt.callerRepo, + SourceRepository: tt.callerRepo, } if tt.testingEnabled { @@ -684,3 +689,455 @@ func Test_verifyTrustedBuilderRef(t *testing.T) { }) } } + +func Test_GetWorkflowInfoFromCertificate(t *testing.T) { + t.Parallel() + // See https://github.com/sigstore/fulcio/blob/e763d76e3f7786b52db4b27ab87dc446da24895a/pkg/certificate/extensions.go. + trigger := "workflow_dispatch" + encodedTrigger, err := asn1.MarshalWithParams(trigger, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + repo := "org/repo" + encodedRepoURI, err := asn1.MarshalWithParams("https://github.com/"+repo, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + issuer := "the-issuer" + encodedIssuer, err := asn1.MarshalWithParams(issuer, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + digest := "abcdef" + encodedDigest, err := asn1.MarshalWithParams(digest, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + encodedHosted, err := asn1.MarshalWithParams("github-hosted", "utf8") + if err != nil { + t.Errorf(err.Error()) + } + hosted := HostedGitHub + ref := "refs/tags/v1.2.3" + encodedRef, err := asn1.MarshalWithParams(ref, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + sourceID := "12345" + encodedSourceID, err := asn1.MarshalWithParams(sourceID, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + sourceOwnerID := "12345" + encodedSourceOwnerID, err := asn1.MarshalWithParams(sourceOwnerID, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + + buildConfigSha1 := "abcdef" + encodedBuildConfigSha1, err := asn1.MarshalWithParams(buildConfigSha1, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + buildConfigPath := "path/to/workflow" + encodedBuildConfigURI, err := asn1.MarshalWithParams("https://github.com/"+repo+"/"+buildConfigPath+"@"+ref, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + + invocationID := "9207262" + encodedInvocationURI, err := asn1.MarshalWithParams("https://github.com/"+repo+"/actions/runs/"+invocationID, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + subjectSha1 := "subjectSha1" + encodedSubjectSha1, err := asn1.MarshalWithParams(subjectSha1, "utf8") + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + cert x509.Certificate + workflow WorkflowIdentity + err error + }{ + { + name: "old cert", + cert: x509.Certificate{ + URIs: []*url.URL{ + { + Path: "/" + repo + "/" + buildConfigPath, + }, + }, + Extensions: []pkix.Extension{ + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDIssuer, + Value: []byte(issuer), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowTrigger, + Value: []byte(trigger), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowSHA, + Value: []byte(digest), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowRepository, + Value: []byte(repo), + }, + }, + }, + workflow: WorkflowIdentity{ + Issuer: issuer, + SubjectWorkflowRef: repo + "/" + buildConfigPath, + SourceRepository: repo, + SourceSha1: digest, + BuildTrigger: trigger, + }, + }, + { + name: "old cert empty URIs", + cert: x509.Certificate{ + Extensions: []pkix.Extension{ + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDIssuer, + Value: []byte(issuer), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowTrigger, + Value: []byte(trigger), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowSHA, + Value: []byte(digest), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowRepository, + Value: []byte(repo), + }, + }, + }, + err: serrors.ErrorInvalidFormat, + }, + { + name: "new cert", + cert: x509.Certificate{ + URIs: []*url.URL{ + { + Path: "/" + repo + "/" + buildConfigPath, + }, + }, + Extensions: []pkix.Extension{ + // Deprecated claims. + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDIssuer, + Value: []byte(issuer), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowTrigger, + Value: []byte(trigger), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowSHA, + Value: []byte(digest), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowRepository, + Value: []byte(repo), + }, + // New claims. + { + Id: fulcio.OIDBuildTrigger, + Value: encodedTrigger, + }, + { + Id: fulcio.OIDSourceRepositoryURI, + Value: encodedRepoURI, + }, + { + Id: fulcio.OIDIssuerV2, + Value: encodedIssuer, + }, + { + Id: fulcio.OIDSourceRepositoryDigest, + Value: encodedDigest, + }, + { + Id: fulcio.OIDRunnerEnvironment, + Value: encodedHosted, + }, + { + Id: fulcio.OIDSourceRepositoryRef, + Value: encodedRef, + }, + { + Id: fulcio.OIDSourceRepositoryIdentifier, + Value: encodedSourceID, + }, + { + Id: fulcio.OIDSourceRepositoryOwnerIdentifier, + Value: encodedSourceOwnerID, + }, + { + Id: fulcio.OIDBuildConfigDigest, + Value: encodedBuildConfigSha1, + }, + { + Id: fulcio.OIDBuildConfigURI, + Value: encodedBuildConfigURI, + }, + { + Id: fulcio.OIDRunInvocationURI, + Value: encodedInvocationURI, + }, + { + Id: fulcio.OIDBuildSignerDigest, + Value: encodedSubjectSha1, + }, + }, + }, + workflow: WorkflowIdentity{ + Issuer: issuer, + SubjectSha1: &subjectSha1, + SubjectHosted: &hosted, + SubjectWorkflowRef: repo + "/" + buildConfigPath, + SourceRepository: repo, + SourceSha1: digest, + SourceRef: &ref, + SourceID: &sourceID, + SourceOwnerID: &sourceOwnerID, + BuildTrigger: trigger, + BuildConfigPath: &buildConfigPath, + RunID: &invocationID, + }, + }, + { + name: "new cert empty URIs", + cert: x509.Certificate{ + Extensions: []pkix.Extension{ + // Deprecated claims. + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDIssuer, + Value: []byte(issuer), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowTrigger, + Value: []byte(trigger), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowSHA, + Value: []byte(digest), + }, + { + //nolint: staticcheck // SA1019: Need to support older signatures. + Id: fulcio.OIDGitHubWorkflowRepository, + Value: []byte(repo), + }, + // New claims. + { + Id: fulcio.OIDBuildTrigger, + Value: encodedTrigger, + }, + { + Id: fulcio.OIDSourceRepositoryURI, + Value: encodedRepoURI, + }, + { + Id: fulcio.OIDIssuerV2, + Value: encodedIssuer, + }, + { + Id: fulcio.OIDSourceRepositoryDigest, + Value: encodedDigest, + }, + { + Id: fulcio.OIDRunnerEnvironment, + Value: encodedHosted, + }, + { + Id: fulcio.OIDSourceRepositoryRef, + Value: encodedRef, + }, + { + Id: fulcio.OIDSourceRepositoryIdentifier, + Value: encodedSourceID, + }, + { + Id: fulcio.OIDSourceRepositoryOwnerIdentifier, + Value: encodedSourceOwnerID, + }, + { + Id: fulcio.OIDBuildConfigDigest, + Value: encodedBuildConfigSha1, + }, + { + Id: fulcio.OIDBuildConfigURI, + Value: encodedBuildConfigURI, + }, + { + Id: fulcio.OIDRunInvocationURI, + Value: encodedInvocationURI, + }, + }, + }, + err: serrors.ErrorInvalidFormat, + }, + { + name: "new cert no deprecated claims", + cert: x509.Certificate{ + URIs: []*url.URL{ + { + Path: "/" + repo + "/" + buildConfigPath, + }, + }, + Extensions: []pkix.Extension{ + // New claims. + { + Id: fulcio.OIDBuildTrigger, + Value: encodedTrigger, + }, + { + Id: fulcio.OIDSourceRepositoryURI, + Value: encodedRepoURI, + }, + { + Id: fulcio.OIDIssuerV2, + Value: encodedIssuer, + }, + { + Id: fulcio.OIDSourceRepositoryDigest, + Value: encodedDigest, + }, + { + Id: fulcio.OIDRunnerEnvironment, + Value: encodedHosted, + }, + { + Id: fulcio.OIDSourceRepositoryRef, + Value: encodedRef, + }, + { + Id: fulcio.OIDSourceRepositoryIdentifier, + Value: encodedSourceID, + }, + { + Id: fulcio.OIDSourceRepositoryOwnerIdentifier, + Value: encodedSourceOwnerID, + }, + { + Id: fulcio.OIDBuildConfigDigest, + Value: encodedBuildConfigSha1, + }, + { + Id: fulcio.OIDBuildConfigURI, + Value: encodedBuildConfigURI, + }, + { + Id: fulcio.OIDRunInvocationURI, + Value: encodedInvocationURI, + }, + }, + }, + workflow: WorkflowIdentity{ + Issuer: issuer, + SubjectWorkflowRef: repo + "/" + buildConfigPath, + SourceRepository: repo, + SourceSha1: digest, + BuildTrigger: trigger, + SubjectHosted: &hosted, + SourceRef: &ref, + SourceID: &sourceID, + SourceOwnerID: &sourceOwnerID, + BuildConfigPath: &buildConfigPath, + RunID: &invocationID, + }, + }, + { + name: "new cert no deprecated claims empty URIs", + cert: x509.Certificate{ + Extensions: []pkix.Extension{ + // New claims. + { + Id: fulcio.OIDBuildTrigger, + Value: encodedTrigger, + }, + { + Id: fulcio.OIDSourceRepositoryURI, + Value: encodedRepoURI, + }, + { + Id: fulcio.OIDIssuerV2, + Value: encodedIssuer, + }, + { + Id: fulcio.OIDSourceRepositoryDigest, + Value: encodedDigest, + }, + { + Id: fulcio.OIDRunnerEnvironment, + Value: encodedHosted, + }, + { + Id: fulcio.OIDSourceRepositoryRef, + Value: encodedRef, + }, + { + Id: fulcio.OIDSourceRepositoryIdentifier, + Value: encodedSourceID, + }, + { + Id: fulcio.OIDSourceRepositoryOwnerIdentifier, + Value: encodedSourceOwnerID, + }, + { + Id: fulcio.OIDBuildConfigDigest, + Value: encodedBuildConfigSha1, + }, + { + Id: fulcio.OIDBuildConfigURI, + Value: encodedBuildConfigURI, + }, + { + Id: fulcio.OIDRunInvocationURI, + Value: encodedInvocationURI, + }, + }, + }, + err: serrors.ErrorInvalidFormat, + }, + } + 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() + workflow, err := GetWorkflowInfoFromCertificate(&tt.cert) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors())) + } + if err != nil { + return + } + + if !cmp.Equal(*workflow, tt.workflow) { + t.Errorf(cmp.Diff(*workflow, tt.workflow)) + } + }) + } +} diff --git a/verifiers/internal/gha/bundle_test.go b/verifiers/internal/gha/bundle_test.go index a6fc492e6..87fe03e25 100644 --- a/verifiers/internal/gha/bundle_test.go +++ b/verifiers/internal/gha/bundle_test.go @@ -14,7 +14,7 @@ func Test_verifyBundle(t *testing.T) { t.Parallel() ctx := context.Background() - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { t.Fatal(err) } diff --git a/verifiers/internal/gha/npm.go b/verifiers/internal/gha/npm.go index 8a106fcea..2ac0f5324 100644 --- a/verifiers/internal/gha/npm.go +++ b/verifiers/internal/gha/npm.go @@ -19,9 +19,18 @@ import ( "github.com/slsa-framework/slsa-verifier/v2/verifiers/utils" ) +type hosted string + +const ( + hostedSelf hosted = "self-hosted" + hostedGitHub hosted = "github-hosted" +) + const ( - publishAttestationV01 = "https://github.com/npm/attestation/tree/main/specs/publish/" - builderGitHubRunnerID = "https://github.com/actions/runner" + publishAttestationV01 = "https://github.com/npm/attestation/tree/main/specs/publish/" + builderLegacyGitHubRunnerID = "https://github.com/actions/runner" + builderGitHubHostedRunnerID = builderLegacyGitHubRunnerID + "/" + string(hostedGitHub) + builderSelfHostedRunnerID = builderLegacyGitHubRunnerID + "/" + string(hostedSelf) ) var errrorInvalidAttestations = errors.New("invalid npm attestations") diff --git a/verifiers/internal/gha/npm_test.go b/verifiers/internal/gha/npm_test.go index b9e10fb43..fee31d995 100644 --- a/verifiers/internal/gha/npm_test.go +++ b/verifiers/internal/gha/npm_test.go @@ -506,7 +506,7 @@ func Test_verifyPackageName(t *testing.T) { t.Parallel() ctx := context.Background() - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { t.Fatal(err) } @@ -584,7 +584,7 @@ func Test_verifyPackageVersion(t *testing.T) { t.Parallel() ctx := context.Background() - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { t.Fatal(err) } @@ -760,7 +760,7 @@ func Test_verifyIntotoHeaders(t *testing.T) { t.Parallel() ctx := context.Background() - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { t.Fatal(err) } @@ -849,7 +849,7 @@ func Test_NpmNew(t *testing.T) { t.Parallel() ctx := context.Background() - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { t.Fatal(err) } diff --git a/verifiers/internal/gha/provenance.go b/verifiers/internal/gha/provenance.go index 0b71f034d..b1186752b 100644 --- a/verifiers/internal/gha/provenance.go +++ b/verifiers/internal/gha/provenance.go @@ -19,7 +19,7 @@ import ( "github.com/slsa-framework/slsa-verifier/v2/verifiers/utils" // Load provenance types. - _ "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" ) @@ -182,7 +182,6 @@ func verifyDigest(prov slsaprovenance.Provenance, expectedHash string) error { if !exists { return fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, fmt.Sprintf("no sha%v subject digest", l)) } - if hash == expectedHash { return nil } @@ -198,13 +197,6 @@ func VerifyProvenanceSignature(ctx context.Context, trustedRoot *TrustedRoot, provenance []byte, artifactHash string) ( *SignedAttestation, error, ) { - // Collect trusted root material for verification (Rekor pubkeys, SCT pubkeys, - // Fulcio root certificates). - _, err := GetTrustedRoot(ctx) - if err != nil { - return nil, err - } - // There are two cases, either we have an embedded certificate, or we need // to use the Redis index for searching by artifact SHA. if hasCertInEnvelope(provenance) { @@ -220,7 +212,8 @@ func VerifyProvenanceSignature(ctx context.Context, trustedRoot *TrustedRoot, provenance, rClient, trustedRoot) } -func VerifyNpmPackageProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, +func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentity, + provenanceOpts *options.ProvenanceOpts, isTrustedBuilder bool, ) error { prov, err := slsaprovenance.ProvenanceFromEnvelope(env) if err != nil { @@ -232,12 +225,52 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, provenanceOpts *options.P // Verify the builder ID. if err := verifyBuilderIDLooseMatch(prov, provenanceOpts.ExpectedBuilderID); err != nil { - return err + // Verification failed. Try again by appending or removing the the hosted status. + // Older provenance uses the shorted version without status, and recent provenance includes the status. + // We consider the short version witout status as github-hosted. + switch { + case !strings.HasSuffix(provenanceOpts.ExpectedBuilderID, "/"+string(hostedGitHub)): + // Append the status. + bid := provenanceOpts.ExpectedBuilderID + "/" + string(hostedGitHub) + oerr := verifyBuilderIDLooseMatch(prov, bid) + if oerr != nil { + // We do return the original error, since that's the caller the user provided. + return err + } + // Verification success. + err = nil + + case strings.HasSuffix(provenanceOpts.ExpectedBuilderID, "/"+string(hostedGitHub)): + // Remove the status. + bid := strings.TrimSuffix(provenanceOpts.ExpectedBuilderID, "/"+string(hostedGitHub)) + oerr := verifyBuilderIDLooseMatch(prov, bid) + if oerr != nil { + // We do return the original error, since that's the caller the user provided. + return err + } + // Verification success. + err = nil + + default: + break + } + + if err != nil { + return err + } } - // NOTE: for the non trusted builders, the information may be forgeable. // Also, the GitHub context is not recorded for the default builder. - return VerifyProvenanceCommonOptions(prov, provenanceOpts, true) + if err := VerifyProvenanceCommonOptions(prov, provenanceOpts, true); err != nil { + return err + } + + // Verify consistency between the provenance and the certificate. + // because for the non trusted builders, the information may be forgeable. + if !isTrustedBuilder { + return verifyProvenanceMatchesCertificate(prov, workflow) + } + return nil } func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, diff --git a/verifiers/internal/gha/provenance_forgeable.go b/verifiers/internal/gha/provenance_forgeable.go new file mode 100644 index 000000000..03d6758ed --- /dev/null +++ b/verifiers/internal/gha/provenance_forgeable.go @@ -0,0 +1,382 @@ +package gha + +import ( + "fmt" + "strings" + + serrors "github.com/slsa-framework/slsa-verifier/v2/errors" + "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance" + + // Load provenance types. + 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 { + // 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 { + return err + } + + // Verify v0.2 parameters. + if err := verifyV02Parameters(prov); err != nil { + return err + } + + // Verify metadata. + if err := verifyMetadata(prov, workflow); err != nil { + return err + } + + // Verify subjects. + if err := verifySubjectDigestName(prov, "sha512"); err != nil { + return err + } + + // Verify trigger config. + if err := verifyBuildConfig(prov, workflow); err != nil { + return err + } + + // Verify resolved dependencies. + if err := verifyResolvedDependencies(prov); err != nil { + return err + } + + // Verify v0.2 build config. + if err := verifyV02BuildConfig(prov); err != nil { + return err + } + + // Additional fields can only be present in fields + // defined as interface{}. We already verified buildConfig, + // parameters and environment for v0.2. + // In addition, fields not defined in the structures will cause an error + // because we use stric unmarshaling in slsaprovenance.go. + // TODO(#571): add tests for additional fields in the provenance. + + // Other fields such as material and config source URI / sha are verified + // as part of the common verification. + + // TODO(#566): verify fields for v1.0 provenance. + + return nil +} + +func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string) error { + subjects, err := prov.Subjects() + if err != nil { + return err + } + + if len(subjects) != 1 { + return fmt.Errorf("%w: invalid number of digests: %v", + serrors.ErrorNonVerifiableClaim, subjects) + } + + _, ok := subjects[0].Digest[digestName] + if !ok { + return fmt.Errorf("%w: digest '%s' not present", + serrors.ErrorNonVerifiableClaim, digestName) + } + return nil +} + +func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error { + triggerPath, err := prov.GetBuildTriggerPath() + if err != nil { + return err + } + return equalCertificateValue(workflow.BuildConfigPath, triggerPath, "trigger workflow") +} + +func verifyResolvedDependencies(prov slsaprovenance.Provenance) error { + n, err := prov.GetNumberResolvedDependencies() + if err != nil { + return err + } + if n != 1 { + return fmt.Errorf("%w: unexpected number of resolved dependencies: %v", + serrors.ErrorNonVerifiableClaim, n) + } + return nil +} + +func verifyMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error { + if err := verifyCommonMetadata(prov, workflow); err != nil { + return err + } + + // Verify v0.2 claims. + if err := verifyV02Metadata(prov); err != nil { + return err + } + + // TODO(#566): verify fields for v1.0 provenance + + return nil +} + +func verifyCommonMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error { + // Verify build invocation ID. + invocationID, err := prov.GetBuildInvocationID() + if err != nil { + return err + } + + runID, runAttempt, err := getRunIDs(workflow) + if err != nil { + return err + } + + // Only verify a non-empty buildID claim. + if invocationID != "" { + expectedID := fmt.Sprintf("%v-%v", runID, runAttempt) + if invocationID != expectedID { + return fmt.Errorf("%w: invocation ID: '%v' != '%v'", + serrors.ErrorMismatchCertificate, invocationID, + expectedID) + } + } + + // Verify start time. + startTime, err := prov.GetBuildStartTime() + if err != nil { + return err + } + if startTime != nil { + return fmt.Errorf("%w: build start time: %v", + serrors.ErrorNonVerifiableClaim, *startTime) + } + + // Verify finish time. + finishTime, err := prov.GetBuildFinishTime() + if err != nil { + return err + } + if finishTime != nil { + return fmt.Errorf("%w: build finish time: %v", + serrors.ErrorNonVerifiableClaim, *finishTime) + } + return nil +} + +func verifyV02Metadata(prov slsaprovenance.Provenance) error { + // https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go + /* + v0.2: + "buildInvocationId": "4757060009-1", + "completeness": { + "parameters": false, + "environment": false, + "materials": false + }, + "reproducible": false + */ + prov02, ok := prov.(*slsav02.ProvenanceV02) + if !ok { + return nil + } + if prov02.Predicate.Metadata == nil { + return nil + } + + if prov02.Predicate.Metadata.Reproducible { + return fmt.Errorf("%w: reproducible: %v", + serrors.ErrorNonVerifiableClaim, + prov02.Predicate.Metadata.Reproducible) + } + + completeness := prov02.Predicate.Metadata.Completeness + if completeness.Parameters || completeness.Materials || + completeness.Environment { + return fmt.Errorf("%w: completeness: %v", + serrors.ErrorNonVerifiableClaim, + completeness) + } + return nil +} + +func verifyV02Parameters(prov slsaprovenance.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) + if !ok { + return nil + } + if prov02.Predicate.Invocation.Parameters == nil { + return nil + } + m, ok := prov02.Predicate.Invocation.Parameters.(map[string]any) + if !ok || len(m) > 0 { + return fmt.Errorf("%w: parameters: %v", + serrors.ErrorNonVerifiableClaim, prov02.Predicate.Invocation.Parameters) + } + + return nil +} + +func verifyV02BuildConfig(prov slsaprovenance.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) + if !ok { + return nil + } + + if prov02.Predicate.BuildConfig == nil { + return nil + } + m, ok := prov02.Predicate.BuildConfig.(map[string]any) + if !ok || len(m) > 0 { + return fmt.Errorf("%w: buildConfig: %v", + serrors.ErrorNonVerifiableClaim, prov02.Predicate.BuildConfig) + } + + return nil +} + +func verifySystemParameters(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error { + /* + "environment": { + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14" + } + */ + sysParams, err := prov.GetSystemParameters() + if err != nil { + return err + } + // Verify that the parameters contain only fields we are able to verify. + // There are 10 fields to verify. + supportedNames := map[string]bool{ + "GITHUB_EVENT_NAME": true, + "GITHUB_REF": true, + "GITHUB_REPOSITORY": true, + "GITHUB_REPOSITORY_ID": true, + "GITHUB_REPOSITORY_OWNER_ID": true, + "GITHUB_RUN_ATTEMPT": true, + "GITHUB_RUN_ID": true, + "GITHUB_SHA": true, + "GITHUB_WORKFLOW_REF": true, + "GITHUB_WORKFLOW_SHA": true, + } + for k := range sysParams { + if !supportedNames[k] { + return fmt.Errorf("%w: unknown '%s' parameter", serrors.ErrorMismatchCertificate, k) + } + } + + // 1. GITHUB_EVENT_NAME. + if err := verifySystemParameter(sysParams, "GITHUB_EVENT_NAME", &workflow.BuildTrigger); err != nil { + return err + } + // 2. GITHUB_REPOSITORY + if err := verifySystemParameter(sysParams, "GITHUB_REPOSITORY", &workflow.SourceRepository); err != nil { + return err + } + // 3. GITHUB_REF + if err := verifySystemParameter(sysParams, "GITHUB_REF", workflow.SourceRef); err != nil { + return err + } + // 4. GITHUB_REPOSITORY_ID + if err := verifySystemParameter(sysParams, "GITHUB_REPOSITORY_ID", workflow.SourceID); err != nil { + return err + } + // 5. GITHUB_REPOSITORY_OWNER_ID + if err := verifySystemParameter(sysParams, "GITHUB_REPOSITORY_OWNER_ID", workflow.SourceOwnerID); err != nil { + return err + } + // 6. GITHUB_REPOSITORY_SHA + if err := verifySystemParameter(sysParams, "GITHUB_SHA", &workflow.SourceSha1); err != nil { + return err + } + // 7. GITHUB_WORKFLOW_REF + if err := verifySystemParameter(sysParams, "GITHUB_WORKFLOW_REF", &workflow.SubjectWorkflowRef); err != nil { + return err + } + // 8. GITHUB_WORKFLOW_SHA + if err := verifySystemParameter(sysParams, "GITHUB_WORKFLOW_SHA", workflow.SubjectSha1); err != nil { + return err + } + + // 9-10. GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT + if err := verifySystemRun(sysParams, workflow); err != nil { + return err + } + return nil +} + +func getRunIDs(workflow *WorkflowIdentity) (string, string, error) { + if workflow == nil { + return "", "", fmt.Errorf("%w: empty workflow", serrors.ErrorInvalidFormat) + } + if workflow.RunID == nil { + return "", "", nil + } + parts := strings.Split(*workflow.RunID, "/") + if len(parts) != 3 { + return "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidFormat, *workflow.RunID) + } + return parts[0], parts[2], nil +} + +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") { + return nil + } + // The certificate contains runID as '4757060009/attempts/1'. + if workflow.RunID == nil { + return fmt.Errorf("%w: empty certificate value to verify 'GITHUB_RUN_*'", + serrors.ErrorMismatchCertificate) + } + + runID, runAttempt, err := getRunIDs(workflow) + if err != nil { + return err + } + + if err := verifySystemParameter(params, "GITHUB_RUN_ID", &runID); err != nil { + return err + } + if err := verifySystemParameter(params, "GITHUB_RUN_ATTEMPT", &runAttempt); err != nil { + return err + } + + return nil +} + +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) { + return nil + } + // Provenance contains the field, we must verify it. + provValue, err := slsaprovenance.GetAsString(params, name) + if err != nil { + return err + } + // The certificate must have the value. Old Fulcio certs are not + // supported. + return equalCertificateValue(certValue, provValue, name) +} + +func equalCertificateValue(expected *string, actual, logName string) error { + if expected == nil { + return fmt.Errorf("%w: empty certificate value to verify '%s'", + serrors.ErrorMismatchCertificate, logName) + } + if actual != *expected { + return fmt.Errorf("%w: %s: '%s' != '%s'", serrors.ErrorMismatchCertificate, + logName, actual, *expected) + } + return nil +} diff --git a/verifiers/internal/gha/provenance_forgeable_test.go b/verifiers/internal/gha/provenance_forgeable_test.go new file mode 100644 index 000000000..4bda05624 --- /dev/null +++ b/verifiers/internal/gha/provenance_forgeable_test.go @@ -0,0 +1,1232 @@ +package gha + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + intoto "github.com/in-toto/in-toto-golang/in_toto" + intotocommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" + intotov02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + intotov1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" + serrors "github.com/slsa-framework/slsa-verifier/v2/errors" + slsav02 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2" + slsav10 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0" +) + +func Test_verifySubjectDigestName(t *testing.T) { + t.Parallel() + tests := []struct { + name string + subject []intoto.Subject + digestName string + err error + }{ + { + name: "valid digest", + digestName: "sha256", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha256": "abcd"}, + }, + }, + }, + { + name: "invalid 2 subjects", + digestName: "sha256", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha256": "abcd"}, + }, + { + Digest: intotocommon.DigestSet{"sha256": "abcd"}, + }, + }, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "invalid no subjects", + digestName: "sha256", + err: serrors.ErrorInvalidDssePayload, + }, + { + name: "wrong digest", + digestName: "sha512", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha256": "abcd"}, + }, + }, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + StatementHeader: intoto.StatementHeader{ + Subject: tt.subject, + }, + }, + } + err := verifySubjectDigestName(prov02, tt.digestName) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{ + StatementHeader: intoto.StatementHeader{ + Subject: tt.subject, + }, + } + err = verifySubjectDigestName(prov1, tt.digestName) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyBuildConfig(t *testing.T) { + t.Parallel() + tests := []struct { + name string + path string + workflow WorkflowIdentity + err error + }{ + { + name: "same path", + path: "the/path", + workflow: WorkflowIdentity{ + BuildConfigPath: asStringPointer("the/path"), + }, + }, + { + name: "no certificate path", + path: "the/path", + err: serrors.ErrorMismatchCertificate, + }, + { + name: "different path", + path: "another/path", + workflow: WorkflowIdentity{ + BuildConfigPath: asStringPointer("the/path"), + }, + err: serrors.ErrorMismatchCertificate, + }, + } + 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() + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + Predicate: intotov02.ProvenancePredicate{ + Invocation: intotov02.ProvenanceInvocation{ + ConfigSource: intotov02.ConfigSource{ + EntryPoint: tt.path, + }, + }, + }, + }, + } + err := verifyBuildConfig(prov02, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{ + Predicate: intotov1.ProvenancePredicate{ + BuildDefinition: intotov1.ProvenanceBuildDefinition{ + ExternalParameters: map[string]interface{}{ + "workflow": map[string]string{ + "path": tt.path, + }, + }, + }, + }, + } + err = verifyBuildConfig(prov1, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyResolvedDependencies(t *testing.T) { + t.Parallel() + tests := []struct { + name string + n int + workflow WorkflowIdentity + err error + }{ + { + name: "one entry", + n: 1, + }, + { + name: "two entry", + n: 2, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "no entry", + n: 0, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + Predicate: intotov02.ProvenancePredicate{}, + }, + } + if tt.n > 0 { + prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.n) + } + err := verifyResolvedDependencies(prov02) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{ + Predicate: intotov1.ProvenancePredicate{ + BuildDefinition: intotov1.ProvenanceBuildDefinition{}, + }, + } + if tt.n > 0 { + prov1.Predicate.BuildDefinition.ResolvedDependencies = make([]intotov1.ResourceDescriptor, tt.n) + } + err = verifyResolvedDependencies(prov1) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyCommonMetadata(t *testing.T) { + t.Parallel() + now := time.Now() + tests := []struct { + name string + metadata bool + invocationID *string + startTime *time.Time + endTime *time.Time + workflow WorkflowIdentity + err error + }{ + { + name: "no claims in cert and prov no metadata", + }, + { + name: "no claims in cert and prov with metadata", + metadata: true, + }, + { + name: "invocation ID in cert not in prov no metadata", + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert not in prov with metadata", + metadata: true, + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert and prov match", + invocationID: asStringPointer("12345-1"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert and prov mismatch attempt", + invocationID: asStringPointer("12345-2"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in cert and prov mismatch run", + invocationID: asStringPointer("1234-1"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in prov only with metadata", + invocationID: asStringPointer("1234-1"), + metadata: true, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in prov only no metadata", + invocationID: asStringPointer("1234-1"), + err: serrors.ErrorMismatchCertificate, + }, + { + name: "start time in prov not in cert", + startTime: &now, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "end time in prov not in cert", + endTime: &now, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + metadata := tt.metadata || tt.invocationID != nil || tt.startTime != nil || + tt.endTime != nil + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + Predicate: intotov02.ProvenancePredicate{}, + }, + } + if metadata { + prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{} + } + if tt.invocationID != nil { + prov02.Predicate.Metadata.BuildInvocationID = *tt.invocationID + } + if tt.startTime != nil { + prov02.Predicate.Metadata.BuildStartedOn = tt.startTime + } + if tt.endTime != nil { + prov02.Predicate.Metadata.BuildFinishedOn = tt.endTime + } + + err := verifyCommonMetadata(prov02, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{} + + if tt.invocationID != nil { + prov1.Predicate.RunDetails.BuildMetadata.InvocationID = *tt.invocationID + } + if tt.startTime != nil { + prov1.Predicate.RunDetails.BuildMetadata.StartedOn = tt.startTime + } + if tt.endTime != nil { + prov1.Predicate.RunDetails.BuildMetadata.FinishedOn = tt.endTime + } + + err = verifyCommonMetadata(prov1, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyV02Metadata(t *testing.T) { + t.Parallel() + tests := []struct { + name string + reproducible, parameters, materials, environment bool + metadata bool + err error + }{ + { + name: "correct all false", + metadata: true, + }, + { + name: "no metadata", + metadata: false, + }, + { + name: "reproducible true", + reproducible: true, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "parameters true", + parameters: true, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "materials true", + materials: true, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "environment true", + environment: true, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + metadata := tt.metadata || tt.reproducible || tt.parameters || + tt.environment || tt.materials + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{}, + } + if metadata { + prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{ + Completeness: intotov02.ProvenanceComplete{ + Parameters: tt.parameters, + Materials: tt.materials, + Environment: tt.environment, + }, + Reproducible: tt.reproducible, + } + } + err := verifyV02Metadata(prov02) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyV02Parameters(t *testing.T) { + t.Parallel() + tests := []struct { + name string + present bool + value map[string]any + err error + }{ + { + name: "no parameters", + }, + { + name: "empty parameters", + present: true, + }, + { + name: "0-length parameters", + value: make(map[string]any, 0), + }, + { + name: "non-empty no parameters", + value: make(map[string]any, 1), + }, + { + name: "non-empty with parameters", + value: map[string]any{"param": "val"}, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{}, + } + if tt.present || len(tt.value) > 0 { + prov02.Predicate.Invocation.Parameters = tt.value + } + err := verifyV02Parameters(prov02) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyV02BuildConfig(t *testing.T) { + t.Parallel() + tests := []struct { + name string + present bool + value map[string]any + err error + }{ + { + name: "no parameters", + }, + { + name: "empty parameters", + present: true, + }, + { + name: "0-length parameters", + value: make(map[string]any, 0), + }, + { + name: "non-empty no parameters", + value: make(map[string]any, 1), + }, + { + name: "non-empty with parameters", + value: map[string]any{"param": "val"}, + err: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{}, + } + if tt.present || len(tt.value) > 0 { + prov02.Predicate.BuildConfig = tt.value + } + err := verifyV02BuildConfig(prov02) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyMetadata(t *testing.T) { + t.Parallel() + now := time.Now() + tests := []struct { + name string + // These 4 parameters are only present in v0.2 provenance. + reproducible, parameters, materials, environment bool + metadata bool + invocationID *string + startTime *time.Time + endTime *time.Time + workflow WorkflowIdentity + errV01, errV02 error + }{ + // From Test_verifyV02Metadata. + { + name: "reproducible true", + reproducible: true, + errV02: serrors.ErrorNonVerifiableClaim, + }, + { + name: "parameters true", + parameters: true, + errV02: serrors.ErrorNonVerifiableClaim, + }, + { + name: "materials true", + materials: true, + errV02: serrors.ErrorNonVerifiableClaim, + }, + { + name: "environment true", + environment: true, + errV02: serrors.ErrorNonVerifiableClaim, + }, + + { + name: "no claims in cert and prov no metadata", + }, + { + name: "no claims in cert and prov with metadata", + metadata: true, + }, + // From Test_verifyCommonMetadata. + { + name: "invocation ID in cert not in prov no metadata", + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert not in prov with metadata", + metadata: true, + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert and prov match", + invocationID: asStringPointer("12345-1"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + }, + { + name: "invocation ID in cert and prov mismatch attempt", + invocationID: asStringPointer("12345-2"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + errV01: serrors.ErrorMismatchCertificate, + errV02: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in cert and prov mismatch run", + invocationID: asStringPointer("1234-1"), + workflow: WorkflowIdentity{ + RunID: asStringPointer("12345/attempt/1"), + }, + errV01: serrors.ErrorMismatchCertificate, + errV02: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in prov only with metadata", + invocationID: asStringPointer("1234-1"), + metadata: true, + errV01: serrors.ErrorMismatchCertificate, + errV02: serrors.ErrorMismatchCertificate, + }, + { + name: "invocation ID in prov only no metadata", + invocationID: asStringPointer("1234-1"), + errV01: serrors.ErrorMismatchCertificate, + errV02: serrors.ErrorMismatchCertificate, + }, + { + name: "start time in prov not in cert", + startTime: &now, + errV01: serrors.ErrorNonVerifiableClaim, + errV02: serrors.ErrorNonVerifiableClaim, + }, + { + name: "end time in prov not in cert", + endTime: &now, + errV01: serrors.ErrorNonVerifiableClaim, + errV02: serrors.ErrorNonVerifiableClaim, + }, + } + 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() + + metadata := tt.metadata || tt.invocationID != nil || tt.startTime != nil || + tt.endTime != nil || tt.reproducible || tt.parameters || + tt.environment || tt.materials + + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{}, + } + if metadata { + prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{ + Completeness: intotov02.ProvenanceComplete{ + Parameters: tt.parameters, + Materials: tt.materials, + Environment: tt.environment, + }, + Reproducible: tt.reproducible, + } + if tt.invocationID != nil { + prov02.Predicate.Metadata.BuildInvocationID = *tt.invocationID + } + if tt.startTime != nil { + prov02.Predicate.Metadata.BuildStartedOn = tt.startTime + } + if tt.endTime != nil { + prov02.Predicate.Metadata.BuildFinishedOn = tt.endTime + } + } + errV02 := verifyMetadata(prov02, &tt.workflow) + if !errCmp(errV02, tt.errV02) { + t.Errorf(cmp.Diff(errV02, tt.errV02)) + } + + prov1 := &slsav10.ProvenanceV1{} + + if tt.invocationID != nil { + prov1.Predicate.RunDetails.BuildMetadata.InvocationID = *tt.invocationID + } + if tt.startTime != nil { + prov1.Predicate.RunDetails.BuildMetadata.StartedOn = tt.startTime + } + if tt.endTime != nil { + prov1.Predicate.RunDetails.BuildMetadata.FinishedOn = tt.endTime + } + + errV01 := verifyMetadata(prov1, &tt.workflow) + if !errCmp(errV01, tt.errV01) { + t.Errorf(cmp.Diff(errV01, tt.errV01)) + } + }) + } +} + +func Test_verifySystemParameters(t *testing.T) { + t.Parallel() + expectedWorkflow := WorkflowIdentity{ + BuildTrigger: "workflow_dispatch", + SubjectWorkflowRef: "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + SubjectSha1: asStringPointer("b38894f2dda4355ea5606fccb166e61565e12a14"), + SourceRepository: "laurentsimon/provenance-npm-test", + SourceRef: asStringPointer("refs/heads/main"), + SourceID: asStringPointer("602223945"), + SourceOwnerID: asStringPointer("64505099"), + SourceSha1: "b38894f2dda4355ea5606fccb166e61565e12a14", + RunID: asStringPointer("4757060009/attempt/1"), + } + tests := []struct { + name string + environment map[string]interface{} + workflow WorkflowIdentity + err error + }{ + { + name: "all field populated", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + }, + { + name: "unknown field", + environment: map[string]interface{}{ + "SOMETHING": "workflow_dispatch", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + // Correct partial populated fields. + { + name: "only GITHUB_EVENT_NAME field populated", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_REF field populated", + environment: map[string]interface{}{ + "GITHUB_REF": "refs/heads/main", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_REPOSITORY field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_REPOSITORY_ID field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY_ID": "602223945", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_REPOSITORY_OWNER_ID field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_RUN_ATTEMPT field populated", + environment: map[string]interface{}{ + "GITHUB_RUN_ATTEMPT": "1", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_RUN_ID field populated", + environment: map[string]interface{}{ + "GITHUB_RUN_ID": "4757060009", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_SHA field populated", + environment: map[string]interface{}{ + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_WORKFLOW_REF field populated", + environment: map[string]interface{}{ + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + }, + workflow: expectedWorkflow, + }, + { + name: "only GITHUB_WORKFLOW_SHA field populated", + environment: map[string]interface{}{ + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + }, + // All fields populated one mismatch. + { + name: "GITHUB_EVENT_NAME mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch2", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_REF mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main2", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_REPOSITORY mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test2", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_REPOSITORY_ID mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "6022239452", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_REPOSITORY_OWNER_ID mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "645050992", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_RUN_ATTEMPT mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "12", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_RUN_ID mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "47570600092", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_SHA mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a142", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_WORKFLOW_REF mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main2", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "GITHUB_WORKFLOW_SHA mismatch", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "refs/heads/main", + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test", + "GITHUB_REPOSITORY_ID": "602223945", + "GITHUB_REPOSITORY_OWNER_ID": "64505099", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_RUN_ID": "4757060009", + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a14", + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main", + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a142", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + // Incorrect partially populated fields. + { + name: "incorrect only GITHUB_EVENT_NAME field populated", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch2", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_REF field populated", + environment: map[string]interface{}{ + "GITHUB_REF": "refs/heads/main2", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_REPOSITORY field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY": "laurentsimon/provenance-npm-test2", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_REPOSITORY_ID field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY_ID": "6022239452", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_REPOSITORY_OWNER_ID field populated", + environment: map[string]interface{}{ + "GITHUB_REPOSITORY_OWNER_ID": "645050992", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_RUN_ATTEMPT field populated", + environment: map[string]interface{}{ + "GITHUB_RUN_ATTEMPT": "12", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_RUN_ID field populated", + environment: map[string]interface{}{ + "GITHUB_RUN_ID": "47570600092", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_SHA field populated", + environment: map[string]interface{}{ + "GITHUB_SHA": "b38894f2dda4355ea5606fccb166e61565e12a142", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_WORKFLOW_REF field populated", + environment: map[string]interface{}{ + "GITHUB_WORKFLOW_REF": "laurentsimon/provenance-npm-test/.github/workflows/release.yml@refs/heads/main2", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "incorrect only GITHUB_WORKFLOW_SHA field populated", + environment: map[string]interface{}{ + "GITHUB_WORKFLOW_SHA": "b38894f2dda4355ea5606fccb166e61565e12a142", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + } + 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() + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + Predicate: intotov02.ProvenancePredicate{ + Invocation: intotov02.ProvenanceInvocation{ + Environment: tt.environment, + }, + }, + }, + } + + err := verifySystemParameters(prov02, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{ + Predicate: intotov1.ProvenancePredicate{ + BuildDefinition: intotov1.ProvenanceBuildDefinition{ + InternalParameters: tt.environment, + }, + }, + } + err = verifySystemParameters(prov1, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + +func Test_verifyProvenanceMatchesCertificate(t *testing.T) { + t.Parallel() + expectedWorkflow := WorkflowIdentity{ + BuildTrigger: "workflow_dispatch", + BuildConfigPath: asStringPointer("release/workflow/path"), + SubjectWorkflowRef: "path/to/trusted-builder@subject-ref", + SubjectSha1: asStringPointer("subject-sha"), + SourceRepository: "repo/name", + SourceRef: asStringPointer("source-ref"), + SourceID: asStringPointer("source-id"), + SourceOwnerID: asStringPointer("source-owner-id"), + SourceSha1: "source-sha", + RunID: asStringPointer("run-id/attempt/run-attempt"), + } + tests := []struct { + name string + subject []intoto.Subject + numberResolvedDependencies int + workflowTriggerPath string + environment map[string]interface{} + workflow WorkflowIdentity + err error + }{ + { + name: "correct provenance", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha512": "abcd"}, + }, + }, + numberResolvedDependencies: 1, + workflowTriggerPath: "release/workflow/path", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch", + "GITHUB_REF": "source-ref", + "GITHUB_REPOSITORY": "repo/name", + "GITHUB_REPOSITORY_ID": "source-id", + "GITHUB_REPOSITORY_OWNER_ID": "source-owner-id", + "GITHUB_RUN_ATTEMPT": "run-attempt", + "GITHUB_RUN_ID": "run-id", + "GITHUB_SHA": "source-sha", + "GITHUB_WORKFLOW_REF": "path/to/trusted-builder@subject-ref", + "GITHUB_WORKFLOW_SHA": "subject-sha", + }, + workflow: expectedWorkflow, + }, + { + name: "unknown field", + environment: map[string]interface{}{ + "SOMETHING": "workflow_dispatch", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "too many resolved dependencies", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha512": "abcd"}, + }, + }, + numberResolvedDependencies: 2, + workflowTriggerPath: "release/workflow/path", + workflow: expectedWorkflow, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "incorrect digest name", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha256": "abcd"}, + }, + }, + numberResolvedDependencies: 1, + workflowTriggerPath: "release/workflow/path", + workflow: expectedWorkflow, + err: serrors.ErrorNonVerifiableClaim, + }, + { + name: "invalid trigger path", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha512": "abcd"}, + }, + }, + numberResolvedDependencies: 1, + workflowTriggerPath: "release/workflow/path2", + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + { + name: "invalid trigger name", + subject: []intoto.Subject{ + { + Digest: intotocommon.DigestSet{"sha512": "abcd"}, + }, + }, + numberResolvedDependencies: 1, + workflowTriggerPath: "release/workflow/path", + environment: map[string]interface{}{ + "GITHUB_EVENT_NAME": "workflow_dispatch2", + }, + workflow: expectedWorkflow, + err: serrors.ErrorMismatchCertificate, + }, + } + 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() + prov02 := &slsav02.ProvenanceV02{ + &intoto.ProvenanceStatement{ + StatementHeader: intoto.StatementHeader{ + Subject: tt.subject, + }, + Predicate: intotov02.ProvenancePredicate{ + Invocation: intotov02.ProvenanceInvocation{ + Environment: tt.environment, + ConfigSource: intotov02.ConfigSource{ + EntryPoint: tt.workflowTriggerPath, + }, + }, + }, + }, + } + + if tt.numberResolvedDependencies > 0 { + prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.numberResolvedDependencies) + } + + err := verifyProvenanceMatchesCertificate(prov02, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + prov1 := &slsav10.ProvenanceV1{ + StatementHeader: intoto.StatementHeader{ + Subject: tt.subject, + }, + Predicate: intotov1.ProvenancePredicate{ + BuildDefinition: intotov1.ProvenanceBuildDefinition{ + InternalParameters: tt.environment, + ExternalParameters: map[string]interface{}{ + // TODO(#566): verify fields for v1.0 provenance. + "workflow": map[string]string{ + "path": tt.workflowTriggerPath, + }, + }, + }, + }, + } + if tt.numberResolvedDependencies > 0 { + prov1.Predicate.BuildDefinition.ResolvedDependencies = make([]intotov1.ResourceDescriptor, tt.numberResolvedDependencies) + } + err = verifyProvenanceMatchesCertificate(prov1, &tt.workflow) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} diff --git a/verifiers/internal/gha/provenance_test.go b/verifiers/internal/gha/provenance_test.go index 2613e84c5..8f994d3be 100644 --- a/verifiers/internal/gha/provenance_test.go +++ b/verifiers/internal/gha/provenance_test.go @@ -26,6 +26,42 @@ func provenanceFromBytes(payload []byte) (slsaprovenance.Provenance, error) { return slsaprovenance.ProvenanceFromEnvelope(env) } +func Test_ProvenanceFromEnvelope(t *testing.T) { + t.Parallel() + tests := []struct { + name string + path string + expected error + }{ + { + name: "invalid dsse: not SLSA predicate", + path: "./testdata/dsse-not-slsa.intoto.jsonl", + expected: serrors.ErrorInvalidDssePayload, + }, + { + name: "slsa 1.0 invalid dsse: not SLSA predicate", + path: "./testdata/dsse-not-slsa-v1.intoto.jsonl", + expected: serrors.ErrorInvalidDssePayload, + }, + // TODO(#573): add more copliance tests. + } + 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() + + content, err := os.ReadFile(tt.path) + if err != nil { + panic(fmt.Errorf("os.ReadFile: %w", err)) + } + _, err = provenanceFromBytes(content) + if !errCmp(err, tt.expected) { + t.Errorf(cmp.Diff(err, tt.expected)) + } + }) + } +} + func Test_VerifyDigest(t *testing.T) { t.Parallel() tests := []struct { @@ -34,24 +70,17 @@ func Test_VerifyDigest(t *testing.T) { artifactHash string expected error }{ - { - name: "invalid dsse: not SLSA predicate", - path: "./testdata/dsse-not-slsa.intoto.jsonl", - artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", - expected: serrors.ErrorInvalidDssePayload, - }, - { - name: "invalid dsse: nil subject", - path: "./testdata/dsse-no-subject.intoto.jsonl", - artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", - expected: serrors.ErrorInvalidDssePayload, - }, { name: "invalid dsse: no sha256 subject digest", path: "./testdata/dsse-no-subject-hash.intoto.jsonl", artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", expected: serrors.ErrorInvalidDssePayload, }, + { + name: "invalid dsse: nil subject", + path: "./testdata/dsse-no-subject.intoto.jsonl", + expected: serrors.ErrorInvalidDssePayload, + }, { name: "mismatched artifact hash with env", path: "./testdata/dsse-valid.intoto.jsonl", @@ -82,20 +111,12 @@ func Test_VerifyDigest(t *testing.T) { artifactHash: "04e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", expected: serrors.ErrorMismatchHash, }, - { - name: "slsa 1.0 invalid dsse: not SLSA predicate", - path: "./testdata/dsse-not-slsa-v1.intoto.jsonl", - artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", - expected: serrors.ErrorInvalidDssePayload, - }, - { name: "invalid dsse: nil subject", path: "./testdata/dsse-no-subject-v1.intoto.jsonl", artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", expected: serrors.ErrorInvalidDssePayload, }, - { name: "invalid dsse: no sha256 subject digest", path: "./testdata/dsse-no-subject-hash-v1.intoto.jsonl", @@ -633,11 +654,6 @@ func Test_VerifyBranch(t *testing.T) { path: "./testdata/dsse-main-ref-v1.intoto.jsonl", branch: "main", }, - { - name: "ref branch3", - path: "./testdata/dsse-branch3-ref-v1.intoto.jsonl", - branch: "branch3", - }, { name: "ref main case-sensitive", path: "./testdata/dsse-main-ref-v1.intoto.jsonl", @@ -859,6 +875,11 @@ func Test_VerifyTag(t *testing.T) { path: "./testdata/dsse-vslsa1-tag.intoto.jsonl", tag: "vslsa1", }, + { + name: "ref branch3", + path: "./testdata/dsse-branch3-ref-v1.intoto.jsonl", + expected: serrors.ErrorMismatchTag, + }, { name: "ref main", path: "./testdata/dsse-main-ref-v1.intoto.jsonl", diff --git a/verifiers/internal/gha/slsaprovenance/common.go b/verifiers/internal/gha/slsaprovenance/common.go index 4f35ec824..d9be54f17 100644 --- a/verifiers/internal/gha/slsaprovenance/common.go +++ b/verifiers/internal/gha/slsaprovenance/common.go @@ -206,6 +206,11 @@ func GetBranch(environment map[string]any, predicateType string) (string, error) } } +func Exists(environment map[string]any, field string) bool { + _, ok := environment[field] + return ok +} + func GetAsString(environment map[string]any, field string) (string, error) { value, ok := environment[field] if !ok { diff --git a/verifiers/internal/gha/slsaprovenance/slsaprovenance.go b/verifiers/internal/gha/slsaprovenance/slsaprovenance.go index 5a4bf489d..eded600d4 100644 --- a/verifiers/internal/gha/slsaprovenance/slsaprovenance.go +++ b/verifiers/internal/gha/slsaprovenance/slsaprovenance.go @@ -1,10 +1,12 @@ package slsaprovenance import ( + "bytes" "encoding/base64" "encoding/json" "fmt" "sync" + "time" intoto "github.com/in-toto/in-toto-golang/in_toto" dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse" @@ -34,6 +36,24 @@ type Provenance interface { // GetTag retrieves the tag of the source from the provenance. GetTag() (string, error) + // Get workflow trigger path. + GetBuildTriggerPath() (string, error) + + // Get system pararmeters. + GetSystemParameters() (map[string]any, error) + + // Get build invocation ID. + GetBuildInvocationID() (string, error) + + // Get build start time. + GetBuildStartTime() (*time.Time, error) + + // Get build finish time. + GetBuildFinishTime() (*time.Time, error) + + // Get number of resolved dependencies. + GetNumberResolvedDependencies() (int, error) + // GetWorkflowInputs retrieves the inputs from the provenance. Only succeeds for event // relevant event types (workflow_inputs). GetWorkflowInputs() (map[string]interface{}, error) @@ -68,7 +88,12 @@ func ProvenanceFromEnvelope(env *dsselib.Envelope) (Provenance, error) { } prov := ptype.(func() Provenance)() - if err := json.Unmarshal(pyld, prov); err != nil { + // Strict unmarshal. + // NOTE: this supports extensions because they are + // only used as part of interface{}-defined fields. + dec := json.NewDecoder(bytes.NewReader(pyld)) + dec.DisallowUnknownFields() + if err := dec.Decode(prov); err != nil { return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err.Error()) } return prov, nil diff --git a/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go b/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go index 74dc2dad5..46f6b7125 100644 --- a/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go +++ b/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go @@ -2,6 +2,7 @@ package v02 import ( "fmt" + "time" intoto "github.com/in-toto/in-toto-golang/in_toto" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" @@ -77,3 +78,40 @@ func (prov *ProvenanceV02) GetWorkflowInputs() (map[string]interface{}, error) { return slsaprovenance.GetWorkflowInputs(environment, prov.PredicateType) } + +func (prov *ProvenanceV02) GetBuildTriggerPath() (string, error) { + return prov.Predicate.Invocation.ConfigSource.EntryPoint, nil +} + +func (prov *ProvenanceV02) GetBuildInvocationID() (string, error) { + if prov.Predicate.Metadata == nil { + return "", nil + } + return prov.Predicate.Metadata.BuildInvocationID, nil +} + +func (prov *ProvenanceV02) GetBuildStartTime() (*time.Time, error) { + if prov.Predicate.Metadata == nil { + return nil, nil + } + return prov.Predicate.Metadata.BuildStartedOn, nil +} + +func (prov *ProvenanceV02) GetBuildFinishTime() (*time.Time, error) { + if prov.Predicate.Metadata == nil { + return nil, nil + } + return prov.Predicate.Metadata.BuildFinishedOn, nil +} + +func (prov *ProvenanceV02) GetNumberResolvedDependencies() (int, error) { + return len(prov.Predicate.Materials), nil +} + +func (prov *ProvenanceV02) GetSystemParameters() (map[string]any, error) { + environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type") + } + return environment, nil +} diff --git a/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go b/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go index 2a28b3bd4..66c61cd35 100644 --- a/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go +++ b/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go @@ -3,6 +3,7 @@ package v1 import ( "encoding/json" "fmt" + "time" intoto "github.com/in-toto/in-toto-golang/in_toto" slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" @@ -94,3 +95,53 @@ func (prov *ProvenanceV1) GetWorkflowInputs() (map[string]interface{}, error) { } return slsaprovenance.GetWorkflowInputs(sysParams, prov.predicateType) } + +// TODO(https://github.com/slsa-framework/slsa-verifier/issues/566): +// verify the ref and repo as well. +func (prov *ProvenanceV1) GetBuildTriggerPath() (string, error) { + sysParams, ok := prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type") + } + + w, ok := sysParams["workflow"] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow parameters type") + } + + wMap, ok := w.(map[string]string) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow not a map") + } + + v, ok := wMap["path"] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no path entry on workflow") + } + return v, nil +} + +func (prov *ProvenanceV1) GetBuildInvocationID() (string, error) { + return prov.Predicate.RunDetails.BuildMetadata.InvocationID, nil +} + +func (prov *ProvenanceV1) GetBuildStartTime() (*time.Time, error) { + return prov.Predicate.RunDetails.BuildMetadata.StartedOn, nil +} + +func (prov *ProvenanceV1) GetBuildFinishTime() (*time.Time, error) { + return prov.Predicate.RunDetails.BuildMetadata.FinishedOn, nil +} + +func (prov *ProvenanceV1) GetNumberResolvedDependencies() (int, error) { + return len(prov.Predicate.BuildDefinition.ResolvedDependencies), nil +} + +func (prov *ProvenanceV1) GetSystemParameters() (map[string]any, error) { + sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type") + } + + return sysParams, nil +} diff --git a/verifiers/internal/gha/testdata/dsse-branch3-ref-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-branch3-ref-v1.intoto.jsonl index 3f0ae3279..a55ea7859 100644 --- a/verifiers/internal/gha/testdata/dsse-branch3-ref-v1.intoto.jsonl +++ b/verifiers/internal/gha/testdata/dsse-branch3-ref-v1.intoto.jsonl @@ -1 +1,10 @@ -{ "payloadType": "application/vnd.in-toto+json", "payload": "eyAiX3R5cGUiOiAiaHR0cHM6Ly9pbi10b3RvLmlvL1N0YXRlbWVudC92MC4xIiwgInN1YmplY3QiOiBbIHsgImRpZ2VzdCI6IHsgInNoYTI1NiI6ICIwYWU3ZTRmYTcxNjg2NTM4NDQwMDEyZWUzNmEyNjM0ZGJhYTE5ZGYyZGQxNmE0NjZmNTI0MTFmYjM0OGJiYzRlIiB9LCAibmFtZSI6ICJiaW5hcnktbGludXgtYW1kNjQiIH0gXSwgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwgInByZWRpY2F0ZSI6IHsgImJ1aWxkRGVmaW5pdGlvbiI6IHsgImJ1aWxkTm90U0xTQSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsICJleHRlcm5hbFBhcmFtZXRlcnMiOiB7ICJzb3VyY2UiOiB7ICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLCAiZGlnZXN0IjogeyAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIiB9IH0gfSwgImludGVybmFsUGFyYW1ldGVycyI6IHsgIlJVTk5FUl9BUkNIIjogIlg2NCIsICJHSVRIVUJfQUNUT1IiOiAiYXNyYWEiLCAiR0lUSFVCX0JBU0VfUkVGIjogIiIsICJHSVRIVUJfRVZFTlRfTkFNRSI6ICJ3b3JrZmxvd19kaXNwYXRjaCIsICJHSVRIVUJfRVZFTlRfUEFZTE9BRCI6IHsgImlucHV0cyI6IG51bGwsICJyZWYiOiAicmVmcy9oZWFkcy9icmFuY2gzIiwgInJlcG9zaXRvcnkiOiB7ICJhbGxvd19mb3JraW5nIjogdHJ1ZSwgImFyY2hpdmVfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC97YXJjaGl2ZV9mb3JtYXR9ey9yZWZ9IiwgImFyY2hpdmVkIjogZmFsc2UsICJhc3NpZ25lZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9hc3NpZ25lZXN7L3VzZXJ9IiwgImJsb2JzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZ2l0L2Jsb2Jzey9zaGF9IiwgImJyYW5jaGVzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvYnJhbmNoZXN7L2JyYW5jaH0iLCAiY2xvbmVfdXJsIjogImh0dHBzOi8vZ2l0aHViLmNvbS9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0LmdpdCIsICJjb2xsYWJvcmF0b3JzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvY29sbGFib3JhdG9yc3svY29sbGFib3JhdG9yfSIsICJjb21tZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2NvbW1lbnRzey9udW1iZXJ9IiwgImNvbW1pdHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9jb21taXRzey9zaGF9IiwgImNvbXBhcmVfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9jb21wYXJlL3tiYXNlfS4uLntoZWFkfSIsICJjb250ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2NvbnRlbnRzL3srcGF0aH0iLCAiY29udHJpYnV0b3JzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvY29udHJpYnV0b3JzIiwgImNyZWF0ZWRfYXQiOiAiMjAyMi0wMi0xNVQxNTozMzo0OVoiLCAiZGVmYXVsdF9icmFuY2giOiAibWFpbiIsICJkZXBsb3ltZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2RlcGxveW1lbnRzIiwgImRlc2NyaXB0aW9uIjogIlRlc3QgZm9yIFNMU0EiLCAiZGlzYWJsZWQiOiBmYWxzZSwgImRvd25sb2Fkc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2Rvd25sb2FkcyIsICJldmVudHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9ldmVudHMiLCAiZm9yayI6IHRydWUsICJmb3JrcyI6IDAsICJmb3Jrc19jb3VudCI6IDAsICJmb3Jrc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2ZvcmtzIiwgImZ1bGxfbmFtZSI6ICJhc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0IiwgImdpdF9jb21taXRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZ2l0L2NvbW1pdHN7L3NoYX0iLCAiZ2l0X3JlZnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9naXQvcmVmc3svc2hhfSIsICJnaXRfdGFnc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2dpdC90YWdzey9zaGF9IiwgImdpdF91cmwiOiAiZ2l0Oi8vZ2l0aHViLmNvbS9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0LmdpdCIsICJoYXNfZG93bmxvYWRzIjogdHJ1ZSwgImhhc19pc3N1ZXMiOiBmYWxzZSwgImhhc19wYWdlcyI6IGZhbHNlLCAiaGFzX3Byb2plY3RzIjogdHJ1ZSwgImhhc193aWtpIjogdHJ1ZSwgImhvbWVwYWdlIjogbnVsbCwgImhvb2tzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvaG9va3MiLCAiaHRtbF91cmwiOiAiaHR0cHM6Ly9naXRodWIuY29tL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QiLCAiaWQiOiA0NTk2MzkxNTAsICJpc190ZW1wbGF0ZSI6IGZhbHNlLCAiaXNzdWVfY29tbWVudF91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2lzc3Vlcy9jb21tZW50c3svbnVtYmVyfSIsICJpc3N1ZV9ldmVudHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9pc3N1ZXMvZXZlbnRzey9udW1iZXJ9IiwgImlzc3Vlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2lzc3Vlc3svbnVtYmVyfSIsICJrZXlzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3Qva2V5c3sva2V5X2lkfSIsICJsYWJlbHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9sYWJlbHN7L25hbWV9IiwgImxhbmd1YWdlIjogIkdvIiwgImxhbmd1YWdlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2xhbmd1YWdlcyIsICJsaWNlbnNlIjogeyAia2V5IjogImFwYWNoZS0yLjAiLCAibmFtZSI6ICJBcGFjaGUgTGljZW5zZSAyLjAiLCAibm9kZV9pZCI6ICJNRGM2VEdsalpXNXpaVEk9IiwgInNwZHhfaWQiOiAiQXBhY2hlLTIuMCIsICJ1cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9hcGFjaGUtMi4wIiB9LCAibWVyZ2VzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvbWVyZ2VzIiwgIm1pbGVzdG9uZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9taWxlc3RvbmVzey9udW1iZXJ9IiwgIm1pcnJvcl91cmwiOiBudWxsLCAibmFtZSI6ICJzbHNhLW9uLWdpdGh1Yi10ZXN0IiwgIm5vZGVfaWQiOiAiUl9rZ0RPRzJXSmJnIiwgIm5vdGlmaWNhdGlvbnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9ub3RpZmljYXRpb25zez9zaW5jZSxhbGwscGFydGljaXBhdGluZ30iLCAib3Blbl9pc3N1ZXMiOiAwLCAib3Blbl9pc3N1ZXNfY291bnQiOiAwLCAib3duZXIiOiB7ICJhdmF0YXJfdXJsIjogImh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS81MTk0NTY5P3Y9NCIsICJldmVudHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZXZlbnRzey9wcml2YWN5fSIsICJmb2xsb3dlcnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZm9sbG93ZXJzIiwgImZvbGxvd2luZ191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwgImdpc3RzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL2dpc3Rzey9naXN0X2lkfSIsICJncmF2YXRhcl9pZCI6ICIiLCAiaHRtbF91cmwiOiAiaHR0cHM6Ly9naXRodWIuY29tL2FzcmFhIiwgImlkIjogNTE5NDU2OSwgImxvZ2luIjogImFzcmFhIiwgIm5vZGVfaWQiOiAiTURRNlZYTmxjalV4T1RRMU5qaz0iLCAib3JnYW5pemF0aW9uc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9vcmdzIiwgInJlY2VpdmVkX2V2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9yZWNlaXZlZF9ldmVudHMiLCAicmVwb3NfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvcmVwb3MiLCAic2l0ZV9hZG1pbiI6IGZhbHNlLCAic3RhcnJlZF91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9zdGFycmVkey9vd25lcn17L3JlcG99IiwgInN1YnNjcmlwdGlvbnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvc3Vic2NyaXB0aW9ucyIsICJ0eXBlIjogIlVzZXIiLCAidXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEiIH0sICJwcml2YXRlIjogZmFsc2UsICJwdWxsc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3B1bGxzey9udW1iZXJ9IiwgInB1c2hlZF9hdCI6ICIyMDIyLTA1LTAyVDE4OjA1OjQ3WiIsICJyZWxlYXNlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3JlbGVhc2Vzey9pZH0iLCAic2l6ZSI6IDEyMTQsICJzc2hfdXJsIjogImdpdEBnaXRodWIuY29tOmFzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IiwgInN0YXJnYXplcnNfY291bnQiOiAwLCAic3RhcmdhemVyc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N0YXJnYXplcnMiLCAic3RhdHVzZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9zdGF0dXNlcy97c2hhfSIsICJzdWJzY3JpYmVyc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N1YnNjcmliZXJzIiwgInN1YnNjcmlwdGlvbl91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N1YnNjcmlwdGlvbiIsICJzdm5fdXJsIjogImh0dHBzOi8vZ2l0aHViLmNvbS9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0IiwgInRhZ3NfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC90YWdzIiwgInRlYW1zX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvdGVhbXMiLCAidG9waWNzIjogW10sICJ0cmVlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2dpdC90cmVlc3svc2hhfSIsICJ1cGRhdGVkX2F0IjogIjIwMjItMDItMTVUMTU6MzY6NDFaIiwgInVybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QiLCAidmlzaWJpbGl0eSI6ICJwdWJsaWMiLCAid2F0Y2hlcnMiOiAwLCAid2F0Y2hlcnNfY291bnQiOiAwIH0sICJzZW5kZXIiOiB7ICJhdmF0YXJfdXJsIjogImh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS81MTk0NTY5P3Y9NCIsICJldmVudHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZXZlbnRzey9wcml2YWN5fSIsICJmb2xsb3dlcnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZm9sbG93ZXJzIiwgImZvbGxvd2luZ191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwgImdpc3RzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL2dpc3Rzey9naXN0X2lkfSIsICJncmF2YXRhcl9pZCI6ICIiLCAiaHRtbF91cmwiOiAiaHR0cHM6Ly9naXRodWIuY29tL2FzcmFhIiwgImlkIjogNTE5NDU2OSwgImxvZ2luIjogImFzcmFhIiwgIm5vZGVfaWQiOiAiTURRNlZYTmxjalV4T1RRMU5qaz0iLCAib3JnYW5pemF0aW9uc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9vcmdzIiwgInJlY2VpdmVkX2V2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9yZWNlaXZlZF9ldmVudHMiLCAicmVwb3NfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvcmVwb3MiLCAic2l0ZV9hZG1pbiI6IGZhbHNlLCAic3RhcnJlZF91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9zdGFycmVkey9vd25lcn17L3JlcG99IiwgInN1YnNjcmlwdGlvbnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvc3Vic2NyaXB0aW9ucyIsICJ0eXBlIjogIlVzZXIiLCAidXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEiIH0sICJ3b3JrZmxvdyI6ICIuZ2l0aHViL3dvcmtmbG93cy9zbHNhLXJldXNhYmxlLnlhbWwiIH0sICJHSVRIVUJfUkVGIjogInJlZnMvaGVhZHMvYnJhbmNoMyIsICJHSVRIVUJfUkVGX1RZUEUiOiAiYnJhbmNoIiwgIkdJVEhVQl9SVU5fQVRURU1QVCI6ICIxIiwgIkdJVEhVQl9STk5fSUQiOiAiMjI1OTcwOTA3OSIsICJHSVRIVUJfUlVOX05VTUJFUiI6ICIxMjciLCAiR0lUSFVCX1NIQSI6ICJhMjg4MGQ2NGQxMmIyOTU3NjE4OTlkNTIzZTMzYzIyNjcwOTgyMDU0IiwgIklNQUdFX09TIjogInVidW50dTIwIiB9IH0sICJydW5EZXRhaWxzIjogeyAiYnVpbGRlciI6IHsgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIgfSwgIm1ldGFkYXRhIjogeyAiaW52b2NhdGlvbklkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvYWN0aW9ucy9ydW5zLzQxMzU0NjM3NDEvYXR0ZW1wdHMvMSIgfSB9IH0gfQo=", "signatures": [ { "keyid": "", "sig": "MEQCIBO/t/MjFal4P4pPgklZu5LF/1gA1761TB42NQH5tJ1aAiAVwD7Wn2zLNz9KNn99NGfU1jwumMhFWIpJqKpBseYHsA==" } ] } +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMGFlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0IgogICAgfQogIF0sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwKICAicHJlZGljYXRlIjogewogICAgImJ1aWxkRGVmaW5pdGlvbiI6IHsKICAgICAgImJ1aWxkVHlwZSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsCiAgICAgICJleHRlcm5hbFBhcmFtZXRlcnMiOiB7CiAgICAgICAgInNvdXJjZSI6IHsKICAgICAgICAgICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLAogICAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICAgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0sCiAgICAgICJpbnRlcm5hbFBhcmFtZXRlcnMiOiB7CiAgICAgICAgIlJVTk5FUl9BUkNIIjogIlg2NCIsCiAgICAgICAgIkdJVEhVQl9BQ1RPUiI6ICJhc3JhYSIsCiAgICAgICAgIkdJVEhVQl9CQVNFX1JFRiI6ICIiLAogICAgICAgICJHSVRIVUJfRVZFTlRfTkFNRSI6ICJ3b3JrZmxvd19kaXNwYXRjaCIsCiAgICAgICAgIkdJVEhVQl9FVkVOVF9QQVlMT0FEIjogewogICAgICAgICAgImlucHV0cyI6IG51bGwsCiAgICAgICAgICAicmVmIjogInJlZnMvaGVhZHMvYnJhbmNoMyIsCiAgICAgICAgICAicmVwb3NpdG9yeSI6IHsKICAgICAgICAgICAgImFsbG93X2ZvcmtpbmciOiB0cnVlLAogICAgICAgICAgICAiYXJjaGl2ZV91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3thcmNoaXZlX2Zvcm1hdH17L3JlZn0iLAogICAgICAgICAgICAiYXJjaGl2ZWQiOiBmYWxzZSwKICAgICAgICAgICAgImFzc2lnbmVlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2Fzc2lnbmVlc3svdXNlcn0iLAogICAgICAgICAgICAiYmxvYnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9naXQvYmxvYnN7L3NoYX0iLAogICAgICAgICAgICAiYnJhbmNoZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9icmFuY2hlc3svYnJhbmNofSIsCiAgICAgICAgICAgICJjbG9uZV91cmwiOiAiaHR0cHM6Ly9naXRodWIuY29tL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IiwKICAgICAgICAgICAgImNvbGxhYm9yYXRvcnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9jb2xsYWJvcmF0b3Jzey9jb2xsYWJvcmF0b3J9IiwKICAgICAgICAgICAgImNvbW1lbnRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvY29tbWVudHN7L251bWJlcn0iLAogICAgICAgICAgICAiY29tbWl0c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2NvbW1pdHN7L3NoYX0iLAogICAgICAgICAgICAiY29tcGFyZV91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2NvbXBhcmUve2Jhc2V9Li4ue2hlYWR9IiwKICAgICAgICAgICAgImNvbnRlbnRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvY29udGVudHMveytwYXRofSIsCiAgICAgICAgICAgICJjb250cmlidXRvcnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9jb250cmlidXRvcnMiLAogICAgICAgICAgICAiY3JlYXRlZF9hdCI6ICIyMDIyLTAyLTE1VDE1OjMzOjQ5WiIsCiAgICAgICAgICAgICJkZWZhdWx0X2JyYW5jaCI6ICJtYWluIiwKICAgICAgICAgICAgImRlcGxveW1lbnRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZGVwbG95bWVudHMiLAogICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVGVzdCBmb3IgU0xTQSIsCiAgICAgICAgICAgICJkaXNhYmxlZCI6IGZhbHNlLAogICAgICAgICAgICAiZG93bmxvYWRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZG93bmxvYWRzIiwKICAgICAgICAgICAgImV2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2V2ZW50cyIsCiAgICAgICAgICAgICJmb3JrIjogdHJ1ZSwKICAgICAgICAgICAgImZvcmtzIjogMCwKICAgICAgICAgICAgImZvcmtzX2NvdW50IjogMCwKICAgICAgICAgICAgImZvcmtzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZm9ya3MiLAogICAgICAgICAgICAiZnVsbF9uYW1lIjogImFzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QiLAogICAgICAgICAgICAiZ2l0X2NvbW1pdHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9naXQvY29tbWl0c3svc2hhfSIsCiAgICAgICAgICAgICJnaXRfcmVmc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2dpdC9yZWZzey9zaGF9IiwKICAgICAgICAgICAgImdpdF90YWdzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvZ2l0L3RhZ3N7L3NoYX0iLAogICAgICAgICAgICAiZ2l0X3VybCI6ICJnaXQ6Ly9naXRodWIuY29tL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IiwKICAgICAgICAgICAgImhhc19kb3dubG9hZHMiOiB0cnVlLAogICAgICAgICAgICAiaGFzX2lzc3VlcyI6IGZhbHNlLAogICAgICAgICAgICAiaGFzX3BhZ2VzIjogZmFsc2UsCiAgICAgICAgICAgICJoYXNfcHJvamVjdHMiOiB0cnVlLAogICAgICAgICAgICAiaGFzX3dpa2kiOiB0cnVlLAogICAgICAgICAgICAiaG9tZXBhZ2UiOiBudWxsLAogICAgICAgICAgICAiaG9va3NfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9ob29rcyIsCiAgICAgICAgICAgICJodG1sX3VybCI6ICJodHRwczovL2dpdGh1Yi5jb20vYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdCIsCiAgICAgICAgICAgICJpZCI6IDQ1OTYzOTE1MCwKICAgICAgICAgICAgImlzX3RlbXBsYXRlIjogZmFsc2UsCiAgICAgICAgICAgICJpc3N1ZV9jb21tZW50X3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvaXNzdWVzL2NvbW1lbnRzey9udW1iZXJ9IiwKICAgICAgICAgICAgImlzc3VlX2V2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2lzc3Vlcy9ldmVudHN7L251bWJlcn0iLAogICAgICAgICAgICAiaXNzdWVzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvaXNzdWVzey9udW1iZXJ9IiwKICAgICAgICAgICAgImtleXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9rZXlzey9rZXlfaWR9IiwKICAgICAgICAgICAgImxhYmVsc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2xhYmVsc3svbmFtZX0iLAogICAgICAgICAgICAibGFuZ3VhZ2UiOiAiR28iLAogICAgICAgICAgICAibGFuZ3VhZ2VzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvbGFuZ3VhZ2VzIiwKICAgICAgICAgICAgImxpY2Vuc2UiOiB7CiAgICAgICAgICAgICAgImtleSI6ICJhcGFjaGUtMi4wIiwKICAgICAgICAgICAgICAibmFtZSI6ICJBcGFjaGUgTGljZW5zZSAyLjAiLAogICAgICAgICAgICAgICJub2RlX2lkIjogIk1EYzZUR2xqWlc1elpUST0iLAogICAgICAgICAgICAgICJzcGR4X2lkIjogIkFwYWNoZS0yLjAiLAogICAgICAgICAgICAgICJ1cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9saWNlbnNlcy9hcGFjaGUtMi4wIgogICAgICAgICAgICB9LAogICAgICAgICAgICAibWVyZ2VzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvbWVyZ2VzIiwKICAgICAgICAgICAgIm1pbGVzdG9uZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9taWxlc3RvbmVzey9udW1iZXJ9IiwKICAgICAgICAgICAgIm1pcnJvcl91cmwiOiBudWxsLAogICAgICAgICAgICAibmFtZSI6ICJzbHNhLW9uLWdpdGh1Yi10ZXN0IiwKICAgICAgICAgICAgIm5vZGVfaWQiOiAiUl9rZ0RPRzJXSmJnIiwKICAgICAgICAgICAgIm5vdGlmaWNhdGlvbnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9ub3RpZmljYXRpb25zez9zaW5jZSxhbGwscGFydGljaXBhdGluZ30iLAogICAgICAgICAgICAib3Blbl9pc3N1ZXMiOiAwLAogICAgICAgICAgICAib3Blbl9pc3N1ZXNfY291bnQiOiAwLAogICAgICAgICAgICAib3duZXIiOiB7CiAgICAgICAgICAgICAgImF2YXRhcl91cmwiOiAiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUxOTQ1Njk/dj00IiwKICAgICAgICAgICAgICAiZXZlbnRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL2V2ZW50c3svcHJpdmFjeX0iLAogICAgICAgICAgICAgICJmb2xsb3dlcnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZm9sbG93ZXJzIiwKICAgICAgICAgICAgICAiZm9sbG93aW5nX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL2ZvbGxvd2luZ3svb3RoZXJfdXNlcn0iLAogICAgICAgICAgICAgICJnaXN0c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9naXN0c3svZ2lzdF9pZH0iLAogICAgICAgICAgICAgICJncmF2YXRhcl9pZCI6ICIiLAogICAgICAgICAgICAgICJodG1sX3VybCI6ICJodHRwczovL2dpdGh1Yi5jb20vYXNyYWEiLAogICAgICAgICAgICAgICJpZCI6IDUxOTQ1NjksCiAgICAgICAgICAgICAgImxvZ2luIjogImFzcmFhIiwKICAgICAgICAgICAgICAibm9kZV9pZCI6ICJNRFE2VlhObGNqVXhPVFExTmprPSIsCiAgICAgICAgICAgICAgIm9yZ2FuaXphdGlvbnNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvb3JncyIsCiAgICAgICAgICAgICAgInJlY2VpdmVkX2V2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9yZWNlaXZlZF9ldmVudHMiLAogICAgICAgICAgICAgICJyZXBvc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9yZXBvcyIsCiAgICAgICAgICAgICAgInNpdGVfYWRtaW4iOiBmYWxzZSwKICAgICAgICAgICAgICAic3RhcnJlZF91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9zdGFycmVkey9vd25lcn17L3JlcG99IiwKICAgICAgICAgICAgICAic3Vic2NyaXB0aW9uc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9zdWJzY3JpcHRpb25zIiwKICAgICAgICAgICAgICAidHlwZSI6ICJVc2VyIiwKICAgICAgICAgICAgICAidXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJwcml2YXRlIjogZmFsc2UsCiAgICAgICAgICAgICJwdWxsc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3B1bGxzey9udW1iZXJ9IiwKICAgICAgICAgICAgInB1c2hlZF9hdCI6ICIyMDIyLTA1LTAyVDE4OjA1OjQ3WiIsCiAgICAgICAgICAgICJyZWxlYXNlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3JlbGVhc2Vzey9pZH0iLAogICAgICAgICAgICAic2l6ZSI6IDEyMTQsCiAgICAgICAgICAgICJzc2hfdXJsIjogImdpdEBnaXRodWIuY29tOmFzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IiwKICAgICAgICAgICAgInN0YXJnYXplcnNfY291bnQiOiAwLAogICAgICAgICAgICAic3RhcmdhemVyc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N0YXJnYXplcnMiLAogICAgICAgICAgICAic3RhdHVzZXNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC9zdGF0dXNlcy97c2hhfSIsCiAgICAgICAgICAgICJzdWJzY3JpYmVyc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N1YnNjcmliZXJzIiwKICAgICAgICAgICAgInN1YnNjcmlwdGlvbl91cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L3N1YnNjcmlwdGlvbiIsCiAgICAgICAgICAgICJzdm5fdXJsIjogImh0dHBzOi8vZ2l0aHViLmNvbS9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0IiwKICAgICAgICAgICAgInRhZ3NfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvYXNyYWEvc2xzYS1vbi1naXRodWItdGVzdC90YWdzIiwKICAgICAgICAgICAgInRlYW1zX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QvdGVhbXMiLAogICAgICAgICAgICAidG9waWNzIjogW10sCiAgICAgICAgICAgICJ0cmVlc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9hc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0L2dpdC90cmVlc3svc2hhfSIsCiAgICAgICAgICAgICJ1cGRhdGVkX2F0IjogIjIwMjItMDItMTVUMTU6MzY6NDFaIiwKICAgICAgICAgICAgInVybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QiLAogICAgICAgICAgICAidmlzaWJpbGl0eSI6ICJwdWJsaWMiLAogICAgICAgICAgICAid2F0Y2hlcnMiOiAwLAogICAgICAgICAgICAid2F0Y2hlcnNfY291bnQiOiAwCiAgICAgICAgICB9LAogICAgICAgICAgInNlbmRlciI6IHsKICAgICAgICAgICAgImF2YXRhcl91cmwiOiAiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzUxOTQ1Njk/dj00IiwKICAgICAgICAgICAgImV2ZW50c191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9ldmVudHN7L3ByaXZhY3l9IiwKICAgICAgICAgICAgImZvbGxvd2Vyc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9mb2xsb3dlcnMiLAogICAgICAgICAgICAiZm9sbG93aW5nX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL2ZvbGxvd2luZ3svb3RoZXJfdXNlcn0iLAogICAgICAgICAgICAiZ2lzdHNfdXJsIjogImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvYXNyYWEvZ2lzdHN7L2dpc3RfaWR9IiwKICAgICAgICAgICAgImdyYXZhdGFyX2lkIjogIiIsCiAgICAgICAgICAgICJodG1sX3VybCI6ICJodHRwczovL2dpdGh1Yi5jb20vYXNyYWEiLAogICAgICAgICAgICAiaWQiOiA1MTk0NTY5LAogICAgICAgICAgICAibG9naW4iOiAiYXNyYWEiLAogICAgICAgICAgICAibm9kZV9pZCI6ICJNRFE2VlhObGNqVXhPVFExTmprPSIsCiAgICAgICAgICAgICJvcmdhbml6YXRpb25zX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL29yZ3MiLAogICAgICAgICAgICAicmVjZWl2ZWRfZXZlbnRzX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL3JlY2VpdmVkX2V2ZW50cyIsCiAgICAgICAgICAgICJyZXBvc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9yZXBvcyIsCiAgICAgICAgICAgICJzaXRlX2FkbWluIjogZmFsc2UsCiAgICAgICAgICAgICJzdGFycmVkX3VybCI6ICJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2FzcmFhL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLAogICAgICAgICAgICAic3Vic2NyaXB0aW9uc191cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYS9zdWJzY3JpcHRpb25zIiwKICAgICAgICAgICAgInR5cGUiOiAiVXNlciIsCiAgICAgICAgICAgICJ1cmwiOiAiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9hc3JhYSIKICAgICAgICAgIH0sCiAgICAgICAgICAid29ya2Zsb3ciOiAiLmdpdGh1Yi93b3JrZmxvd3Mvc2xzYS1yZXVzYWJsZS55YW1sIgogICAgICAgIH0sCiAgICAgICAgIkdJVEhVQl9SRUYiOiAicmVmcy9oZWFkcy9icmFuY2gzIiwKICAgICAgICAiR0lUSFVCX1JFRl9UWVBFIjogImJyYW5jaCIsCiAgICAgICAgIkdJVEhVQl9SVU5fQVRURU1QVCI6ICIxIiwKICAgICAgICAiR0lUSFVCX1JOTl9JRCI6ICIyMjU5NzA5MDc5IiwKICAgICAgICAiR0lUSFVCX1JVTl9OVU1CRVIiOiAiMTI3IiwKICAgICAgICAiR0lUSFVCX1NIQSI6ICJhMjg4MGQ2NGQxMmIyOTU3NjE4OTlkNTIzZTMzYzIyNjcwOTgyMDU0IiwKICAgICAgICAiSU1BR0VfT1MiOiAidWJ1bnR1MjAiCiAgICAgIH0KICAgIH0sCiAgICAicnVuRGV0YWlscyI6IHsKICAgICAgImJ1aWxkZXIiOiB7CiAgICAgICAgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIKICAgICAgfSwKICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIgogICAgICB9CiAgICB9CiAgfQp9Cg==", + "signatures": [ + { + "keyid": "", + "sig": "MEQCIBO/t/MjFal4P4pPgklZu5LF/1gA1761TB42NQH5tJ1aAiAVwD7Wn2zLNz9KNn99NGfU1jwumMhFWIpJqKpBseYHsA==" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl index 53709ea94..31393cc9e 100644 --- a/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl +++ b/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl @@ -1 +1,10 @@ -{ "payloadType": "application/vnd.in-toto+json", "payload": "eyAiX3R5cGUiOiAiaHR0cHM6Ly9pbi10b3RvLmlvL1N0YXRlbWVudC92MC4xIiwgInN1YmplY3QiOiBbIHsgImRpZ2VzdCI6IHsgInNoYTEiOiAiNDUwNjI5MGUyZThmZWIxZjM0YjI3YTA0NGY3Y2M4NjNjODMwZWY2YiIgfSwgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0IiB9IF0sICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MSIsICJwcmVkaWNhdGUiOiB7ICJidWlsZERlZmluaXRpb24iOiB7ICJidWlsZE5vdFNMU0EiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJBY3Rpb25zV29ya2Zsb3dAdjEiLCAiZXh0ZXJuYWxQYXJhbWF0ZXJzIjogeyAic291cmNlIjogeyAidXJpIjogImdpdCtodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlIiwgImRpZ2VzdCI6IHsgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIgfSB9IH0gfSwgInJ1bkRldGFpbHMiOiB7ICJidWlsZGVyIjogeyAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIiB9LCAibWV0YWRhdGEiOiB7ICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIiB9IH0gfSB9Cg==", "signatures": [ { "keyid": "", "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" } ] } +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGExIjogIjQ1MDYyOTBlMmU4ZmViMWYzNGIyN2EwNDRmN2NjODYzYzgzMGVmNmIiCiAgICAgIH0sCiAgICAgICJuYW1lIjogImJpbmFyeS1saW51eC1hbWQ2NCIKICAgIH0KICBdLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZERlZmluaXRpb24iOiB7CiAgICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJBY3Rpb25zV29ya2Zsb3dAdjEiLAogICAgICAiZXh0ZXJuYWxQYXJhbWV0ZXJzIjogewogICAgICAgICJzb3VyY2UiOiB7CiAgICAgICAgICAidXJpIjogImdpdCtodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlIiwKICAgICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAgICJzaGExIjogIjRlNmM1ZjZkMGI0YTEyNmZhMjM3M2Q3ZTU3YjdhMGFmMDUxMDg3OTEiCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgInJ1bkRldGFpbHMiOiB7CiAgICAgICJidWlsZGVyIjogewogICAgICAgICJpZCI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1Ykhvc3RlZEFjdGlvbnNAdjEiCiAgICAgIH0sCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiaW52b2NhdGlvbklkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvYWN0aW9ucy9ydW5zLzQxMzU0NjM3NDEvYXR0ZW1wdHMvMSIKICAgICAgfQogICAgfQogIH0KfQo=", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl index c273f99c3..f843bcc84 100644 --- a/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl +++ b/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl @@ -1 +1,10 @@ -{ "payloadType": "application/vnd.in-toto+json", "payload": "eyAiX3R5cGUiOiAiaHR0cHM6Ly9pbi10b3RvLmlvL1N0YXRlbWVudC92MC4xIiwgInN1YmplY3QiOiBbXSwgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwgInByZWRpY2F0ZSI6IHsgImJ1aWxkRGVmaW5pdGlvbiI6IHsgImJ1aWxkTm90U0xTQSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsICJleHRlcm5hbFBhcmFtYXRlcnMiOiB7ICJzb3VyY2UiOiB7ICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLCAiZGlnZXN0IjogeyAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIiB9IH0gfSB9LCAicnVuRGV0YWlscyI6IHsgImJ1aWxkZXIiOiB7ICJpZCI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1Ykhvc3RlZEFjdGlvbnNAdjEiIH0sICJtZXRhZGF0YSI6IHsgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiIH0gfSB9IH0K", "signatures": [ { "keyid": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGRUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgfSwKICAgICJpbnZvY2F0aW9uIjogewogICAgICAiY29uZmlnU291cmNlIjogewogICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAiU0hBMSI6ICI0NTA2MjkwZTJlOGZlYjFmMzRiMjdhMDQ0ZjdjYzg2M2M4MzBlZjZiIgogICAgICAgIH0sCiAgICAgICAgImVudHJ5UG9pbnQiOiAiVGVzdCBTTFNBIiwKICAgICAgICAidXJpIjogImdpdCthc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0LmdpdCIKICAgICAgfSwKICAgICAgImVudmlyb25tZW50IjogewogICAgICAgICJhcmNoIjogImFtZDY0IiwKICAgICAgICAiZW52IjogewogICAgICAgICAgIkdJVEhVQl9FVkVOVF9OQU1FIjogIndvcmtmbG93X2Rpc3BhdGNoIiwKICAgICAgICAgICJHSVRIVUJfUlVOX0lEIjogIjE4OTM3OTkyMjAiLAogICAgICAgICAgIkdJVEhVQl9SVU5fTlVNQkVSIjogIjc2IgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJtYXRlcmlhbHMiOiBbCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgIlNIQTEiOiAiNDUwNjI5MGUyZThmZWIxZjM0YjI3YTA0NGY3Y2M4NjNjODMwZWY2YiIKICAgICAgICB9LAogICAgICAgICJ1cmkiOiAiZ2l0K2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IgogICAgICB9CiAgICBdCiAgfSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCn0K", "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" } ] } +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogW10sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwKICAicHJlZGljYXRlIjogewogICAgImJ1aWxkRGVmaW5pdGlvbiI6IHsKICAgICAgImJ1aWxkVHlwZSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsCiAgICAgICJleHRlcm5hbFBhcmFtZXRlcnMiOiB7CiAgICAgICAgInNvdXJjZSI6IHsKICAgICAgICAgICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLAogICAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICAgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAicnVuRGV0YWlscyI6IHsKICAgICAgImJ1aWxkZXIiOiB7CiAgICAgICAgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIKICAgICAgfSwKICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIgogICAgICB9CiAgICB9CiAgfQp9Cg==", + "signatures": [ + { + "keyid": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGRUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgfSwKICAgICJpbnZvY2F0aW9uIjogewogICAgICAiY29uZmlnU291cmNlIjogewogICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAiU0hBMSI6ICI0NTA2MjkwZTJlOGZlYjFmMzRiMjdhMDQ0ZjdjYzg2M2M4MzBlZjZiIgogICAgICAgIH0sCiAgICAgICAgImVudHJ5UG9pbnQiOiAiVGVzdCBTTFNBIiwKICAgICAgICAidXJpIjogImdpdCthc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0LmdpdCIKICAgICAgfSwKICAgICAgImVudmlyb25tZW50IjogewogICAgICAgICJhcmNoIjogImFtZDY0IiwKICAgICAgICAiZW52IjogewogICAgICAgICAgIkdJVEhVQl9FVkVOVF9OQU1FIjogIndvcmtmbG93X2Rpc3BhdGNoIiwKICAgICAgICAgICJHSVRIVUJfUlVOX0lEIjogIjE4OTM3OTkyMjAiLAogICAgICAgICAgIkdJVEhVQl9SVU5fTlVNQkVSIjogIjc2IgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJtYXRlcmlhbHMiOiBbCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgIlNIQTEiOiAiNDUwNjI5MGUyZThmZWIxZjM0YjI3YTA0NGY3Y2M4NjNjODMwZWY2YiIKICAgICAgICB9LAogICAgICAgICJ1cmkiOiAiZ2l0K2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IgogICAgICB9CiAgICBdCiAgfSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCn0K", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl index 05841b706..7004ca4ff 100644 --- a/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl +++ b/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl @@ -1 +1,10 @@ -{ "payloadType": "application/vnd.in-toto+json", "payload": "eyAiX3R5cGUiOiAiaHR0cHM6Ly9pbi10b3RvLmlvL1N0YXRlbWVudC92MC4xIiwgInN1YmplY3QiOiBbIHsgImRpZ2VzdCI6IHsgInNoYTI1NiI6ICIwMWU3ZTRmYTcxNjg2NTM4NDQwMDEyZWUzNmEyNjM0ZGJhYTE5ZGYyZGQxNmE0NjZmNTI0MTFmYjM0OGJiYzRlIiB9LCAibmFtZSI6ICJiaW5hcnktbGludXgtYW1kNjQtMSIgfSwgeyAiZGlnZXN0IjogeyAic2hhMjU2IjogIjAyZTdlNGZhNzE2ODY1Mzg0NDAwMTJlZTM2YTI2MzRkYmFhMTlkZjJkZDE2YTQ2NmY1MjQxMWZiMzQ4YmJjNGUiIH0sICJuYW1lIjogImJpbmFyeS1saW51eC1hbWQ2NC0yIiB9LCB7ICJkaWdlc3QiOiB7ICJzaGEyNTYiOiAiMDNlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIgfSwgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTMiIH0gXSwgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwgInByZWRpY2F0ZSI6IHsgImJ1aWxkRGVmaW5pdGlvbiI6IHsgImJ1aWxkTm90U0xTQSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsICJleHRlcm5hbFBhcmFtYXRlcnMiOiB7ICJzb3VyY2UiOiB7ICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLCAiZGlnZXN0IjogeyAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIiB9IH0gfSB9LCAicnVuRGV0YWlscyI6IHsgImJ1aWxkZXIiOiB7ICJpZCI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1Ykhvc3RlZEFjdGlvbnNAdjEiIH0sICJtZXRhZGF0YSI6IHsgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiIH0gfSB9IH0K", "signatures": [ { "keyid": "", "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" } ] } +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDFlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTEiCiAgICB9LAogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDJlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTIiCiAgICB9LAogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDNlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTMiCiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGREZWZpbml0aW9uIjogewogICAgICAiYnVpbGRUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICAgImV4dGVybmFsUGFyYW1ldGVycyI6IHsKICAgICAgICAic291cmNlIjogewogICAgICAgICAgInVyaSI6ICJnaXQraHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZSIsCiAgICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgICAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJydW5EZXRhaWxzIjogewogICAgICAiYnVpbGRlciI6IHsKICAgICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgICB9LAogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiCiAgICAgIH0KICAgIH0KICB9Cn0K", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl index 75f039815..b79b74be1 100644 --- a/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl +++ b/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl @@ -1 +1,10 @@ -{ "payloadType": "application/vnd.in-toto+json", "payload": "eyAiX3R5cGUiOiAiaHR0cHM6Ly9pbi10b3RvLmlvL1N0YXRlbWVudC92MC4xIiwgInN1YmplY3QiOiBbIHsgImRpZ2VzdCI6IHsgInNoYTI1NiI6ICIwYWU3ZTRmYTcxNjg2NTM4NDQwMDEyZWUzNmEyNjM0ZGJhYTE5ZGYyZGQxNmE0NjZmNTI0MTFmYjM0OGJiYzRlIiB9LCAibmFtZSI6ICJiaW5hcnktbGludXgtYW1kNjQiIH0gXSwgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwgInByZWRpY2F0ZSI6IHsgImJ1aWxkRGVmaW5pdGlvbiI6IHsgImJ1aWxkVHlwZSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsICJleHRlcm5hbFBhcmFtYXRlcnMiOiB7ICJzb3VyY2UiOiB7ICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLCAiZGlnZXN0IjogeyAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIiB9IH0gfSB9LCAicnVuRGV0YWlscyI6IHsgImJ1aWxkZXIiOiB7ICJpZCI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1Ykhvc3RlZEFjdGlvbnNAdjEiIH0sICJtZXRhZGF0YSI6IHsgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiIH0gfSB9IH0K", "signatures": [ { "keyid": "", "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" } ] } +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMGFlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0IgogICAgfQogIF0sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxIiwKICAicHJlZGljYXRlIjogewogICAgImJ1aWxkRGVmaW5pdGlvbiI6IHsKICAgICAgImJ1aWxkVHlwZSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsCiAgICAgICJleHRlcm5hbFBhcmFtZXRlcnMiOiB7CiAgICAgICAgInNvdXJjZSI6IHsKICAgICAgICAgICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLAogICAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICAgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAicnVuRGV0YWlscyI6IHsKICAgICAgImJ1aWxkZXIiOiB7CiAgICAgICAgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIKICAgICAgfSwKICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIgogICAgICB9CiAgICB9CiAgfQp9Cg==", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/trusted_root.go b/verifiers/internal/gha/trusted_root.go index 0ebf5b480..faae2a464 100644 --- a/verifiers/internal/gha/trusted_root.go +++ b/verifiers/internal/gha/trusted_root.go @@ -4,6 +4,7 @@ import ( "context" "crypto/x509" "fmt" + "sync/atomic" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/pkg/cosign" @@ -26,7 +27,7 @@ type TrustedRoot struct { FulcioIntermediates *x509.CertPool } -func GetTrustedRoot(ctx context.Context) (*TrustedRoot, error) { +func getTrustedRoot(ctx context.Context) (*TrustedRoot, error) { rekorPubKeys, err := cosign.GetRekorPubs(ctx) if err != nil { return nil, fmt.Errorf("%w: %s", serrors.ErrorRekorPubKey, err) @@ -56,3 +57,20 @@ func GetTrustedRoot(ctx context.Context) (*TrustedRoot, error) { CTPubKeys: ctPubKeys, }, nil } + +// Cache the TUF roots to reduce traffic and read contention on the cached file. +var manager atomic.Value + +func TrustedRootSingleton(ctx context.Context) (*TrustedRoot, error) { + root := manager.Load() + if root != nil { + return root.(*TrustedRoot), nil + } + trustedRoot, err := getTrustedRoot(ctx) + if err != nil { + return nil, err + } + + manager.Store(trustedRoot) + return trustedRoot, nil +} diff --git a/verifiers/internal/gha/verifier.go b/verifiers/internal/gha/verifier.go index 210c76c1d..34b6d7cbb 100644 --- a/verifiers/internal/gha/verifier.go +++ b/verifiers/internal/gha/verifier.go @@ -72,8 +72,8 @@ func verifyEnvAndCert(env *dsse.Envelope, } fmt.Fprintf(os.Stderr, "Verified build using builder https://github.com%s at commit %s\n", - workflowInfo.JobWobWorkflowRef, - workflowInfo.CallerHash) + workflowInfo.SubjectWorkflowRef, + workflowInfo.SourceSha1) // Return verified provenance. r, err := base64.StdEncoding.DecodeString(env.Payload) if err != nil { @@ -125,6 +125,7 @@ func verifyNpmEnvAndCert(env *dsse.Envelope, } // WARNING: builderID may be empty if it's not a trusted reusable builder workflow. + isTrustedBuilder := false if trustedBuilderID != nil { // We only support builders built using the BYOB framework. // The builder is guaranteed to be delegatorGenericReusableWorkflow, since this is the builder @@ -132,6 +133,11 @@ func verifyNpmEnvAndCert(env *dsse.Envelope, // The delegator workflow will set the builder ID to the caller's path, // which is what users match against. provenanceOpts.ExpectedBuilderID = *builderOpts.ExpectedID + + if workflowInfo.SubjectHosted != nil && *workflowInfo.SubjectHosted != HostedGitHub { + return nil, fmt.Errorf("%w: self hosted re-usable workflow", serrors.ErrorMismatchBuilderID) + } + isTrustedBuilder = true } else { // NOTE: if the user created provenance using a re-usable workflow // that does not integrate with the BYOB framework, this code will be run. @@ -143,27 +149,45 @@ func verifyNpmEnvAndCert(env *dsse.Envelope, // We may add support for verifying provenance from arbitrary re-usable workflows // later; which may be useful for org-level builders. - // The builder.id is set to builderGitHubRunnerID by the npm CLI. - trustedBuilderID, err = utils.TrustedBuilderIDNew(builderGitHubRunnerID, false) + // TODO(https://github.com/gh-community/npm-provenance-private-beta-community/issues/9#issuecomment-1516685721): + // Allow the user to provide one of 3 builders: self-hosted, github-hosted and legacy github-hosted. + // Verify that the value provided is consistent with certificate information. + + if workflowInfo.SubjectHosted == nil { + return nil, fmt.Errorf("%w: hosted status unknonwn", serrors.ErrorNotSupported) + } + switch *builderOpts.ExpectedID { + case builderLegacyGitHubRunnerID, builderGitHubHostedRunnerID: + if *workflowInfo.SubjectHosted != HostedGitHub { + return nil, fmt.Errorf("%w: re-usable workflow is self-hosted", serrors.ErrorMismatchBuilderID) + } + case builderSelfHostedRunnerID: + if *workflowInfo.SubjectHosted != HostedSelf { + return nil, fmt.Errorf("%w: re-usable workflow is GitHub-hosted", serrors.ErrorMismatchBuilderID) + } + default: + return nil, fmt.Errorf("%w: builder %v. Expected one of %v, %v", serrors.ErrorNotSupported, *builderOpts.ExpectedID, + builderSelfHostedRunnerID, builderGitHubHostedRunnerID) + } + + trustedBuilderID, err = utils.TrustedBuilderIDNew(*builderOpts.ExpectedID, false) if err != nil { return nil, err } - if err := trustedBuilderID.MatchesLoose(*builderOpts.ExpectedID, false); err != nil { - return nil, fmt.Errorf("%w", err) - } + // On GitHub we only support the default GitHub runner builder. - provenanceOpts.ExpectedBuilderID = builderGitHubRunnerID + provenanceOpts.ExpectedBuilderID = *builderOpts.ExpectedID } // Verify properties of the SLSA provenance. // Unpack and verify info in the provenance, including the Subject Digest. - if err := VerifyNpmPackageProvenance(env, provenanceOpts); err != nil { + if err := VerifyNpmPackageProvenance(env, workflowInfo, provenanceOpts, isTrustedBuilder); err != nil { return nil, err } fmt.Fprintf(os.Stderr, "Verified build using builder %s at commit %s\n", trustedBuilderID.String(), - workflowInfo.CallerHash) + workflowInfo.SourceSha1) return trustedBuilderID, nil } @@ -185,7 +209,7 @@ func (v *GHAVerifier) VerifyArtifact(ctx context.Context, return nil, nil, err } - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { return nil, nil, err } @@ -214,7 +238,7 @@ func (v *GHAVerifier) VerifyImage(ctx context.Context, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) { /* Retrieve any valid signed attestations that chain up to Fulcio root CA. */ - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { return nil, nil, err } @@ -277,7 +301,7 @@ func (v *GHAVerifier) VerifyNpmPackage(ctx context.Context, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) { - trustedRoot, err := GetTrustedRoot(ctx) + trustedRoot, err := TrustedRootSingleton(ctx) if err != nil { return nil, nil, err }