@@ -6,6 +6,7 @@ package attest
66import (
77 "errors"
88 "fmt"
9+ "regexp"
910
1011 "github.com/carabiner-dev/attestation"
1112 "github.com/carabiner-dev/signer"
@@ -15,12 +16,23 @@ import (
1516)
1617
1718type VerificationOptions struct {
19+ // ExpectedIssuer is the OIDC issuer of the certificates signing the
20+ // attestations. It is required, no identity is accepted without it.
1821 ExpectedIssuer string
19- ExpectedSan string
2022
21- // AlternateSans lists additional signer identities accepted when
22- // verifying attestations. It carries the pre-rename workflow identity
23- // while repositories still have attestations signed with it.
23+ // ExpectedSan pins the signer identity to an exact subject alternative
24+ // name. When set, ExpectedSanPrefix is ignored.
25+ ExpectedSan string
26+
27+ // ExpectedSanPrefix accepts any signer identity starting with the
28+ // prefix. Users pin the provenance workflow to different tags and
29+ // digests, so the git reference ending its identity varies.
30+ ExpectedSanPrefix string
31+
32+ // AlternateSans lists additional signer identities accepted (exactly)
33+ // when verifying attestations. It carries the identities of the
34+ // workflows that signed attestations before the actions moved to their
35+ // current repository.
2436 //
2537 // See https://github.com/slsa-framework/source-tool/issues/255
2638 AlternateSans []string
@@ -30,24 +42,88 @@ const (
3042 // ExpectedIssuer is the OIDC issuer found in the sigstore bundles
3143 ExpectedIssuer = "https://token.actions.githubusercontent.com"
3244
33- // Expected SAN is the expected identity of the workflow signing the
34- // provenance and VSAs.
35- ExpectedSan = "https://github.com/slsa-framework/source-actions/.github/workflows/compute_slsa_source.yml@refs/heads/main"
45+ // ExpectedSanPrefix is the prefix of the identity of the reusable workflow
46+ // signing the provenance and VSAs. The full identity ends with the git
47+ // reference the workflow was pinned to, which varies across users and
48+ // releases.
49+ ExpectedSanPrefix = "https://github.com/slsa-framework/actions/.github/workflows/compute_slsa_source.yml@"
3650
37- // OldExpectedSan is the old singer identity before splitting out the actions to their own repo
38- // this constant is part of a compatibility hack that should be reverted once the latests attestations
39- // of the repos are signed with the new identity.
51+ // LegacySourceActionsSan is the identity of the workflow that signed
52+ // attestations while the actions lived in slsa-framework/source-actions.
53+ LegacySourceActionsSan = "https://github.com/slsa-framework/source-actions/.github/workflows/compute_slsa_source.yml@refs/heads/main"
54+
55+ // LegacyPocSan is the identity of the workflow that signed attestations
56+ // before the actions were split out of the slsa-source-poc repository.
4057 //
4158 // See https://github.com/slsa-framework/source-tool/issues/255
42- OldExpectedSan = "https://github.com/slsa-framework/slsa-source-poc/.github/workflows/compute_slsa_source.yml@refs/heads/main"
59+ LegacyPocSan = "https://github.com/slsa-framework/slsa-source-poc/.github/workflows/compute_slsa_source.yml@refs/heads/main"
4360)
4461
45- // TODO: Update ExpectedSan to support regex so we can get the branches/tags we really think
46- // folks should be using (they won't all run from main).
62+ // DefaultVerifierOptions accept attestations signed by the current provenance
63+ // workflow, whatever reference it is pinned to, and by the legacy workflows
64+ // while repositories still carry attestations signed by them.
4765var DefaultVerifierOptions = VerificationOptions {
48- ExpectedIssuer : ExpectedIssuer ,
49- ExpectedSan : ExpectedSan ,
50- AlternateSans : []string {OldExpectedSan },
66+ ExpectedIssuer : ExpectedIssuer ,
67+ ExpectedSanPrefix : ExpectedSanPrefix ,
68+ AlternateSans : []string {LegacySourceActionsSan , LegacyPocSan },
69+ }
70+
71+ // expectedIdentities returns the signer identities accepted by the options.
72+ // Without an issuer no identity is accepted.
73+ func (vo * VerificationOptions ) expectedIdentities () []* sapi.Identity {
74+ if vo .ExpectedIssuer == "" {
75+ return nil
76+ }
77+
78+ ids := []* sapi.Identity {}
79+ switch {
80+ case vo .ExpectedSan != "" :
81+ ids = append (ids , exactIdentity (vo .ExpectedIssuer , vo .ExpectedSan ))
82+ case vo .ExpectedSanPrefix != "" :
83+ ids = append (ids , & sapi.Identity {
84+ Sigstore : & sapi.IdentitySigstore {
85+ Issuer : vo .ExpectedIssuer ,
86+ IdentityMatch : & sapi.StringMatcher {
87+ Kind : & sapi.StringMatcher_Prefix {Prefix : vo .ExpectedSanPrefix },
88+ },
89+ },
90+ })
91+ }
92+
93+ for _ , san := range vo .AlternateSans {
94+ if san == "" {
95+ continue
96+ }
97+ ids = append (ids , exactIdentity (vo .ExpectedIssuer , san ))
98+ }
99+ return ids
100+ }
101+
102+ // exactIdentity builds a sigstore identity matching the issuer and SAN exactly
103+ func exactIdentity (issuer , san string ) * sapi.Identity {
104+ return & sapi.Identity {
105+ Sigstore : & sapi.IdentitySigstore {
106+ Issuer : issuer ,
107+ Identity : san ,
108+ },
109+ }
110+ }
111+
112+ // String describes the accepted identities for error messages
113+ func (vo * VerificationOptions ) String () string {
114+ sans := []string {}
115+ switch {
116+ case vo .ExpectedSan != "" :
117+ sans = append (sans , vo .ExpectedSan )
118+ case vo .ExpectedSanPrefix != "" :
119+ sans = append (sans , vo .ExpectedSanPrefix + "*" )
120+ }
121+ for _ , san := range vo .AlternateSans {
122+ if san != "" {
123+ sans = append (sans , san )
124+ }
125+ }
126+ return fmt .Sprintf ("issuer %q identities %q" , vo .ExpectedIssuer , sans )
51127}
52128
53129type Verifier interface {
@@ -64,27 +140,32 @@ type BndVerifier struct {
64140 Options VerificationOptions
65141}
66142
143+ // Verify checks a signed bundle, ensuring the signer matches the expected
144+ // identity. Note that this method does not accept the alternate identities,
145+ // only the expected SAN (or prefix) is checked.
67146func (bv * BndVerifier ) Verify (data string ) (* verify.VerificationResult , error ) {
68- // TODO: There's more for us to do here... but what?
69- // Maybe check to make sure it's from the identity we expect (the workflow?)
70147 verifier := signer .NewVerifier ()
71148
149+ identityOpts := []options.VerificationOptFunc {
150+ options .WithExpectedIdentity (bv .Options .ExpectedIssuer , bv .Options .ExpectedSan ),
151+ }
152+ if bv .Options .ExpectedSan == "" && bv .Options .ExpectedSanPrefix != "" {
153+ identityOpts = append (identityOpts , options .WithExpectedIdentityRegex (
154+ "" , "^" + regexp .QuoteMeta (bv .Options .ExpectedSanPrefix ),
155+ ))
156+ }
157+
72158 // Verify the signed bundle
73- vr , err := verifier .VerifyInlineBundle (
74- []byte (data ),
75- options .WithExpectedIdentity (
76- bv .Options .ExpectedIssuer , bv .Options .ExpectedSan ,
77- ),
78- )
159+ vr , err := verifier .VerifyInlineBundle ([]byte (data ), identityOpts ... )
79160 if err != nil {
80161 return nil , err
81162 }
82163 return vr , nil
83164}
84165
85166// VerifyEnvelope verifies the signature of an attestation envelope fetched
86- // by the collector and checks that the signer matches the expected identity
87- // (issuer + SAN) or one of the accepted alternate identities.
167+ // by the collector and checks that the signer matches one of the expected
168+ // identities.
88169func (bv * BndVerifier ) VerifyEnvelope (env attestation.Envelope ) error {
89170 if env == nil {
90171 return errors .New ("unable to verify, envelope is nil" )
@@ -104,24 +185,15 @@ func (bv *BndVerifier) VerifyEnvelope(env attestation.Envelope) error {
104185 return errors .New ("envelope carries no verified signature" )
105186 }
106187
107- // Check the signer identity against the expected SANs
108- for _ , san := range append ([]string {bv .Options .ExpectedSan }, bv .Options .AlternateSans ... ) {
109- if san == "" {
110- continue
111- }
112- if verification .MatchesIdentity (& sapi.Identity {
113- Sigstore : & sapi.IdentitySigstore {
114- Issuer : bv .Options .ExpectedIssuer ,
115- Identity : san ,
116- },
117- }) {
188+ // Check the signer identity against the expected identities
189+ for _ , id := range bv .Options .expectedIdentities () {
190+ if verification .MatchesIdentity (id ) {
118191 return nil
119192 }
120193 }
121194
122195 return fmt .Errorf (
123- "envelope signer does not match the expected identity (issuer %q identity %q)" ,
124- bv .Options .ExpectedIssuer , bv .Options .ExpectedSan ,
196+ "envelope signer does not match any expected identity (%s)" , bv .Options .String (),
125197 )
126198}
127199
0 commit comments