Skip to content

Commit

Permalink
Add "Source Repository Visibility At Signing" ext
Browse files Browse the repository at this point in the history
Adding a new Fulcio cert extension: "Source Repository Visibility At Signing"

Includes the source visibility at the time of signing/creating the
certificate for GitHub Actions (backed by the `repository_visibility`
clam in the GHA ID token). The plan is for GitLab to add a backing ID
token claim to support this extension in the next few weeks.

TL'DR
- Attesting to source visibility in the certificate means we can verify
  wether a provenance attestation came from a public/private source
  repository without performing a network request to the source
  repository at the time of signing
- If you want to verify source visibility some time after signing you
  will need to make a unauthenticated network request to the source
  repository uri
- npm will start to verify this extension value (if it's set) at the
  time of publish and rejecting any public packages with provenance that
  come from a private source repository
  - npm will also perform a just-in-time reachability checks to the
    source repository/commit when viewing a package on npmjs.com
- "Source Repository Visibility At Signing" extension will be optional in
Fulcio to allow CI systems to omit it if they don't have access to this
info

See full discussion here:
#1263

Signed-off-by: Philip Harrison <philip@mailharrison.com>
  • Loading branch information
feelepxyz committed Jul 17, 2023
1 parent cbe0910 commit 6237781
Show file tree
Hide file tree
Showing 7 changed files with 560 additions and 474 deletions.
6 changes: 6 additions & 0 deletions docs/oid-info.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Nice-to-haves:
- MAY include claim to support: `Build Config Digest`
- MAY include claim to support: `Build Trigger`
- MAY include claim to support: `Run Invocation URI`
- MAY include claim to support: `Source Repository Visibility At Signing`

## Terminology

Expand Down Expand Up @@ -177,6 +178,10 @@ Event or action that initiated the build. For example: `push`.

Run Invocation URL to uniquely identify the build execution. SHOULD be fully qualified. For example: `https://github.com/example/repository/actions/runs/1536140711/attempts/1`.

### 1.3.6.1.4.1.57264.1.22 | Source Repository Visibility At Signing

Source repository visibility at the time of signing the certificate. MAY be empty if there is no Source Repository Visibility information available. For example: `private` or `public`.

## 1.3.6.1.4.1.57264.2 | Policy OID for Sigstore Timestamp Authority

Not used by Fulcio. This specifies the policy OID for the [timestamp authority](https://github.com/sigstore/timestamp-authority)
Expand Down Expand Up @@ -204,6 +209,7 @@ that Sigstore operates.
| workflow_sha | ci_config_sha ([WIP][gitlab-wip-cliams]) | ?? | ?? | Build Config Digest | An immutable reference to the specific version of the top-level build instructions. Should include the digest type followed by the digest, e.g. `sha1:abc123`. |
| event_name | pipeline_source | ?? | ?? | Build Trigger | The event or action that triggered the build. |
| server_url + repository + "/actions/runs/" + run_id + "/attempts/" + run_attempt | server_url + project_path + /-/jobs/ + job_id | ?? | ?? | Run Invocation URI | An immutable identifier that can uniquely identify the build execution |
| repository_visibility | ?? | ?? | ?? | Source Repository Visibility At Signing | Source repository visibility at the time of signing the certificate |

[github-oidc-doc]: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token
[oid-link]: http://oid-info.com/get/1.3.6.1.4.1.57264
Expand Down
44 changes: 31 additions & 13 deletions pkg/certificate/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@ var (
OIDIssuerV2 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 8}

// CI extensions
OIDBuildSignerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}
OIDBuildSignerDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}
OIDRunnerEnvironment = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}
OIDSourceRepositoryURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}
OIDSourceRepositoryDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}
OIDSourceRepositoryRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}
OIDSourceRepositoryIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}
OIDSourceRepositoryOwnerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}
OIDSourceRepositoryOwnerIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}
OIDBuildConfigURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}
OIDBuildConfigDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}
OIDBuildTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}
OIDRunInvocationURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}
OIDBuildSignerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}
OIDBuildSignerDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}
OIDRunnerEnvironment = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}
OIDSourceRepositoryURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}
OIDSourceRepositoryDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}
OIDSourceRepositoryRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}
OIDSourceRepositoryIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}
OIDSourceRepositoryOwnerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}
OIDSourceRepositoryOwnerIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}
OIDBuildConfigURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}
OIDBuildConfigDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}
OIDBuildTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}
OIDRunInvocationURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}
OIDSourceRepositoryVisibilityAtSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}
)

