Thanks, Andreas.

I have expanded the test plan to also test single aud authentication.

I'm unsure what else to say in the "where problems could occur" besides
what I have just added. Let me know if that is not enough.

** Description changed:

  [ Impact ]
  
-  * Local OAuth2 authentication fails if the JWT aud claim is an array.
+  * Local OAuth2 authentication fails if the JWT aud claim is an array.
  From RFC7519:
  
-  > In the general case, the "aud" value is an array of case-sensitive strings,
-  > each containing a StringOrURI value. In the special case when the JWT has
-  > one audience, the "aud" value MAY be a single case-sensitive string
-  > containing a StringOrURI value.
+  > In the general case, the "aud" value is an array of case-sensitive strings,
+  > each containing a StringOrURI value. In the special case when the JWT has
+  > one audience, the "aud" value MAY be a single case-sensitive string
+  > containing a StringOrURI value.
  
-  * Many IdPs (Keycloak, for example) can be configured to send "aud" as an
-    array if the token is meant for multiple audiences.
+  * Many IdPs (Keycloak, for example) can be configured to send "aud" as an
+    array if the token is meant for multiple audiences.
  
-  * The inability to parse array claims breaks compatibility with compliant
-    IdPs.
+  * The inability to parse array claims breaks compatibility with compliant
+    IdPs.
  
  [ Test Plan ]
  
-  * On a fresh Ubuntu Noble LXC container, run the following script as
+  * On a fresh Ubuntu Noble LXC container, run the following script as
  root.
  
  echo initial setup
  
  apt update && apt install -y python3-jwt python3-cryptography openssl
  dovecot-core
  
  echo key generation and dictionary setup
  
  mkdir -p /etc/dovecot/keys/default/RS256/
  openssl genrsa -out /etc/dovecot/keys/test.pem 2048
  openssl rsa -in /etc/dovecot/keys/test.pem -pubout -out 
/etc/dovecot/keys/default/RS256/default
  find /etc/dovecot/keys -type d -exec chmod 755 {} \;
  chmod 644 /etc/dovecot/keys/default/RS256/default
  
  echo writing minimal dovecot configuration:
  
  cat << EOF > /etc/dovecot/dovecot.conf
  auth_debug = yes
  auth_debug_passwords = yes
  auth_mechanisms = xoauth2 oauthbearer plain
  
  passdb {
-     driver = oauth2
-     mechanisms = xoauth2 oauthbearer plain
-     args = /etc/dovecot/dovecot-oauth2.conf.ext
+     driver = oauth2
+     mechanisms = xoauth2 oauthbearer plain
+     args = /etc/dovecot/dovecot-oauth2.conf.ext
  }
  
  userdb {
-     driver = static
-     args = uid=1000 gid=1000 home=/tmp/%u
+     driver = static
+     args = uid=1000 gid=1000 home=/tmp/%u
  }
  EOF
  
  cat << EOF > /etc/dovecot/dovecot-oauth2.conf.ext
  introspection_mode = local
  client_id = dovecot
  local_validation_key_dict = fs:posix:prefix=/etc/dovecot/keys/
  username_attribute = sub
  EOF
  
  systemctl restart dovecot
  
  echo generating test JWT
  
  cat << EOF > gen-jwt.py
- import jwt, time
+ import jwt, time, os
+ 
+ aud = 'dovecot' if 'SINGLE' in os.environ else ['dovecot',
+ 'https://example.com']
  
  with open('/etc/dovecot/keys/test.pem', 'rb') as f:
-     key = f.read()
+     key = f.read()
  
  payload = {
-     'iss': 'https://example.com',
-     'sub': '[email protected]',
-     'aud': ['dovecot', 'https://example.com'],
-     'exp': int(time.time()) + 3600,
+     'iss': 'https://example.com',
+     'sub': '[email protected]',
+     'aud': aud,
+     'exp': int(time.time()) + 3600,
  }
  
  print(jwt.encode(payload, key, algorithm='RS256'))
  EOF
  
- echo trying to authenticate
+ echo trying to authenticate (single aud)
+ 
+ doveadm auth test [email protected] "$(SINGLE=1 python3 gen-jwt.py)"
+ 
+ echo trying to authenticate (multiple aud)
  
  doveadm auth test [email protected] "$(python3 gen-jwt.py)"
  
  [ Where problems could occur ]
  
-  * This is an upstream change and was already fixed in the release branch for
-    2.3.21. The code also handles the scenario where "aud" is a single string,
-    so clients using JWTs with a single "aud" don't break.
+  * This is an upstream change and was already fixed in the release branch for
+    2.3.21.
+ 
+  * The code also handles the scenario where "aud" is a single string, so
+    clients using JWTs with a single "aud" don't break. The test plan above
+    demonstrates this.
+ 
+  * Unlikely that this change breaks oauth authentication: the same patch is
+    present in the 2.4.X release series and I couldn't find any bugs reported.
  
  [ Other Info ]
  
-  * Fixed upstream in 2.4.0 and local JWT validation was introduced in 2.3.11,
-    so this could only possibly affect Jammy and Noble.
+  * Fixed upstream in 2.4.0 and local JWT validation was introduced in 2.3.11,
+    so this could only possibly affect Jammy and Noble.
  
-  * However, the "aud" field validation was only added in 63e0c9e, which is 
only
-    present in 2.3.21.1.
+  * However, the "aud" field validation was only added in 63e0c9e, which is 
only
+    present in 2.3.21.1.
  
-  * When running the test plan above on a Jammy system, authentication
+  * When running the test plan above on a Jammy system, authentication
  works.
  
  [ Original Bug Description ]
  
  Description: Ubuntu 24.04.1 LTS
  Release: 24.04
  dovecot-core/noble-updates 1:2.3.21+dfsg1-2ubuntu6.1
  
  On Ubuntu’s Dovecot build, local OAuth2/JWT validation fails if the JWT
  aud claim is a JSON array. Dovecot logs:
  
  Local validation failed: client_id set but aud is missing
  
  This happens even though aud is present (as an array):
  { "aud": ["dovecot", "https://checkin.thga.de";], ... }
  
  Upstream Dovecot release-2.3.21 uses an array-aware accessor:
  
-     get_field_multiple(tree, "aud")
-     Source: src/lib-oauth2/oauth2-jwt.c (release-2.3.21 branch)
+     get_field_multiple(tree, "aud")
+     Source: src/lib-oauth2/oauth2-jwt.c (release-2.3.21 branch)
  
  But Ubuntu appears to be built from code corresponding to the 2.3.21 tag
  where it uses:
  
-     get_field(tree, "aud")
-     Source: src/lib-oauth2/oauth2-jwt.c (2.3.21 tag)
+     get_field(tree, "aud")
+     Source: src/lib-oauth2/oauth2-jwt.c (2.3.21 tag)
  
  With get_field(), aud arrays are not handled, so aud is treated as
  missing.

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2142200

Title:
  dovecot-core: OAuth2 JWT validation fails with client_id set but aud
  is missing when aud claim is an array

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/dovecot/+bug/2142200/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to