Skip to content

Commit a7a044b

Browse files
authored
Add "Source Repository Visibility At Signing" ext (#1279)
* Add "Source Repository Visibility At Signing" ext 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 <[email protected]> * Add GitLab project_visibility Signed-off-by: Philip Harrison <[email protected]> --------- Signed-off-by: Philip Harrison <[email protected]>
1 parent beb9c6b commit a7a044b

File tree

10 files changed

+690
-543
lines changed

10 files changed

+690
-543
lines changed

docs/oid-info.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Nice-to-haves:
3737
- MAY include claim to support: `Build Config Digest`
3838
- MAY include claim to support: `Build Trigger`
3939
- MAY include claim to support: `Run Invocation URI`
40+
- MAY include claim to support: `Source Repository Visibility At Signing`
4041

4142
## Terminology
4243

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

178179
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`.
179180

181+
### 1.3.6.1.4.1.57264.1.22 | Source Repository Visibility At Signing
182+
183+
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`.
184+
180185
## 1.3.6.1.4.1.57264.2 | Policy OID for Sigstore Timestamp Authority
181186

182187
Not used by Fulcio. This specifies the policy OID for the [timestamp authority](https://github.com/sigstore/timestamp-authority)
@@ -204,6 +209,7 @@ that Sigstore operates.
204209
| 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`. |
205210
| event_name | pipeline_source | ?? | ?? | Build Trigger | The event or action that triggered the build. |
206211
| 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 |
212+
| repository_visibility | project_visibility | ?? | ?? | Source Repository Visibility At Signing | Source repository visibility at the time of signing the certificate |
207213

208214
[github-oidc-doc]: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token
209215
[oid-link]: http://oid-info.com/get/1.3.6.1.4.1.57264

pkg/certificate/extensions.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,20 @@ var (
3939
OIDIssuerV2 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 8}
4040

4141
// CI extensions
42-
OIDBuildSignerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}
43-
OIDBuildSignerDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}
44-
OIDRunnerEnvironment = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}
45-
OIDSourceRepositoryURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}
46-
OIDSourceRepositoryDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}
47-
OIDSourceRepositoryRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}
48-
OIDSourceRepositoryIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}
49-
OIDSourceRepositoryOwnerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}
50-
OIDSourceRepositoryOwnerIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}
51-
OIDBuildConfigURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}
52-
OIDBuildConfigDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}
53-
OIDBuildTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}
54-
OIDRunInvocationURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}
42+
OIDBuildSignerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 9}
43+
OIDBuildSignerDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 10}
44+
OIDRunnerEnvironment = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 11}
45+
OIDSourceRepositoryURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 12}
46+
OIDSourceRepositoryDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 13}
47+
OIDSourceRepositoryRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 14}
48+
OIDSourceRepositoryIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 15}
49+
OIDSourceRepositoryOwnerURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 16}
50+
OIDSourceRepositoryOwnerIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 17}
51+
OIDBuildConfigURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 18}
52+
OIDBuildConfigDigest = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 19}
53+
OIDBuildTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 20}
54+
OIDRunInvocationURI = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 21}
55+
OIDSourceRepositoryVisibilityAtSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 22}
5556
)
5657

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

129130
// Run Invocation URL to uniquely identify the build execution.
130131
RunInvocationURI string // 1.3.6.1.4.1.57264.1.21
132+
133+
// Source repository visibility at the time of signing the certificate.
134+
SourceRepositoryVisibilityAtSigning string // 1.3.6.1.4.1.57264.1.22
131135
}
132136

133137
func (e Extensions) Render() ([]pkix.Extension, error) {
@@ -320,6 +324,16 @@ func (e Extensions) Render() ([]pkix.Extension, error) {
320324
Value: val,
321325
})
322326
}
327+
if e.SourceRepositoryVisibilityAtSigning != "" {
328+
val, err := asn1.MarshalWithParams(e.SourceRepositoryVisibilityAtSigning, "utf8")
329+
if err != nil {
330+
return nil, err
331+
}
332+
exts = append(exts, pkix.Extension{
333+
Id: OIDSourceRepositoryVisibilityAtSigning,
334+
Value: val,
335+
})
336+
}
323337

324338
return exts, nil
325339
}
@@ -399,6 +413,10 @@ func parseExtensions(ext []pkix.Extension) (Extensions, error) {
399413
if err := ParseDERString(e.Value, &out.RunInvocationURI); err != nil {
400414
return Extensions{}, err
401415
}
416+
case e.Id.Equal(OIDSourceRepositoryVisibilityAtSigning):
417+
if err := ParseDERString(e.Value, &out.SourceRepositoryVisibilityAtSigning); err != nil {
418+
return Extensions{}, err
419+
}
402420
}
403421
}
404422

pkg/certificate/extensions_test.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,26 @@ func TestExtensions(t *testing.T) {
3737
},
3838
`complete extensions list should create all extensions with correct OIDs`: {
3939
Extensions: Extensions{
40-
Issuer: "issuer", // OID 1.3.6.1.4.1.57264.1.1 and 1.3.6.1.4.1.57264.1.8
41-
GithubWorkflowTrigger: "2", // OID 1.3.6.1.4.1.57264.1.2
42-
GithubWorkflowSHA: "3", // OID 1.3.6.1.4.1.57264.1.3
43-
GithubWorkflowName: "4", // OID 1.3.6.1.4.1.57264.1.4
44-
GithubWorkflowRepository: "5", // OID 1.3.6.1.4.1.57264.1.5
45-
GithubWorkflowRef: "6", // 1.3.6.1.4.1.57264.1.6
46-
BuildSignerURI: "9", // 1.3.6.1.4.1.57264.1.9
47-
BuildSignerDigest: "10", // 1.3.6.1.4.1.57264.1.10
48-
RunnerEnvironment: "11", // 1.3.6.1.4.1.57264.1.11
49-
SourceRepositoryURI: "12", // 1.3.6.1.4.1.57264.1.12
50-
SourceRepositoryDigest: "13", // 1.3.6.1.4.1.57264.1.13
51-
SourceRepositoryRef: "14", // 1.3.6.1.4.1.57264.1.14
52-
SourceRepositoryIdentifier: "15", // 1.3.6.1.4.1.57264.1.15
53-
SourceRepositoryOwnerURI: "16", // 1.3.6.1.4.1.57264.1.16
54-
SourceRepositoryOwnerIdentifier: "17", // 1.3.6.1.4.1.57264.1.17
55-
BuildConfigURI: "18", // 1.3.6.1.4.1.57264.1.18
56-
BuildConfigDigest: "19", // 1.3.6.1.4.1.57264.1.19
57-
BuildTrigger: "20", // 1.3.6.1.4.1.57264.1.20
58-
RunInvocationURI: "21", // 1.3.6.1.4.1.57264.1.21
40+
Issuer: "issuer", // OID 1.3.6.1.4.1.57264.1.1 and 1.3.6.1.4.1.57264.1.8
41+
GithubWorkflowTrigger: "2", // OID 1.3.6.1.4.1.57264.1.2
42+
GithubWorkflowSHA: "3", // OID 1.3.6.1.4.1.57264.1.3
43+
GithubWorkflowName: "4", // OID 1.3.6.1.4.1.57264.1.4
44+
GithubWorkflowRepository: "5", // OID 1.3.6.1.4.1.57264.1.5
45+
GithubWorkflowRef: "6", // 1.3.6.1.4.1.57264.1.6
46+
BuildSignerURI: "9", // 1.3.6.1.4.1.57264.1.9
47+
BuildSignerDigest: "10", // 1.3.6.1.4.1.57264.1.10
48+
RunnerEnvironment: "11", // 1.3.6.1.4.1.57264.1.11
49+
SourceRepositoryURI: "12", // 1.3.6.1.4.1.57264.1.12
50+
SourceRepositoryDigest: "13", // 1.3.6.1.4.1.57264.1.13
51+
SourceRepositoryRef: "14", // 1.3.6.1.4.1.57264.1.14
52+
SourceRepositoryIdentifier: "15", // 1.3.6.1.4.1.57264.1.15
53+
SourceRepositoryOwnerURI: "16", // 1.3.6.1.4.1.57264.1.16
54+
SourceRepositoryOwnerIdentifier: "17", // 1.3.6.1.4.1.57264.1.17
55+
BuildConfigURI: "18", // 1.3.6.1.4.1.57264.1.18
56+
BuildConfigDigest: "19", // 1.3.6.1.4.1.57264.1.19
57+
BuildTrigger: "20", // 1.3.6.1.4.1.57264.1.20
58+
RunInvocationURI: "21", // 1.3.6.1.4.1.57264.1.21
59+
SourceRepositoryVisibilityAtSigning: "22", // 1.3.6.1.4.1.57264.1.22
5960
},
6061
Expect: []pkix.Extension{
6162
{
@@ -138,6 +139,10 @@ func TestExtensions(t *testing.T) {
138139
Id: OIDRunInvocationURI,
139140
Value: marshalDERString(t, "21"),
140141
},
142+
{
143+
Id: OIDSourceRepositoryVisibilityAtSigning,
144+
Value: marshalDERString(t, "22"),
145+
},
141146
},
142147
WantErr: false,
143148
},

pkg/identity/github/issuer_test.go

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,26 @@ func TestIssuer(t *testing.T) {
4545
Subject: "repo:sigstore/fulcio:ref:refs/heads/main",
4646
}
4747
claims, err := json.Marshal(map[string]interface{}{
48-
"aud": "sigstore",
49-
"event_name": "push",
50-
"exp": 0,
51-
"iss": "https://token.actions.githubusercontent.com",
52-
"job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main",
53-
"job_workflow_sha": "example-sha",
54-
"ref": "refs/heads/main",
55-
"repository": "sigstore/fulcio",
56-
"repository_id": "12345",
57-
"repository_owner": "username",
58-
"repository_owner_id": "345",
59-
"run_attempt": "1",
60-
"run_id": "42",
61-
"runner_environment": "cloud-hosted",
62-
"sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
63-
"sub": "repo:sigstore/fulcio:ref:refs/heads/main",
64-
"workflow": "foo",
65-
"workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main",
66-
"workflow_sha": "example-sha-other",
48+
"aud": "sigstore",
49+
"event_name": "push",
50+
"exp": 0,
51+
"iss": "https://token.actions.githubusercontent.com",
52+
"job_workflow_ref": "sigstore/fulcio/.github/workflows/foo.yaml@refs/heads/main",
53+
"job_workflow_sha": "example-sha",
54+
"ref": "refs/heads/main",
55+
"repository": "sigstore/fulcio",
56+
"repository_id": "12345",
57+
"repository_owner": "username",
58+
"repository_owner_id": "345",
59+
"repository_visibility": "public",
60+
"run_attempt": "1",
61+
"run_id": "42",
62+
"runner_environment": "cloud-hosted",
63+
"sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
64+
"sub": "repo:sigstore/fulcio:ref:refs/heads/main",
65+
"workflow": "foo",
66+
"workflow_ref": "sigstore/other/.github/workflows/foo.yaml@refs/heads/main",
67+
"workflow_sha": "example-sha-other",
6768
})
6869
if err != nil {
6970
t.Fatal(err)

pkg/identity/github/principal.go

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ type workflowPrincipal struct {
7171
// ID of the source repo
7272
repositoryOwnerID string
7373

74+
// Visibility of the source repo
75+
repositoryVisibility string
76+
7477
// Ref of top-level workflow that is running
7578
workflowRef string
7679

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

8790
func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.Principal, error) {
8891
var claims struct {
89-
JobWorkflowRef string `json:"job_workflow_ref"`
90-
Sha string `json:"sha"`
91-
EventName string `json:"event_name"`
92-
Repository string `json:"repository"`
93-
Workflow string `json:"workflow"`
94-
Ref string `json:"ref"`
95-
JobWorkflowSha string `json:"job_workflow_sha"`
96-
RunnerEnvironment string `json:"runner_environment"`
97-
RepositoryID string `json:"repository_id"`
98-
RepositoryOwner string `json:"repository_owner"`
99-
RepositoryOwnerID string `json:"repository_owner_id"`
100-
WorkflowRef string `json:"workflow_ref"`
101-
WorkflowSha string `json:"workflow_sha"`
102-
RunID string `json:"run_id"`
103-
RunAttempt string `json:"run_attempt"`
92+
JobWorkflowRef string `json:"job_workflow_ref"`
93+
Sha string `json:"sha"`
94+
EventName string `json:"event_name"`
95+
Repository string `json:"repository"`
96+
Workflow string `json:"workflow"`
97+
Ref string `json:"ref"`
98+
JobWorkflowSha string `json:"job_workflow_sha"`
99+
RunnerEnvironment string `json:"runner_environment"`
100+
RepositoryID string `json:"repository_id"`
101+
RepositoryOwner string `json:"repository_owner"`
102+
RepositoryOwnerID string `json:"repository_owner_id"`
103+
RepositoryVisibility string `json:"repository_visibility"`
104+
WorkflowRef string `json:"workflow_ref"`
105+
WorkflowSha string `json:"workflow_sha"`
106+
RunID string `json:"run_id"`
107+
RunAttempt string `json:"run_attempt"`
104108
}
105109
if err := token.Claims(&claims); err != nil {
106110
return nil, err
@@ -139,6 +143,9 @@ func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (ident
139143
if claims.RepositoryOwnerID == "" {
140144
return nil, errors.New("missing repository_owner_id claim in ID token")
141145
}
146+
if claims.RepositoryVisibility == "" {
147+
return nil, errors.New("missing repository_visibility claim in ID token")
148+
}
142149
if claims.WorkflowRef == "" {
143150
return nil, errors.New("missing workflow_ref claim in ID token")
144151
}
@@ -153,24 +160,25 @@ func WorkflowPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (ident
153160
}
154161

155162
return &workflowPrincipal{
156-
subject: token.Subject,
157-
issuer: token.Issuer,
158-
url: `https://github.com/`,
159-
sha: claims.Sha,
160-
eventName: claims.EventName,
161-
repository: claims.Repository,
162-
workflow: claims.Workflow,
163-
ref: claims.Ref,
164-
jobWorkflowRef: claims.JobWorkflowRef,
165-
jobWorkflowSha: claims.JobWorkflowSha,
166-
runnerEnvironment: claims.RunnerEnvironment,
167-
repositoryID: claims.RepositoryID,
168-
repositoryOwner: claims.RepositoryOwner,
169-
repositoryOwnerID: claims.RepositoryOwnerID,
170-
workflowRef: claims.WorkflowRef,
171-
workflowSha: claims.WorkflowSha,
172-
runID: claims.RunID,
173-
runAttempt: claims.RunAttempt,
163+
subject: token.Subject,
164+
issuer: token.Issuer,
165+
url: `https://github.com/`,
166+
sha: claims.Sha,
167+
eventName: claims.EventName,
168+
repository: claims.Repository,
169+
workflow: claims.Workflow,
170+
ref: claims.Ref,
171+
jobWorkflowRef: claims.JobWorkflowRef,
172+
jobWorkflowSha: claims.JobWorkflowSha,
173+
runnerEnvironment: claims.RunnerEnvironment,
174+
repositoryID: claims.RepositoryID,
175+
repositoryOwner: claims.RepositoryOwner,
176+
repositoryOwnerID: claims.RepositoryOwnerID,
177+
repositoryVisibility: claims.RepositoryVisibility,
178+
workflowRef: claims.WorkflowRef,
179+
workflowSha: claims.WorkflowSha,
180+
runID: claims.RunID,
181+
runAttempt: claims.RunAttempt,
174182
}, nil
175183
}
176184

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