// Extensions contains all custom x509 extensions defined by Fulcio
Expand Down Expand Up @@ -128,6 +129,9 @@ type Extensions struct {

// Run Invocation URL to uniquely identify the build execution.
RunInvocationURI string // 1.3.6.1.4.1.57264.1.21

// Source repository visibility at the time of signing the certificate.
SourceRepositoryVisibilityAtSigning string // 1.3.6.1.4.1.57264.1.22
}

func (e Extensions) Render() ([]pkix.Extension, error) {
Expand Down Expand Up @@ -320,6 +324,16 @@ func (e Extensions) Render() ([]pkix.Extension, error) {
Value: val,
})
}
if e.SourceRepositoryVisibilityAtSigning != "" {
val, err := asn1.MarshalWithParams(e.SourceRepositoryVisibilityAtSigning, "utf8")
if err != nil {
return nil, err
}
exts = append(exts, pkix.Extension{
Id: OIDSourceRepositoryVisibilityAtSigning,
Value: val,
})
}

return exts, nil
}
Expand Down Expand Up @@ -399,6 +413,10 @@ func parseExtensions(ext []pkix.Extension) (Extensions, error) {
if err := ParseDERString(e.Value, &out.RunInvocationURI); err != nil {
return Extensions{}, err
}
case e.Id.Equal(OIDSourceRepositoryVisibilityAtSigning):
if err := ParseDERString(e.Value, &out.SourceRepositoryVisibilityAtSigning); err != nil {
return Extensions{}, err
}
}
}

