DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
oidc.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file oidc.h
6 * @brief OpenID Connect ID-token verification, RS256 (DETWS_ENABLE_OIDC).
7 *
8 * A relying-party verifier for OIDC ID tokens (RFC 7519 JWT, OpenID Connect Core
9 * 3.1.3.7). Given an ID token and the issuer's JWKS, it:
10 * 1. parses the JWT header and requires `alg` == RS256,
11 * 2. selects the signing key by `kid` (or the sole key if the token has none),
12 * 3. verifies the RSASSA-PKCS1-v1.5 SHA-256 signature (via ssh_rsa_verify -
13 * real modular exponentiation; mbedTLS-accelerated on ESP32),
14 * 4. checks `iss`, `aud` (string or array), `exp`, and `nbf` against the
15 * caller's expectations and clock,
16 * 5. extracts `sub` / `email` and the times into ::DetwsOidcClaims.
17 *
18 * Zero-heap (fixed stack/BSS buffers) and host-tested against real openssl-signed
19 * RS256 vectors. The verifier is pure: it does NOT fetch anything. Fetching the
20 * discovery document / JWKS over HTTPS and caching keys is the caller's job (do it
21 * off the request hot path with the HTTP client, then pass the JWKS JSON here) -
22 * which keeps key rotation, caching policy, and TLS trust in the application's
23 * hands and the verifier deterministic.
24 *
25 * Only RS256 (the OIDC default and by far the most common) is supported; HS256
26 * shared-secret tokens are the JWT module's job (services/jwt), ES256 is out of
27 * scope.
28 *
29 * @author Douglas Quigg (dstroy0)
30 * @date 2026
31 */
32
33#ifndef DETERMINISTICESPASYNCWEBSERVER_OIDC_H
34#define DETERMINISTICESPASYNCWEBSERVER_OIDC_H
35
36#include "ServerConfig.h"
37#include <stddef.h>
38#include <stdint.h>
39
40#if DETWS_ENABLE_OIDC
41
42/** @brief RSA-2048 modulus size in bytes (the supported key size). */
43#define DETWS_OIDC_RSA_BYTES 256
44
45/** @brief Verification result codes (0 = success, negatives = failure reasons). */
46enum class DetwsOidcResult : int32_t
47{
48 DETWS_OIDC_OK = 0, ///< Token verified and all claims pass.
49 DETWS_OIDC_ERR_FORMAT = -1, ///< Not a 3-part JWT / bad base64 / oversized.
50 DETWS_OIDC_ERR_ALG = -2, ///< Header `alg` is not RS256.
51 DETWS_OIDC_ERR_KEY = -3, ///< No usable RSA key (kid not found / malformed JWK).
52 DETWS_OIDC_ERR_SIGNATURE = -4, ///< RSA signature verification failed.
53 DETWS_OIDC_ERR_ISS = -5, ///< `iss` does not match the expected issuer.
54 DETWS_OIDC_ERR_AUD = -6, ///< `aud` does not contain the expected audience.
55 DETWS_OIDC_ERR_EXPIRED = -7, ///< `exp` is missing or in the past.
56 DETWS_OIDC_ERR_NOT_YET = -8, ///< `nbf` is in the future.
57};
58
59/** @brief A parsed RSA public key (from a JWKS entry). */
60struct DetwsOidcKey
61{
62 uint8_t n[DETWS_OIDC_RSA_BYTES]; ///< Modulus, big-endian, right-aligned.
63 uint8_t e[4]; ///< Public exponent, big-endian (4 bytes).
64 bool loaded; ///< True once n/e are populated.
65};
66
67/** @brief Claims extracted from a verified token. */
68struct DetwsOidcClaims
69{
70 char sub[DETWS_OIDC_SUB_LEN]; ///< Subject identifier.
71 char email[DETWS_OIDC_EMAIL_LEN]; ///< Email (empty if the claim is absent).
72 int64_t iat; ///< Issued-at (0 if absent). 64-bit: epoch seconds outlive 2038.
73 int64_t exp; ///< Expiry (epoch seconds). 64-bit.
74};
75
76/**
77 * @brief Read the `kid` from a token's JWT header.
78 * @return true if a `kid` string is present (copied, null-terminated, into @p out).
79 */
80bool detws_oidc_token_kid(const char *token, size_t token_len, char *kid_out, size_t kid_cap);
81
82/**
83 * @brief Extract an RSA JWK by @p kid from a JWKS JSON document.
84 *
85 * @param jwks_json the JWKS document (`{"keys":[ {RSA JWK}, ... ]}`).
86 * @param kid key id to match; nullptr / "" selects the first RSA key.
87 * @param key receives n/e on success.
88 * @return true if a matching RSA key was found and parsed.
89 */
90bool detws_oidc_jwks_find(const char *jwks_json, const char *kid, DetwsOidcKey *key);
91
92/**
93 * @brief Verify an ID token against an already-resolved key.
94 *
95 * @param token,token_len the compact ID token.
96 * @param key the issuer's RSA public key.
97 * @param expected_iss required `iss` value (exact match).
98 * @param expected_aud required `aud` value (string match, or membership of
99 * the `aud` array).
100 * @param now_unix current time (epoch seconds) for exp/nbf checks.
101 * @param claims receives extracted claims on success (may be nullptr).
102 * @return ::DETWS_OIDC_OK or a negative ::DetwsOidcResult.
103 */
104DetwsOidcResult detws_oidc_verify_with_key(const char *token, size_t token_len, const DetwsOidcKey *key,
105 const char *expected_iss, const char *expected_aud, uint32_t now_unix,
106 DetwsOidcClaims *claims);
107
108/**
109 * @brief Verify an ID token, resolving the key from @p jwks_json by the token's kid.
110 *
111 * Convenience wrapper over detws_oidc_jwks_find() + detws_oidc_verify_with_key().
112 * @return ::DETWS_OIDC_OK or a negative ::DetwsOidcResult (ERR_KEY if no key matches).
113 */
114DetwsOidcResult detws_oidc_verify(const char *token, size_t token_len, const char *jwks_json, const char *expected_iss,
115 const char *expected_aud, uint32_t now_unix, DetwsOidcClaims *claims);
116
117#endif // DETWS_ENABLE_OIDC
118#endif // DETERMINISTICESPASYNCWEBSERVER_OIDC_H
User-facing configuration for DeterministicESPAsyncWebServer.