30const char *mem_find(
const char *hs,
const char *he,
const char *needle)
32 size_t nl = strnlen(needle, (
size_t)(he - hs) + 1);
33 if (nl == 0 || (
size_t)(he - hs) < nl)
35 for (
const char *p = hs; p + nl <= he; p++)
36 if (memcmp(p, needle, nl) == 0)
44bool find_field(
const char *s,
const char *e,
const char *name,
const char **vstart,
size_t *vlen,
char *type)
47 int nn = snprintf(needle,
sizeof(needle),
"\"%s\"", name);
50 if (nn <= 0 || nn >= (
int)
sizeof(needle))
52 const char *p = mem_find(s, e, needle);
56 while (p < e && (*p ==
' ' || *p ==
'\t' || *p ==
':' || *p ==
'\n' || *p ==
'\r'))
64 while (q < e && *q !=
'"')
66 if (*q ==
'\\' && q + 1 < e)
73 *vlen = (size_t)(q - p);
80 while (q < e && *q !=
']')
85 *vlen = (size_t)(q - p + 1);
89 if (*p ==
'-' || (*p >=
'0' && *p <=
'9'))
94 while (q < e && *q >=
'0' && *q <=
'9')
97 *vlen = (size_t)(q - p);
106bool get_str(
const char *s,
const char *e,
const char *name,
char *out,
size_t cap)
111 if (!find_field(s, e, name, &v, &vl, &t) || t !=
's')
120 if (ch ==
'\\' && i + 1 < vl)
133bool get_int64(
const char *s,
const char *e,
const char *name, int64_t *out)
138 if (!find_field(s, e, name, &v, &vl, &t) || t !=
'n' || vl == 0)
140 bool neg = (*v ==
'-');
141 size_t i = neg ? 1 : 0;
144 val = val * 10 + (v[i] -
'0');
145 *out = neg ? -val : val;
150bool aud_contains(
const char *s,
const char *e,
const char *want)
155 if (!find_field(s, e,
"aud", &v, &vl, &t))
157 size_t wl = strnlen(want, vl + 1);
159 return vl == wl && memcmp(v, want, wl) == 0;
163 const char *end = v + vl;
166 const char *q = (
const char *)memchr(p,
'"', (
size_t)(end - p));
170 const char *r = (
const char *)memchr(q,
'"', (
size_t)(end - q));
173 if ((
size_t)(r - q) == wl && memcmp(q, want, wl) == 0)
183bool split3(
const char *tok,
size_t len,
const char **seg,
size_t *seglen)
185 const char *d1 = (
const char *)memchr(tok,
'.', len);
188 size_t rem = len - (size_t)(d1 + 1 - tok);
189 const char *d2 = (
const char *)memchr(d1 + 1,
'.', rem);
192 size_t rem2 = len - (size_t)(d2 + 1 - tok);
193 if (memchr(d2 + 1,
'.', rem2))
196 seglen[0] = (size_t)(d1 - tok);
198 seglen[1] = (size_t)(d2 - d1 - 1);
201 return seglen[0] && seglen[1] && seglen[2];
207bool right_align(
const uint8_t *src,
size_t len, uint8_t *dst,
size_t width)
211 if (len == width + 1 && src[0] == 0)
219 memset(dst, 0, width);
220 memcpy(dst + (width - len), src, len);
224bool parse_rsa_jwk(
const char *s,
const char *e, DetwsOidcKey *key)
227 if (!get_str(s, e,
"n", b64,
sizeof(b64)))
229 uint8_t tmp[DETWS_OIDC_RSA_BYTES + 8];
230 size_t nlen =
base64url_decode(b64, strnlen(b64,
sizeof(b64)), tmp,
sizeof(tmp));
231 if (nlen == 0 || !right_align(tmp, nlen, key->n, DETWS_OIDC_RSA_BYTES))
234 if (!get_str(s, e,
"e", b64,
sizeof(b64)))
237 size_t elen =
base64url_decode(b64, strnlen(b64,
sizeof(b64)), e_tmp,
sizeof(e_tmp));
238 if (elen == 0 || !right_align(e_tmp, elen, key->e, 4))
245bool detws_oidc_token_kid(
const char *token,
size_t token_len,
char *kid_out,
size_t kid_cap)
247 if (!token || !kid_out || kid_cap == 0)
251 if (!split3(token, token_len, seg, seglen))
258 return get_str((
const char *)hdr, (
const char *)hdr + hn,
"kid", kid_out, kid_cap);
261bool detws_oidc_jwks_find(
const char *jwks_json,
const char *kid, DetwsOidcKey *key)
263 if (!jwks_json || !key)
265 const char *all_end = jwks_json + strnlen(jwks_json, DETWS_OIDC_JWKS_MAX);
266 const char *p = mem_find(jwks_json, all_end,
"\"keys\"");
267 p = p ? (
const char *)memchr(p,
'[', (
size_t)(all_end - p)) : nullptr;
274 const char *obj = (
const char *)memchr(p,
'{', (
size_t)(all_end - p));
277 const char *end = (
const char *)memchr(obj,
'}', (
size_t)(all_end - obj));
283 char this_kid[DETWS_OIDC_KID_LEN];
284 bool has_kid = get_str(obj, end,
"kid", this_kid,
sizeof(this_kid));
286 want = has_kid && strcmp(this_kid, kid) == 0;
290 if (want && parse_rsa_jwk(obj, end, key))
292 if (want && kid && *kid)
299DetwsOidcResult detws_oidc_verify_with_key(
const char *token,
size_t token_len,
const DetwsOidcKey *key,
300 const char *expected_iss,
const char *expected_aud, uint32_t now_unix,
301 DetwsOidcClaims *claims)
303 if (!token || !key || !key->loaded || token_len == 0 || token_len >
DETWS_OIDC_MAX_LEN)
304 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
308 if (!split3(token, token_len, seg, seglen))
309 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
316 const size_t hdr_cap = 512;
317 const size_t iss_cap = 256;
320 uint8_t *sig = (uint8_t *)
scratch_alloc(DETWS_OIDC_RSA_BYTES, 1);
323 if (!hdr || !sig || !pl || !iss)
324 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
329 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
332 if (!get_str((
const char *)hdr, (
const char *)hdr + hn,
"alg", alg,
sizeof(alg)) || strcmp(alg,
"RS256") != 0)
333 return DetwsOidcResult::DETWS_OIDC_ERR_ALG;
336 if (
base64url_decode(seg[2], seglen[2], sig, DETWS_OIDC_RSA_BYTES) != DETWS_OIDC_RSA_BYTES)
337 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
340 size_t signing_len = (size_t)(seg[1] + seglen[1] - token);
341 if (
ssh_rsa_verify(key->n, key->e, (
const uint8_t *)token, signing_len, sig, DETWS_OIDC_RSA_BYTES,
343 return DetwsOidcResult::DETWS_OIDC_ERR_SIGNATURE;
348 return DetwsOidcResult::DETWS_OIDC_ERR_FORMAT;
350 const char *ps = (
const char *)pl;
351 const char *pe = ps + pn;
353 if (expected_iss && *expected_iss)
355 if (!get_str(ps, pe,
"iss", iss, iss_cap) || strcmp(iss, expected_iss) != 0)
356 return DetwsOidcResult::DETWS_OIDC_ERR_ISS;
358 if (expected_aud && *expected_aud)
360 if (!aud_contains(ps, pe, expected_aud))
361 return DetwsOidcResult::DETWS_OIDC_ERR_AUD;
365 if (!get_int64(ps, pe,
"exp", &exp) || (int64_t)now_unix >= exp)
366 return DetwsOidcResult::DETWS_OIDC_ERR_EXPIRED;
368 if (get_int64(ps, pe,
"nbf", &nbf) && (int64_t)now_unix < nbf)
369 return DetwsOidcResult::DETWS_OIDC_ERR_NOT_YET;
373 claims->sub[0] =
'\0';
374 claims->email[0] =
'\0';
377 get_str(ps, pe,
"sub", claims->sub,
sizeof(claims->sub));
378 get_str(ps, pe,
"email", claims->email,
sizeof(claims->email));
379 get_int64(ps, pe,
"iat", &claims->iat);
381 return DetwsOidcResult::DETWS_OIDC_OK;
384DetwsOidcResult detws_oidc_verify(
const char *token,
size_t token_len,
const char *jwks_json,
const char *expected_iss,
385 const char *expected_aud, uint32_t now_unix, DetwsOidcClaims *claims)
387 char kid[DETWS_OIDC_KID_LEN];
388 if (!detws_oidc_token_kid(token, token_len, kid,
sizeof(kid)))
392 if (!detws_oidc_jwks_find(jwks_json, kid[0] ? kid : nullptr, &key))
393 return DetwsOidcResult::DETWS_OIDC_ERR_KEY;
394 return detws_oidc_verify_with_key(token, token_len, &key, expected_iss, expected_aud, now_unix, claims);
#define DETWS_OIDC_MAX_LEN
Max accepted OIDC ID-token length (also sizes the Authorization buffer).
size_t base64url_decode(const char *src, size_t src_len, uint8_t *dst, size_t dst_cap)
Decode src_len characters of base64url (RFC 4648 section 5, '-'/'_' alphabet; an '=' ends the input).
RAII scope guard for transient scratch borrows.
OpenID Connect ID-token verification, RS256 (DETWS_ENABLE_OIDC).
void * scratch_alloc(size_t n, size_t align)
Borrow n bytes of scratch, aligned to align.
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
int ssh_rsa_verify(const uint8_t n_be[SSH_RSA_KEY_BYTES], const uint8_t e_be4[4], const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len, SshRsaHash hash)
Verify an RSA PKCS#1 v1.5 signature (rsa-sha2-256/512) with a public key.
RSA-SHA2-256/512 host-key signing and public-key serialization.