Skip to content

OAuth/OIDC token-validation gaps: nonce not enforced when absent, at_hash alg, inbound bearer not signature/aud/exp-validated + unverified email used for authz #1609

Description

@Pig-Tail

Describe the bug

While reviewing the new (2.5) OAuth/OpenID Connect support I noticed three token-validation gaps. These are hardening / spec-compliance issues (2.5 is unreleased, so filing here per SECURITY.md rather than as an advisory); their real-world impact depends on the configured Identity Provider (IdP), so I'm raising them for discussion rather than asserting a concrete bypass.

1. nonce not enforced when the id_token omits the claim — cups/oauth.c

// cups/oauth.c:1358-1364 (cupsOAuthGetTokens)
jwt    = cupsJWTImportString(id_value, CUPS_JWS_FORMAT_COMPACT);
jnonce = cupsJWTGetClaimString(jwt, "nonce");
nonce  = oauth_load_value(auth_uri, resource_uri, _CUPS_OTYPE_NONCE, false);
// Check nonce
if (!jwt || (jnonce && nonce && strcmp(jnonce, nonce)))
  goto done;

When CUPS sent a nonce (it is persisted whenever the openid scope is requested), OIDC Core §3.1.3.7 requires the returned id_token to contain a matching nonce. Here, if the id_token omits the claim (jnonce == NULL), the jnonce && nonce && ... test short-circuits and the nonce check is skipped, so the token is accepted on signature alone. Suggested fix: if a nonce was sent (stored value exists), require jnonce != NULL && !strcmp(jnonce, nonce).

2. at_hash only validated for SHA-256-class id_tokens — cups/oauth.c:1377-1391

The access-token hash is always computed with sha2-256 and the code requires exactly 16 decoded at_hash bytes. For an id_token signed with an RS/ES/PS-384 or -512 algorithm the correct at_hash is 24/32 bytes, so a legitimate token is rejected (at_hash_bytes != 16goto done). The hash algorithm should follow the id_token's signing algorithm.

3. cupsd does not cryptographically validate inbound Bearer tokens; authorization uses the unverified email claim — scheduler/auth.c

In the Bearer branch (scheduler/auth.c:725-782), cupsd passes the token to cupsOAuthGetUserId(), which (when no cached identity exists) forwards it to the IdP userinfo_endpoint and trusts the JSON response (cups/oauth.c:1454-1518). On this inbound path there is no local verification of the token signature, iss, aud/azp, exp, or nbf (the existing cupsJWTHasValidSignature() / JWKS code is only used in the client-side token-exchange flow). Consequently a token minted by the same IdP for a different relying party can authenticate to CUPS (audience confusion), since CUPS never checks the token was issued for it.

Additionally, authorization matches the unverified email claim:

// scheduler/auth.c:1893
else if (!_cups_strcasecmp(username, name) || (con->email[0] && !_cups_strcasecmp(con->email, name)))
// scheduler/auth.c:1887,1920  -> cupsArrayFind(og->members, con->email)

email_verified is never consulted. If the configured IdP issues tokens with a user-settable/unverified email, a user could set email to a value listed in a policy's Require user ... or an OAuthGroup member file and gain that identity's access.

Suggested fixes: validate inbound tokens (signature via the IdP JWKS, plus iss/aud/exp) before trusting them, and require email_verified before using email for authorization decisions.

Expected behavior

Token and claim validation should follow the OIDC spec (enforce nonce when sent, algorithm-appropriate at_hash, and audience/expiry/email_verified checks).

System Information:

  • OS and its version: Ubuntu 24.04 (x86_64)
  • CUPS version: master / 2.5b1 (OAuth/OIDC support is 2.5-only)

Additional context

These are validation/hardening gaps rather than a demonstrated exploit; severity is IdP-dependent. Happy to split into separate issues if the maintainers prefer.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions