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:
2026-08-14 16:16:26 +01:00
parent 3eb11b07e9
commit 8640451877
19 changed files with 2002 additions and 508 deletions
+18 -4
View File
@@ -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)
}