201-
BuildSignerURI: baseURL.JoinPath(w.jobWorkflowRef).String(),
202-
BuildSignerDigest: w.jobWorkflowSha,
203-
RunnerEnvironment: w.runnerEnvironment,
204-
SourceRepositoryURI: baseURL.JoinPath(w.repository).String(),
205-
SourceRepositoryDigest: w.sha,
206-
SourceRepositoryRef: w.ref,
207-
SourceRepositoryIdentifier: w.repositoryID,
208-
SourceRepositoryOwnerURI: baseURL.JoinPath(w.repositoryOwner).String(),
209-
SourceRepositoryOwnerIdentifier: w.repositoryOwnerID,
210-
BuildConfigURI: baseURL.JoinPath(w.workflowRef).String(),
211-
BuildConfigDigest: w.workflowSha,
212-
BuildTrigger: w.eventName,
213-
RunInvocationURI: baseURL.JoinPath(w.repository, "actions/runs", w.runID, "attempts", w.runAttempt).String(),
209+
BuildSignerURI: baseURL.JoinPath(w.jobWorkflowRef).String(),
210+
BuildSignerDigest: w.jobWorkflowSha,
211+
RunnerEnvironment: w.runnerEnvironment,
212+
SourceRepositoryURI: baseURL.JoinPath(w.repository).String(),
213+
SourceRepositoryDigest: w.sha,
214+
SourceRepositoryRef: w.ref,
215+
SourceRepositoryIdentifier: w.repositoryID,
216+
SourceRepositoryOwnerURI: baseURL.JoinPath(w.repositoryOwner).String(),
217+
SourceRepositoryOwnerIdentifier: w.repositoryOwnerID,
218+
BuildConfigURI: baseURL.JoinPath(w.workflowRef).String(),
219+
BuildConfigDigest: w.workflowSha,
220+
BuildTrigger: w.eventName,
221+
RunInvocationURI: baseURL.JoinPath(w.repository, "actions/runs", w.runID, "attempts", w.runAttempt).String(),
222+
SourceRepositoryVisibilityAtSigning: w.repositoryVisibility,
214223
}.Render()
215224
if err != nil {
216225
return err

0 commit comments

Comments
 (0)