Cleanup [dec2025] (#101)

* Cleanup excessive comments.

* Remove leftovers hanging around from previous refactor

* Improve test coverage
This commit is contained in:
2025-12-09 01:38:02 +00:00
committed by GitHub
parent 9126c74723
commit c474bbafd6
172 changed files with 25136 additions and 52201 deletions
+330
View File
@@ -0,0 +1,330 @@
package fixtures
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"math/big"
"time"
)
// TokenFixture provides JWT token generation for tests
type TokenFixture struct {
RSAPrivateKey *rsa.PrivateKey
RSAPublicKey *rsa.PublicKey
ECPrivateKey *ecdsa.PrivateKey
ECPublicKey *ecdsa.PublicKey
KeyID string
Issuer string
Audience string
ClockSkew time.Duration
}
// NewTokenFixture creates a new token fixture with generated keys
func NewTokenFixture() (*TokenFixture, error) {
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
return &TokenFixture{
RSAPrivateKey: rsaKey,
RSAPublicKey: &rsaKey.PublicKey,
ECPrivateKey: ecKey,
ECPublicKey: &ecKey.PublicKey,
KeyID: "test-key-id",
Issuer: "https://test-issuer.com",
Audience: "test-client-id",
ClockSkew: 2 * time.Minute,
}, nil
}
// DefaultClaims returns standard JWT claims
func (f *TokenFixture) DefaultClaims() map[string]interface{} {
now := time.Now()
return map[string]interface{}{
"iss": f.Issuer,
"aud": f.Audience,
"sub": "test-subject",
"email": "[email protected]",
"exp": now.Add(1 * time.Hour).Unix(),
"iat": now.Add(-f.ClockSkew).Unix(),
"nbf": now.Add(-f.ClockSkew).Unix(),
"nonce": "test-nonce",
"jti": generateJTI(),
}
}
// ValidToken creates a valid JWT token with optional claim overrides
func (f *TokenFixture) ValidToken(claimOverrides map[string]interface{}) (string, error) {
claims := f.DefaultClaims()
for k, v := range claimOverrides {
claims[k] = v
}
return f.createJWT(claims, "RS256", f.KeyID)
}
// ExpiredToken creates an expired JWT token
func (f *TokenFixture) ExpiredToken() (string, error) {
claims := f.DefaultClaims()
claims["exp"] = time.Now().Add(-1 * time.Hour).Unix()
return f.createJWT(claims, "RS256", f.KeyID)
}
// NotYetValidToken creates a token that's not valid yet
func (f *TokenFixture) NotYetValidToken() (string, error) {
claims := f.DefaultClaims()
claims["nbf"] = time.Now().Add(1 * time.Hour).Unix()
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithSkew creates a token with a specific time offset
func (f *TokenFixture) TokenWithSkew(skew time.Duration) (string, error) {
claims := f.DefaultClaims()
claims["exp"] = time.Now().Add(skew).Unix()
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithRoles creates a token with specific roles
func (f *TokenFixture) TokenWithRoles(roles []string) (string, error) {
claims := f.DefaultClaims()
claims["roles"] = roles
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithGroups creates a token with specific groups
func (f *TokenFixture) TokenWithGroups(groups []string) (string, error) {
claims := f.DefaultClaims()
claims["groups"] = groups
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithEmail creates a token with a specific email
func (f *TokenFixture) TokenWithEmail(email string) (string, error) {
claims := f.DefaultClaims()
claims["email"] = email
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithAudience creates a token with a specific audience
func (f *TokenFixture) TokenWithAudience(audience string) (string, error) {
claims := f.DefaultClaims()
claims["aud"] = audience
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithIssuer creates a token with a specific issuer
func (f *TokenFixture) TokenWithIssuer(issuer string) (string, error) {
claims := f.DefaultClaims()
claims["iss"] = issuer
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenMissingClaim creates a token missing specified claims
func (f *TokenFixture) TokenMissingClaim(missingClaims ...string) (string, error) {
claims := f.DefaultClaims()
for _, claim := range missingClaims {
delete(claims, claim)
}
return f.createJWT(claims, "RS256", f.KeyID)
}
// TokenWithCustomClaims creates a token with custom claims
func (f *TokenFixture) TokenWithCustomClaims(customClaims map[string]interface{}) (string, error) {
claims := f.DefaultClaims()
for k, v := range customClaims {
claims[k] = v
}
return f.createJWT(claims, "RS256", f.KeyID)
}
// MalformedToken returns an invalid JWT string
func (f *TokenFixture) MalformedToken() string {
return "not.a.valid.jwt"
}
// EmptyToken returns an empty string
func (f *TokenFixture) EmptyToken() string {
return ""
}
// TokenWithWrongSignature creates a token signed with a different key
func (f *TokenFixture) TokenWithWrongSignature() (string, error) {
wrongKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", err
}
claims := f.DefaultClaims()
return createJWTWithKey(claims, "RS256", f.KeyID, wrongKey)
}
// TokenWithWrongAlgorithm creates a token with mismatched algorithm
func (f *TokenFixture) TokenWithWrongAlgorithm() (string, error) {
claims := f.DefaultClaims()
// Create token claiming RS256 but we'll return it as-is
// This simulates algorithm confusion attacks
return f.createJWT(claims, "none", f.KeyID)
}
// ECToken creates a token signed with EC key
func (f *TokenFixture) ECToken() (string, error) {
claims := f.DefaultClaims()
return f.createECJWT(claims, "ES256", f.KeyID)
}
// GetJWKS returns a JWKS containing the test public key
func (f *TokenFixture) GetJWKS() map[string]interface{} {
return map[string]interface{}{
"keys": []map[string]interface{}{
{
"kty": "RSA",
"kid": f.KeyID,
"use": "sig",
"alg": "RS256",
"n": base64.RawURLEncoding.EncodeToString(f.RSAPublicKey.N.Bytes()),
"e": base64.RawURLEncoding.EncodeToString(bigIntToBytes(big.NewInt(int64(f.RSAPublicKey.E)))),
},
},
}
}
// GetJWKSBytes returns JWKS as JSON bytes
func (f *TokenFixture) GetJWKSBytes() ([]byte, error) {
return json.Marshal(f.GetJWKS())
}
// createJWT creates a JWT with the fixture's RSA key
func (f *TokenFixture) createJWT(claims map[string]interface{}, alg, kid string) (string, error) {
return createJWTWithKey(claims, alg, kid, f.RSAPrivateKey)
}
// createECJWT creates a JWT with the fixture's EC key
func (f *TokenFixture) createECJWT(claims map[string]interface{}, alg, kid string) (string, error) {
return createECJWTWithKey(claims, alg, kid, f.ECPrivateKey)
}
// Helper functions
func generateJTI() string {
b := make([]byte, 16)
_, _ = rand.Read(b) // #nosec G104 - test fixture, crypto strength not critical
return base64.RawURLEncoding.EncodeToString(b)
}
func bigIntToBytes(i *big.Int) []byte {
return i.Bytes()
}
func createJWTWithKey(claims map[string]interface{}, alg, kid string, key *rsa.PrivateKey) (string, error) {
header := map[string]interface{}{
"alg": alg,
"typ": "JWT",
"kid": kid,
}
headerBytes, err := json.Marshal(header)
if err != nil {
return "", err
}
claimsBytes, err := json.Marshal(claims)
if err != nil {
return "", err
}
headerB64 := base64.RawURLEncoding.EncodeToString(headerBytes)
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsBytes)
signingInput := headerB64 + "." + claimsB64
// For "none" algorithm, return without signature
if alg == "none" {
return signingInput + ".", nil
}
// Sign with RSA-SHA256
signature, err := signRS256([]byte(signingInput), key)
if err != nil {
return "", err
}
signatureB64 := base64.RawURLEncoding.EncodeToString(signature)
return signingInput + "." + signatureB64, nil
}
func createECJWTWithKey(claims map[string]interface{}, alg, kid string, key *ecdsa.PrivateKey) (string, error) {
header := map[string]interface{}{
"alg": alg,
"typ": "JWT",
"kid": kid,
}
headerBytes, err := json.Marshal(header)
if err != nil {
return "", err
}
claimsBytes, err := json.Marshal(claims)
if err != nil {
return "", err
}
headerB64 := base64.RawURLEncoding.EncodeToString(headerBytes)
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsBytes)
signingInput := headerB64 + "." + claimsB64
// Sign with ECDSA-SHA256
signature, err := signES256([]byte(signingInput), key)
if err != nil {
return "", err
}
signatureB64 := base64.RawURLEncoding.EncodeToString(signature)
return signingInput + "." + signatureB64, nil
}
func signRS256(data []byte, key *rsa.PrivateKey) ([]byte, error) {
h := hashSHA256(data)
return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h)
}
func signES256(data []byte, key *ecdsa.PrivateKey) ([]byte, error) {
h := hashSHA256(data)
r, s, err := ecdsa.Sign(rand.Reader, key, h)
if err != nil {
return nil, err
}
// Encode r and s as fixed-size byte arrays
curveBits := key.Curve.Params().BitSize
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes++
}
rBytes := r.Bytes()
sBytes := s.Bytes()
signature := make([]byte, 2*keyBytes)
copy(signature[keyBytes-len(rBytes):keyBytes], rBytes)
copy(signature[2*keyBytes-len(sBytes):], sBytes)
return signature, nil
}
func hashSHA256(data []byte) []byte {
h := sha256.Sum256(data)
return h[:]
}
+244
View File
@@ -0,0 +1,244 @@
package fixtures
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewTokenFixture(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
assert.NotNil(t, fixture.RSAPrivateKey)
assert.NotNil(t, fixture.RSAPublicKey)
assert.NotNil(t, fixture.ECPrivateKey)
assert.NotNil(t, fixture.ECPublicKey)
assert.NotEmpty(t, fixture.KeyID)
assert.NotEmpty(t, fixture.Issuer)
assert.NotEmpty(t, fixture.Audience)
}
func TestDefaultClaims(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
claims := fixture.DefaultClaims()
assert.Equal(t, fixture.Issuer, claims["iss"])
assert.Equal(t, fixture.Audience, claims["aud"])
assert.NotEmpty(t, claims["sub"])
assert.NotEmpty(t, claims["email"])
assert.NotNil(t, claims["exp"])
assert.NotNil(t, claims["iat"])
assert.NotNil(t, claims["nbf"])
assert.NotEmpty(t, claims["jti"])
}
func TestValidToken(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
t.Run("creates valid JWT structure", func(t *testing.T) {
token, err := fixture.ValidToken(nil)
require.NoError(t, err)
assert.NotEmpty(t, token)
// JWT has 3 parts
parts := strings.Split(token, ".")
assert.Len(t, parts, 3)
})
t.Run("applies claim overrides", func(t *testing.T) {
token, err := fixture.ValidToken(map[string]interface{}{
"email": "[email protected]",
})
require.NoError(t, err)
assert.NotEmpty(t, token)
})
}
func TestExpiredToken(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.ExpiredToken()
require.NoError(t, err)
assert.NotEmpty(t, token)
parts := strings.Split(token, ".")
assert.Len(t, parts, 3)
}
func TestNotYetValidToken(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.NotYetValidToken()
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenWithSkew(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
t.Run("positive skew", func(t *testing.T) {
token, err := fixture.TokenWithSkew(5 * time.Minute)
require.NoError(t, err)
assert.NotEmpty(t, token)
})
t.Run("negative skew", func(t *testing.T) {
token, err := fixture.TokenWithSkew(-5 * time.Minute)
require.NoError(t, err)
assert.NotEmpty(t, token)
})
}
func TestTokenWithRoles(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithRoles([]string{"admin", "user"})
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenWithGroups(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithGroups([]string{"developers", "admins"})
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenWithEmail(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithEmail("[email protected]")
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenWithAudience(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithAudience("custom-audience")
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenWithIssuer(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithIssuer("https://custom-issuer.com")
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestTokenMissingClaim(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
t.Run("missing single claim", func(t *testing.T) {
token, err := fixture.TokenMissingClaim("email")
require.NoError(t, err)
assert.NotEmpty(t, token)
})
t.Run("missing multiple claims", func(t *testing.T) {
token, err := fixture.TokenMissingClaim("email", "sub", "nonce")
require.NoError(t, err)
assert.NotEmpty(t, token)
})
}
func TestTokenWithCustomClaims(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithCustomClaims(map[string]interface{}{
"custom_claim": "custom_value",
"another_claim": 123,
})
require.NoError(t, err)
assert.NotEmpty(t, token)
}
func TestMalformedToken(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token := fixture.MalformedToken()
assert.Equal(t, "not.a.valid.jwt", token)
parts := strings.Split(token, ".")
assert.Len(t, parts, 4) // 4 parts instead of 3
}
func TestEmptyToken(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token := fixture.EmptyToken()
assert.Empty(t, token)
}
func TestTokenWithWrongSignature(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
token, err := fixture.TokenWithWrongSignature()
require.NoError(t, err)
assert.NotEmpty(t, token)
parts := strings.Split(token, ".")
assert.Len(t, parts, 3)
}
func TestGetJWKS(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
jwks := fixture.GetJWKS()
assert.Contains(t, jwks, "keys")
keys, ok := jwks["keys"].([]map[string]interface{})
require.True(t, ok)
assert.Len(t, keys, 1)
key := keys[0]
assert.Equal(t, "RSA", key["kty"])
assert.Equal(t, fixture.KeyID, key["kid"])
assert.NotEmpty(t, key["n"])
assert.NotEmpty(t, key["e"])
}
func TestGetJWKSBytes(t *testing.T) {
fixture, err := NewTokenFixture()
require.NoError(t, err)
jwksBytes, err := fixture.GetJWKSBytes()
require.NoError(t, err)
assert.NotEmpty(t, jwksBytes)
assert.Contains(t, string(jwksBytes), "keys")
}