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:
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,