mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
fix(auth): harden OIDC flow, token validation, session, and bearer handling
- replay entry covers exp + clock-skew window; callback GET-only (RFC 6749) - callback rate-limit returns 429 + Retry-After; panic-recovery sets no-store - cross-chunk integrity for chunked tokens; bounded chunked-token read scan - HTTPError classification so 5xx/429 token-endpoint responses retry - redaction of token/code/state in logs; per-source external auth gate
This commit is contained in:
+18
-4
@@ -95,9 +95,18 @@ func NewClientAssertionSigner(pemBytes []byte, alg, kid string) (*ClientAssertio
|
||||
func validateAlgKeyMatch(alg string, key crypto.PrivateKey) error {
|
||||
switch alg[0] {
|
||||
case 'R', 'P': // RS* or PS*
|
||||
if _, ok := key.(*rsa.PrivateKey); !ok {
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("alg %q requires an RSA key, got %T", alg, key)
|
||||
}
|
||||
// RFC 7518 §3.3: RS*/PS* MUST use a key of 2048 bits or larger.
|
||||
// A smaller key signs locally but conformant IdPs (Entra, Okta,
|
||||
// Keycloak) reject it, so every exchange/refresh/revocation fails
|
||||
// invalid_client at runtime — better to fail at construction
|
||||
// (R135).
|
||||
if rsaKey.N.BitLen() < 2048 {
|
||||
return fmt.Errorf("alg %q requires an RSA key of at least 2048 bits, got %d", alg, rsaKey.N.BitLen())
|
||||
}
|
||||
case 'E': // ES*
|
||||
ecKey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
@@ -142,6 +151,14 @@ func (s *ClientAssertionSigner) Sign(audience, clientID string) (string, error)
|
||||
|
||||
now := nowFn()
|
||||
|
||||
// RFC 7523 §3: aud must be the token (or introspection/revocation)
|
||||
// endpoint URL. An empty audience yields a client_assertion with
|
||||
// "aud":"" that the IdP will reject, so fail fast with a clear
|
||||
// error rather than minting a useless token (R150).
|
||||
if audience == "" {
|
||||
return "", fmt.Errorf("client assertion audience must not be empty")
|
||||
}
|
||||
|
||||
// 16 random bytes as lowercase hex for jti uniqueness.
|
||||
jtiBytes := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rander, jtiBytes); err != nil {
|
||||
@@ -312,6 +329,3 @@ func buildClientAssertionSignerFromConfig(config *Config) (*ClientAssertionSigne
|
||||
|
||||
return NewClientAssertionSigner(pemBytes, alg, config.ClientAssertionKeyID)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user