// Package oidc implementiert Core IAM-06: SSO-Anmeldung ueber OpenID // Connect gegen externe Identity-Provider. // // WICHTIGER HINWEIS: In dieser Umgebung ist kein registrierter externer // OIDC-Provider (Client-ID/Secret bei Google/Okta/Azure AD o.ae.) verfuegbar, // gegen den ein echter Authorization-Code-Redirect-Flow end-to-end // durchgefuehrt werden koennte (analog AUD-05/Archive, IAM-05/LDAP). ANDERS // als bei LDAP/Archive ist die eigentliche Token-VALIDIERUNG (JWKS- // Signaturpruefung, Ablauf, Claims, Nonce/State) aber ein rein // kryptographischer Vorgang ohne Netzwerkabhaengigkeit zur Testzeit — die // Tests unten erzeugen ein eigenes RSA-Schluesselpaar, signieren ein // ID-Token selbst und pruefen es exakt so, wie es ein echter Provider // liefern wuerde. Nur der Redirect zu einem echten externen Provider bleibt // ungeprueft. package oidc import ( "crypto/rsa" "encoding/base64" "encoding/binary" "encoding/json" "errors" "fmt" "math/big" ) type jwk struct { Kid string `json:"kid"` Kty string `json:"kty"` N string `json:"n"` E string `json:"e"` } type jwkSet struct { Keys []jwk `json:"keys"` } var ErrUnsupportedKeyType = errors.New("oidc: nicht unterstuetzter jwk-schluesseltyp") // ParseJWKS dekodiert eine JWKS-Antwort (RFC 7517) in kid -> RSA-Public-Key. // Nur RSA-Schluessel (kty=RSA, Standard bei allen gaengigen OIDC-Providern // fuer ID-Tokens) werden unterstuetzt. func ParseJWKS(data []byte) (map[string]*rsa.PublicKey, error) { var set jwkSet if err := json.Unmarshal(data, &set); err != nil { return nil, fmt.Errorf("jwks parsen: %w", err) } out := make(map[string]*rsa.PublicKey, len(set.Keys)) for _, k := range set.Keys { if k.Kty != "RSA" { continue } pub, err := decodeRSAKey(k) if err != nil { return nil, fmt.Errorf("schluessel %q dekodieren: %w", k.Kid, err) } out[k.Kid] = pub } return out, nil } func decodeRSAKey(k jwk) (*rsa.PublicKey, error) { nBytes, err := base64.RawURLEncoding.DecodeString(k.N) if err != nil { return nil, fmt.Errorf("modulus dekodieren: %w", err) } eBytes, err := base64.RawURLEncoding.DecodeString(k.E) if err != nil { return nil, fmt.Errorf("exponent dekodieren: %w", err) } eBuf := make([]byte, 8) copy(eBuf[8-len(eBytes):], eBytes) e := int(binary.BigEndian.Uint64(eBuf)) return &rsa.PublicKey{N: new(big.Int).SetBytes(nBytes), E: e}, nil }