taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit 184bb2a5fae4546e74914775dc6ea0f9a4d187f9
parent 372545ca10082c7fa8d1da1aa2376453216ab61f
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Tue, 23 Dec 2025 13:45:01 +0900

support HMAC based ID tokens (very rare)

Diffstat:
Mlocales/de-DE/taldir.yml | 2+-
Mpkg/taldir/oidc_validator.go | 89++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
2 files changed, 58 insertions(+), 33 deletions(-)

diff --git a/locales/de-DE/taldir.yml b/locales/de-DE/taldir.yml @@ -5,7 +5,7 @@ error: "Es ist ein Fehler aufgetreten!" title: "Alias Registration und Suche" selectAliasToLookup: "Bitte wähle einen Alias-Typ den Du suchen möchtest:" howtoRegisterOrModify: "Um ein neues Alias mit einer Bezahlsystemadresse zu verbinden oder die bereits verbundene Bezahlsystemaddresse zu ändern, musst Du es erst über die Suche oben suchen." -registerOrModify: "Verbinden oder Anpassen" +registerOrModify: "Registrieren oder Anpassen" validationInitiated: "Alias-Validierung gestartet" pleaseCompleteValidation: "Bitte vervollständige die Alias-Validierung wie in der versendeten Nachricht beschrieben damit wir sicher sein können dass Du es wirklich bist!" notYetLinked: "`%s` ist noch nicht mit einer Bezahlsystemadresse verlinkt. Du kannst eine IBAN oder Taler wallet Adresse verlinken:" diff --git a/pkg/taldir/oidc_validator.go b/pkg/taldir/oidc_validator.go @@ -55,6 +55,9 @@ type OidcValidator struct { // Client secret clientSecret string + // Shared (ID) token secret (HMAC signatures) + sharedTokenSecret string + // Scope(s) scope string @@ -124,8 +127,55 @@ func (t OidcValidator) IsAliasValid(alias string) (err error) { return } -func (t OidcValidator) ProcessOidcCallback(r *http.Request) (string, string, error) { +func (t OidcValidator) ValidateIDToken(tokenString string, expectedAlias string) error { var jwkSet jose.JSONWebKeySet + var claims jwt.Claims + token, err := jwt.ParseSigned(tokenString, t.jwtAlgos) + if err != nil { + return fmt.Errorf("unable to parse token: %v", err) + } + if len(token.Headers) != 1 { + return fmt.Errorf("token has more than one header") + } + if token.Headers[0].Algorithm == string(jose.HS256) || + token.Headers[0].Algorithm == string(jose.HS384) || + token.Headers[0].Algorithm == string(jose.HS512) { + // Verify and extract claims using the JWKS + err = token.Claims(t.sharedTokenSecret, &claims) + if err != nil { + return fmt.Errorf("unable to validate token claims: %v", err) + } + } else { + req, err := http.NewRequest("GET", t.jwksEndpoint, nil) + if err != nil { + return fmt.Errorf("failed to create JWKS request") + } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to execute JWKS request") + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected response code %d", resp.StatusCode) + } + err = json.NewDecoder(resp.Body).Decode(&jwkSet) + if err != nil { + return fmt.Errorf("unable to parse JWKS response") + } + // Verify and extract claims using the JWKS + err = token.Claims(jwkSet, &claims) + if err != nil { + return fmt.Errorf("unable to validate token claims: %v", err) + } + } + if claims.Subject != expectedAlias { + return fmt.Errorf("subject in ID token (%s) does not match state (%s)", claims.Subject, expectedAlias) + } + return nil + +} + +func (t OidcValidator) ProcessOidcCallback(r *http.Request) (string, string, error) { // Process authorization code state := r.URL.Query().Get("state") if state == "" { @@ -134,22 +184,6 @@ func (t OidcValidator) ProcessOidcCallback(r *http.Request) (string, string, err if t.authorizationsState[state] == nil { return "", "", fmt.Errorf("state invalid") } - req, err := http.NewRequest("GET", t.jwksEndpoint, nil) - if err != nil { - return "", "", fmt.Errorf("failed to create JWKS request") - } - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", "", fmt.Errorf("failed to execute JWKS request") - } - if resp.StatusCode != http.StatusOK { - return "", "", fmt.Errorf("unexpected response code %d", resp.StatusCode) - } - err = json.NewDecoder(resp.Body).Decode(&jwkSet) - if err != nil { - return "", "", fmt.Errorf("unable to parse JWKS response") - } // TODO process authorization code code := r.URL.Query().Get("code") data := url.Values{} @@ -158,14 +192,14 @@ func (t OidcValidator) ProcessOidcCallback(r *http.Request) (string, string, err data.Set("redirect_uri", t.redirectURI) data.Set("code", code) - req, err = http.NewRequest("POST", t.tokenEndpoint, strings.NewReader(data.Encode())) + req, err := http.NewRequest("POST", t.tokenEndpoint, strings.NewReader(data.Encode())) if err != nil { return "", "", fmt.Errorf("failed to create token request: %v", err) } req.SetBasicAuth(t.clientID, t.clientSecret) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - client = &http.Client{} - resp, err = client.Do(req) + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return "", "", fmt.Errorf("failed to execute token request: %v", err) } @@ -177,21 +211,11 @@ func (t OidcValidator) ProcessOidcCallback(r *http.Request) (string, string, err if err != nil { return "", "", fmt.Errorf("unable to parse token response: %v", err) } - token, err := jwt.ParseSigned(tokenResponse.IDToken, t.jwtAlgos) - if err != nil { - return "", "", fmt.Errorf("unable to parse token: %v", err) - } - - // Verify and extract claims using the JWKS - var claims jwt.Claims - err = token.Claims(jwkSet, &claims) + err = t.ValidateIDToken(tokenResponse.IDToken, t.authorizationsState[state].alias) if err != nil { return "", "", fmt.Errorf("unable to validate token: %v", err) } - if claims.Subject != t.authorizationsState[state].alias { - return "", "", fmt.Errorf("subject in ID token (%s) does not match state (%s)", claims.Subject, t.authorizationsState[state].alias) - } - return claims.Subject, t.authorizationsState[state].challenge, nil + return t.authorizationsState[state].alias, t.authorizationsState[state].challenge, nil } func (t OidcValidator) RegistrationStart(topic string, link string, message string, alias string, challenge string) (string, error) { @@ -221,6 +245,7 @@ func makeOidcValidator(cfg *TaldirConfig, name string, landingPageTpl *template. scope: sec.Key("scope").MustString("openid"), tokenEndpoint: sec.Key("token_endpoint").MustString(""), jwksEndpoint: sec.Key("jwks_endpoint").MustString(""), + sharedTokenSecret: sec.Key("shared_token_secret").MustString("secret"), authorizationEndpoint: sec.Key("authorization_endpoint").MustString(""), validAliasRegex: sec.Key("valid_alias_regex").MustString(""), redirectURI: redirectURI,