Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit b975fed

Browse files
authored
Merge pull request #67 from evalops/codex/asb-verifier-security-tests
[codex] expand ASB verifier security tests
2 parents 7f240df + cee26c3 commit b975fed

3 files changed

Lines changed: 318 additions & 39 deletions

File tree

internal/authn/oidc/verifier.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package oidc
22

33
import (
44
"context"
5+
"crypto/ecdsa"
6+
"crypto/ed25519"
7+
"crypto/rsa"
58
"fmt"
69
"strconv"
710
"strings"
@@ -51,7 +54,14 @@ func (v *Verifier) Verify(ctx context.Context, in *core.Attestation) (*core.Work
5154

5255
claims := jwt.MapClaims{}
5356
parsed, err := jwt.ParseWithClaims(in.Token, claims, func(token *jwt.Token) (any, error) {
54-
return v.keyfunc(ctx, token)
57+
key, err := v.keyfunc(ctx, token)
58+
if err != nil {
59+
return nil, err
60+
}
61+
if err := ensureSupportedSigningMethod(token, key); err != nil {
62+
return nil, err
63+
}
64+
return key, nil
5565
}, jwt.WithIssuer(v.issuer), jwt.WithAudience(v.audience))
5666
if err != nil {
5767
return nil, fmt.Errorf("%w: %v", core.ErrUnauthorized, err)
@@ -141,3 +151,22 @@ func copyNumericClaim(attributes map[string]string, claims jwt.MapClaims, key st
141151
}
142152
}
143153
}
154+
155+
func ensureSupportedSigningMethod(token *jwt.Token, key any) error {
156+
switch key.(type) {
157+
case ed25519.PublicKey:
158+
if token.Method == jwt.SigningMethodEdDSA {
159+
return nil
160+
}
161+
case *rsa.PublicKey:
162+
switch token.Method.(type) {
163+
case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
164+
return nil
165+
}
166+
case *ecdsa.PublicKey:
167+
if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok {
168+
return nil
169+
}
170+
}
171+
return fmt.Errorf("%w: unexpected signing method %q", core.ErrUnauthorized, token.Method.Alg())
172+
}

internal/authn/oidc/verifier_test.go

Lines changed: 133 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@ import (
1111
"github.com/golang-jwt/jwt/v5"
1212
)
1313

14+
func newTestVerifier(t *testing.T, publicKey ed25519.PublicKey, prefixes []string) *oidc.Verifier {
15+
t.Helper()
16+
17+
verifier, err := oidc.NewVerifier(oidc.Config{
18+
Issuer: "https://token.actions.githubusercontent.com",
19+
Audience: "asb-control-plane",
20+
AllowedSubjectPrefixes: prefixes,
21+
Keyfunc: func(context.Context, *jwt.Token) (any, error) {
22+
return publicKey, nil
23+
},
24+
})
25+
if err != nil {
26+
t.Fatalf("NewVerifier() error = %v", err)
27+
}
28+
return verifier
29+
}
30+
1431
func TestVerifier_VerifyGitHubActionsToken(t *testing.T) {
1532
t.Parallel()
1633

@@ -41,17 +58,7 @@ func TestVerifier_VerifyGitHubActionsToken(t *testing.T) {
4158
t.Fatalf("SignedString() error = %v", err)
4259
}
4360

44-
verifier, err := oidc.NewVerifier(oidc.Config{
45-
Issuer: "https://token.actions.githubusercontent.com",
46-
Audience: "asb-control-plane",
47-
AllowedSubjectPrefixes: []string{"repo:evalops/"},
48-
Keyfunc: func(context.Context, *jwt.Token) (any, error) {
49-
return publicKey, nil
50-
},
51-
})
52-
if err != nil {
53-
t.Fatalf("NewVerifier() error = %v", err)
54-
}
61+
verifier := newTestVerifier(t, publicKey, []string{"repo:evalops/"})
5562

5663
identity, err := verifier.Verify(context.Background(), &core.Attestation{
5764
Kind: core.AttestationKindOIDCJWT,
@@ -93,22 +100,128 @@ func TestVerifier_RejectsUnexpectedSubjectPrefix(t *testing.T) {
93100
t.Fatalf("SignedString() error = %v", err)
94101
}
95102

96-
verifier, err := oidc.NewVerifier(oidc.Config{
97-
Issuer: "https://token.actions.githubusercontent.com",
98-
Audience: "asb-control-plane",
99-
AllowedSubjectPrefixes: []string{"repo:evalops/"},
100-
Keyfunc: func(context.Context, *jwt.Token) (any, error) {
101-
return publicKey, nil
102-
},
103+
verifier := newTestVerifier(t, publicKey, []string{"repo:evalops/"})
104+
105+
if _, err := verifier.Verify(context.Background(), &core.Attestation{
106+
Kind: core.AttestationKindOIDCJWT,
107+
Token: raw,
108+
}); err == nil {
109+
t.Fatal("Verify() error = nil, want non-nil")
110+
}
111+
}
112+
113+
func TestVerifier_AllowsAnySubjectWhenPrefixesUnset(t *testing.T) {
114+
t.Parallel()
115+
116+
publicKey, privateKey, err := ed25519.GenerateKey(nil)
117+
if err != nil {
118+
t.Fatalf("GenerateKey() error = %v", err)
119+
}
120+
121+
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
122+
"iss": "https://token.actions.githubusercontent.com",
123+
"sub": "repo:other-org/repo:ref:refs/heads/main",
124+
"aud": "asb-control-plane",
125+
"exp": time.Now().Add(5 * time.Minute).Unix(),
126+
"iat": time.Now().Add(-time.Minute).Unix(),
103127
})
128+
raw, err := token.SignedString(privateKey)
104129
if err != nil {
105-
t.Fatalf("NewVerifier() error = %v", err)
130+
t.Fatalf("SignedString() error = %v", err)
131+
}
132+
133+
verifier := newTestVerifier(t, publicKey, nil)
134+
if _, err := verifier.Verify(context.Background(), &core.Attestation{
135+
Kind: core.AttestationKindOIDCJWT,
136+
Token: raw,
137+
}); err != nil {
138+
t.Fatalf("Verify() error = %v", err)
139+
}
140+
}
141+
142+
func TestVerifier_RejectsExpiredToken(t *testing.T) {
143+
t.Parallel()
144+
145+
publicKey, privateKey, err := ed25519.GenerateKey(nil)
146+
if err != nil {
147+
t.Fatalf("GenerateKey() error = %v", err)
148+
}
149+
150+
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
151+
"iss": "https://token.actions.githubusercontent.com",
152+
"sub": "repo:evalops/asb:ref:refs/heads/main",
153+
"aud": "asb-control-plane",
154+
"exp": time.Now().Add(-time.Minute).Unix(),
155+
"iat": time.Now().Add(-2 * time.Minute).Unix(),
156+
})
157+
raw, err := token.SignedString(privateKey)
158+
if err != nil {
159+
t.Fatalf("SignedString() error = %v", err)
106160
}
107161

162+
verifier := newTestVerifier(t, publicKey, []string{"repo:evalops/"})
108163
if _, err := verifier.Verify(context.Background(), &core.Attestation{
109164
Kind: core.AttestationKindOIDCJWT,
110165
Token: raw,
111166
}); err == nil {
112-
t.Fatal("Verify() error = nil, want non-nil")
167+
t.Fatal("Verify() error = nil, want expired token failure")
168+
}
169+
}
170+
171+
func TestVerifier_RejectsTokenBeforeNotBefore(t *testing.T) {
172+
t.Parallel()
173+
174+
publicKey, privateKey, err := ed25519.GenerateKey(nil)
175+
if err != nil {
176+
t.Fatalf("GenerateKey() error = %v", err)
177+
}
178+
179+
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
180+
"iss": "https://token.actions.githubusercontent.com",
181+
"sub": "repo:evalops/asb:ref:refs/heads/main",
182+
"aud": "asb-control-plane",
183+
"exp": time.Now().Add(5 * time.Minute).Unix(),
184+
"iat": time.Now().Add(-time.Minute).Unix(),
185+
"nbf": time.Now().Add(time.Minute).Unix(),
186+
})
187+
raw, err := token.SignedString(privateKey)
188+
if err != nil {
189+
t.Fatalf("SignedString() error = %v", err)
190+
}
191+
192+
verifier := newTestVerifier(t, publicKey, []string{"repo:evalops/"})
193+
if _, err := verifier.Verify(context.Background(), &core.Attestation{
194+
Kind: core.AttestationKindOIDCJWT,
195+
Token: raw,
196+
}); err == nil {
197+
t.Fatal("Verify() error = nil, want not-before validation failure")
198+
}
199+
}
200+
201+
func TestVerifier_RejectsUnexpectedSigningMethod(t *testing.T) {
202+
t.Parallel()
203+
204+
publicKey, _, err := ed25519.GenerateKey(nil)
205+
if err != nil {
206+
t.Fatalf("GenerateKey() error = %v", err)
207+
}
208+
209+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
210+
"iss": "https://token.actions.githubusercontent.com",
211+
"sub": "repo:evalops/asb:ref:refs/heads/main",
212+
"aud": "asb-control-plane",
213+
"exp": time.Now().Add(5 * time.Minute).Unix(),
214+
})
215+
raw, err := token.SignedString([]byte("shared-secret"))
216+
if err != nil {
217+
t.Fatalf("SignedString() error = %v", err)
218+
}
219+
220+
verifier := newTestVerifier(t, publicKey, []string{"repo:evalops/"})
221+
if _, err := verifier.Verify(context.Background(), &core.Attestation{
222+
Kind: core.AttestationKindOIDCJWT,
223+
Token: raw,
224+
}); err == nil {
225+
t.Fatal("Verify() error = nil, want signing-method failure")
113226
}
114227
}

0 commit comments

Comments
 (0)