Expand Down
43 changes: 24 additions & 19 deletions pkg/certificate/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,26 @@ func TestExtensions(t *testing.T) {
},
`complete extensions list should create all extensions with correct OIDs`: {
Extensions: Extensions{
Issuer: "issuer", // OID 1.3.6.1.4.1.57264.1.1 and 1.3.6.1.4.1.57264.1.8
GithubWorkflowTrigger: "2", // OID 1.3.6.1.4.1.57264.1.2
GithubWorkflowSHA: "3", // OID 1.3.6.1.4.1.57264.1.3
GithubWorkflowName: "4", // OID 1.3.6.1.4.1.57264.1.4
GithubWorkflowRepository: "5", // OID 1.3.6.1.4.1.57264.1.5
GithubWorkflowRef: "6", // 1.3.6.1.4.1.57264.1.6
BuildSignerURI: "9", // 1.3.6.1.4.1.57264.1.9
BuildSignerDigest: "10", // 1.3.6.1.4.1.57264.1.10
RunnerEnvironment: "11", // 1.3.6.1.4.1.57264.1.11
SourceRepositoryURI: "12", // 1.3.6.1.4.1.57264.1.12
SourceRepositoryDigest: "13", // 1.3.6.1.4.1.57264.1.13
SourceRepositoryRef: "14", // 1.3.6.1.4.1.57264.1.14
SourceRepositoryIdentifier: "15", // 1.3.6.1.4.1.57264.1.15
SourceRepositoryOwnerURI: "16", // 1.3.6.1.4.1.57264.1.16
SourceRepositoryOwnerIdentifier: "17", // 1.3.6.1.4.1.57264.1.17
BuildConfigURI: "18", // 1.3.6.1.4.1.57264.1.18
BuildConfigDigest: "19", // 1.3.6.1.4.1.57264.1.19
BuildTrigger: "20", // 1.3.6.1.4.1.57264.1.20
RunInvocationURI: "21", // 1.3.6.1.4.1.57264.1.21
Issuer: "issuer", // OID 1.3.6.1.4.1.57264.1.1 and 1.3.6.1.4.1.57264.1.8
GithubWorkflowTrigger: "2", // OID 1.3.6.1.4.1.57264.1.2
GithubWorkflowSHA: "3", // OID 1.3.6.1.4.1.57264.1.3
GithubWorkflowName: "4", // OID 1.3.6.1.4.1.57264.1.4
GithubWorkflowRepository: "5", // OID 1.3.6.1.4.1.57264.1.5
GithubWorkflowRef: "6", // 1.3.6.1.4.1.57264.1.6
BuildSignerURI: "9", // 1.3.6.1.4.1.57264.1.9
BuildSignerDigest: "10", // 1.3.6.1.4.1.57264.1.10
RunnerEnvironment: "11", // 1.3.6.1.4.1.57264.1.11
SourceRepositoryURI: "12", // 1.3.6.1.4.1.57264.1.12
SourceRepositoryDigest: "13", // 1.3.6.1.4.1.57264.1.13
SourceRepositoryRef: "14", // 1.3.6.1.4.1.57264.1.14
SourceRepositoryIdentifier: "15", // 1.3.6.1.4.1.57264.1.15
SourceRepositoryOwnerURI: "16", // 1.3.6.1.4.1.57264.1.16
SourceRepositoryOwnerIdentifier: "17", // 1.3.6.1.4.1.57264.1.17
BuildConfigURI: "18", // 1.3.6.1.4.1.57264.1.18
BuildConfigDigest: "19", // 1.3.6.1.4.1.57264.1.19
BuildTrigger: "20", // 1.3.6.1.4.1.57264.1.20
RunInvocationURI: "21", // 1.3.6.1.4.1.57264.1.21
SourceRepositoryVisibilityAtSigning: "22", // 1.3.6.1.4.1.57264.1.22
},
Expect: []pkix.Extension{
{
Expand Down Expand Up @@ -138,6 +139,10 @@ func TestExtensions(t *testing.T) {
Id: OIDRunInvocationURI,
Value: marshalDERString(t, "21"),
},
{
Id: OIDSourceRepositoryVisibilityAtSigning,
Value: marshalDERString(t, "22"),
},
},
WantErr: false,
},
Expand Down
39 changes: 20 additions & 19 deletions pkg/identity/github/issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,26 @@ func TestIssuer(t *testing.T) {
Subject: "repo:sigstore/fulcio:ref:refs/heads/main",
}
claims, err := json.Marshal(map[string]interface{}{
"aud": "sigstore",
"event_name": "push",
"exp": 0,
"iss": "https://token.actions.githubusercontent.com",
"job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main",
"job_workflow_sha": "example-sha",
"ref": "refs/heads/main",
"repository": "sigstore/fulcio",
"repository_id": "12345",
"repository_owner": "username",
"repository_owner_id": "345",
"run_attempt": "1",
"run_id": "42",
"runner_environment": "cloud-hosted",
"sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sub": "repo:sigstore/fulcio:ref:refs/heads/main",
"workflow": "foo",
"workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main",
"workflow_sha": "example-sha-other",
"aud": "sigstore",
"event_name": "push",
"exp": 0,
"iss": "https://token.actions.githubusercontent.com",
"job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main",
"job_workflow_sha": "example-sha",
"ref": "refs/heads/main",
"repository": "sigstore/fulcio",
"repository_id": "12345",
"repository_owner": "username",
"repository_owner_id": "345",
"repository_visibility": "public",
"run_attempt": "1",
"run_id": "42",
"runner_environment": "cloud-hosted",
"sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sub": "repo:sigstore/fulcio:ref:refs/heads/main",
"workflow": "foo",
"workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main",
"workflow_sha": "example-sha-other",
})
if err != nil {
t.Fatal(err)
Expand Down
101 changes: 55 additions & 46 deletions pkg/identity/github/principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type workflowPrincipal struct {
// ID of the source repo
repositoryOwnerID string

// Visibility of the source repo
repositoryVisibility string

// Ref of top-level workflow that is running
workflowRef string

Expand All @@ -86,21 +89,22 @@ type workflowPrincipal struct {

func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.Principal, error) {
var claims struct {
JobWorkflowRef string `json:"job_workflow_ref"`
Sha string `json:"sha"`
EventName string `json:"event_name"`
Repository string `json:"repository"`
Workflow string `json:"workflow"`
Ref string `json:"ref"`
JobWorkflowSha string `json:"job_workflow_sha"`
RunnerEnvironment string `json:"runner_environment"`
RepositoryID string `json:"repository_id"`
RepositoryOwner string `json:"repository_owner"`
RepositoryOwnerID string `json:"repository_owner_id"`
WorkflowRef string `json:"workflow_ref"`
WorkflowSha string `json:"workflow_sha"`
RunID string `json:"run_id"`
RunAttempt string `json:"run_attempt"`
JobWorkflowRef string `json:"job_workflow_ref"`
Sha string `json:"sha"`
EventName string `json:"event_name"`
Repository string `json:"repository"`
Workflow string `json:"workflow"`
Ref string `json:"ref"`
JobWorkflowSha string `json:"job_workflow_sha"`
RunnerEnvironment string `json:"runner_environment"`
RepositoryID string `json:"repository_id"`
RepositoryOwner string `json:"repository_owner"`
RepositoryOwnerID string `json:"repository_owner_id"`
RepositoryVisibility string `json:"repository_visibility"`
WorkflowRef string `json:"workflow_ref"`
WorkflowSha string `json:"workflow_sha"`
RunID string `json:"run_id"`
RunAttempt string `json:"run_attempt"`
}
if err := token.Claims(&claims); err != nil {
return nil, err
Expand Down Expand Up @@ -139,6 +143,9 @@ func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (ident
if claims.RepositoryOwnerID == "" {
return nil, errors.New("missing repository_owner_id claim in ID token")
}
if claims.RepositoryVisibility == "" {
return nil, errors.New("missing repository_visibility claim in ID token")
}
if claims.WorkflowRef == "" {
return nil, errors.New("missing workflow_ref claim in ID token")
}
Expand All @@ -153,24 +160,25 @@ func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (ident
}

return &workflowPrincipal{
subject: token.Subject,
issuer: token.Issuer,
url: `https://github.com/`,
sha: claims.Sha,
eventName: claims.EventName,
repository: claims.Repository,
workflow: claims.Workflow,
ref: claims.Ref,
jobWorkflowRef: claims.JobWorkflowRef,
jobWorkflowSha: claims.JobWorkflowSha,
runnerEnvironment: claims.RunnerEnvironment,
repositoryID: claims.RepositoryID,
repositoryOwner: claims.RepositoryOwner,
repositoryOwnerID: claims.RepositoryOwnerID,
workflowRef: claims.WorkflowRef,
workflowSha: claims.WorkflowSha,
runID: claims.RunID,
runAttempt: claims.RunAttempt,
subject: token.Subject,
issuer: token.Issuer,
url: `https://github.com/`,
sha: claims.Sha,
eventName: claims.EventName,
repository: claims.Repository,
workflow: claims.Workflow,
ref: claims.Ref,
jobWorkflowRef: claims.JobWorkflowRef,
jobWorkflowSha: claims.JobWorkflowSha,
runnerEnvironment: claims.RunnerEnvironment,
repositoryID: claims.RepositoryID,
repositoryOwner: claims.RepositoryOwner,
repositoryOwnerID: claims.RepositoryOwnerID,
repositoryVisibility: claims.RepositoryVisibility,
workflowRef: claims.WorkflowRef,
workflowSha: claims.WorkflowSha,
runID: claims.RunID,
runAttempt: claims.RunAttempt,
}, nil
}

Expand Down Expand Up @@ -198,19 +206,20 @@ func (w workflowPrincipal) Embed(_ context.Context, cert *x509.Certificate) erro
GithubWorkflowRef: w.ref,
// END: Deprecated

BuildSignerURI: baseURL.JoinPath(w.jobWorkflowRef).String(),
BuildSignerDigest: w.jobWorkflowSha,
RunnerEnvironment: w.runnerEnvironment,
SourceRepositoryURI: baseURL.JoinPath(w.repository).String(),
SourceRepositoryDigest: w.sha,
SourceRepositoryRef: w.ref,
SourceRepositoryIdentifier: w.repositoryID,
SourceRepositoryOwnerURI: baseURL.JoinPath(w.repositoryOwner).String(),
SourceRepositoryOwnerIdentifier: w.repositoryOwnerID,
BuildConfigURI: baseURL.JoinPath(w.workflowRef).String(),
BuildConfigDigest: w.workflowSha,
BuildTrigger: w.eventName,
RunInvocationURI: baseURL.JoinPath(w.repository, "actions/runs", w.runID, "attempts", w.runAttempt).String(),
BuildSignerURI: baseURL.JoinPath(w.jobWorkflowRef).String(),
BuildSignerDigest: w.jobWorkflowSha,
RunnerEnvironment: w.runnerEnvironment,
SourceRepositoryURI: baseURL.JoinPath(w.repository).String(),
SourceRepositoryDigest: w.sha,
SourceRepositoryRef: w.ref,
SourceRepositoryIdentifier: w.repositoryID,
SourceRepositoryOwnerURI: baseURL.JoinPath(w.repositoryOwner).String(),
SourceRepositoryOwnerIdentifier: w.repositoryOwnerID,
BuildConfigURI: baseURL.JoinPath(w.workflowRef).String(),
BuildConfigDigest: w.workflowSha,
BuildTrigger: w.eventName,
RunInvocationURI: baseURL.JoinPath(w.repository, "actions/runs", w.runID, "attempts", w.runAttempt).String(),
SourceRepositoryVisibilityAtSigning: w.repositoryVisibility,
}.Render()
if err != nil {
return err
Expand Down

0 comments on commit 6237781

Please sign in to comment.