mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-17 04:37:48 +00:00
R53: enforce 2048-bit minimum RSA key size on verify
This commit is contained in:
@@ -568,6 +568,13 @@ func verifySignatureWithKey(tokenString string, pubKey crypto.PublicKey, alg str
|
|||||||
hashed := h.Sum(nil)
|
hashed := h.Sum(nil)
|
||||||
switch pubKey := pubKey.(type) {
|
switch pubKey := pubKey.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
|
// Enforce a minimum RSA modulus size (NIST SP 800-57: ≥2048 bits).
|
||||||
|
// Without this, a token signed with a weak (<2048-bit) RSA key whose
|
||||||
|
// modulus the trusted JWKS publishes is accepted as valid. Modern
|
||||||
|
// providers all use ≥2048; rejecting smaller keys is safe hardening.
|
||||||
|
if pubKey.N.BitLen() < 2048 {
|
||||||
|
return fmt.Errorf("RSA key size %d bits is below the minimum 2048 bits", pubKey.N.BitLen())
|
||||||
|
}
|
||||||
if strings.HasPrefix(alg, "RS") {
|
if strings.HasPrefix(alg, "RS") {
|
||||||
return rsa.VerifyPKCS1v15(pubKey, hashFunc, hashed, signature)
|
return rsa.VerifyPKCS1v15(pubKey, hashFunc, hashed, signature)
|
||||||
} else if strings.HasPrefix(alg, "PS") {
|
} else if strings.HasPrefix(alg, "PS") {
|
||||||
|
|||||||
@@ -1595,3 +1595,80 @@ func TestInvalidRedirectURI(t *testing.T) {
|
|||||||
t.Errorf("Expected redirect to /legitimate-redirect, but got: %s", location)
|
t.Errorf("Expected redirect to /legitimate-redirect, but got: %s", location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Review regression: RSA signature verification must reject tokens signed
|
||||||
|
// with keys below the NIST 2048-bit minimum modulus size.
|
||||||
|
func TestJWTRSAKeySizeMinimum(t *testing.T) {
|
||||||
|
tc := newTestCleanup(t)
|
||||||
|
logger := NewLogger("debug")
|
||||||
|
|
||||||
|
mkToidc := func(pub *rsa.PublicKey) *TraefikOidc {
|
||||||
|
jwk := JWK{
|
||||||
|
Kty: "RSA",
|
||||||
|
Kid: "test-key-id",
|
||||||
|
Alg: "RS256",
|
||||||
|
N: base64.RawURLEncoding.EncodeToString(pub.N.Bytes()),
|
||||||
|
E: base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}), // 65537
|
||||||
|
}
|
||||||
|
o := &TraefikOidc{
|
||||||
|
issuerURL: "https://test-issuer.com",
|
||||||
|
clientID: "test-client-id",
|
||||||
|
audience: "test-client-id",
|
||||||
|
clientSecret: "test-client-secret",
|
||||||
|
jwkCache: &MockJWKCache{JWKS: &JWKSet{Keys: []JWK{jwk}}, Err: nil},
|
||||||
|
jwksURL: "https://test-jwks-url.com",
|
||||||
|
tokenBlacklist: tc.addCache(NewCache()),
|
||||||
|
tokenCache: tc.addTokenCache(NewTokenCache()),
|
||||||
|
limiter: rate.NewLimiter(rate.Every(time.Second), 10),
|
||||||
|
logger: logger,
|
||||||
|
excludedURLs: map[string]struct{}{},
|
||||||
|
httpClient: &http.Client{},
|
||||||
|
extractClaimsFunc: extractClaims,
|
||||||
|
}
|
||||||
|
o.jwtVerifier = o
|
||||||
|
o.tokenVerifier = o
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"iss": "https://test-issuer.com",
|
||||||
|
"aud": "test-client-id",
|
||||||
|
"exp": float64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
|
"iat": float64(time.Now().Add(-2 * time.Minute).Unix()),
|
||||||
|
"sub": "test-subject",
|
||||||
|
"jti": "rsa-keysize-" + generateRandomString(8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("weak 1024-bit RSA key rejected", func(t *testing.T) {
|
||||||
|
weak, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate weak key: %v", err)
|
||||||
|
}
|
||||||
|
o := mkToidc(&weak.PublicKey)
|
||||||
|
tok, err := createTestJWT(weak, "RS256", "test-key-id", claims())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create JWT: %v", err)
|
||||||
|
}
|
||||||
|
err = o.VerifyToken(tok)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected 1024-bit RSA token to be rejected, but verification succeeded")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("2048-bit RSA key accepted", func(t *testing.T) {
|
||||||
|
strong, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate strong key: %v", err)
|
||||||
|
}
|
||||||
|
o := mkToidc(&strong.PublicKey)
|
||||||
|
tok, err := createTestJWT(strong, "RS256", "test-key-id", claims())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create JWT: %v", err)
|
||||||
|
}
|
||||||
|
if err = o.VerifyToken(tok); err != nil {
|
||||||
|
t.Fatalf("expected 2048-bit RSA token to verify, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user