OpenPrinting / OpenPrinting/cups
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
@michaelrsweet is already working on this.
Since Jun 27, 2026.
- Dominant language
- C
- Stars
- 1.8k
- Forks
- 331
- Avg merge
- 9h 42m
- Merged PRs (30d)
- 6
Description
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 != 16 → goto 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.