20static bool ct_eq(
const char *a,
const char *b,
size_t n)
23 for (
size_t i = 0; i < n; i++)
25 diff |= (uint8_t)(a[i] ^ b[i]);
35static bool pc_jwt_split(
const char *token,
size_t token_len,
size_t *signing_len,
const char **sig,
size_t *sig_len)
37 const char *d1 = (
const char *)memchr(token,
'.', token_len);
42 size_t rem = token_len - (size_t)(d1 + 1 - token);
43 const char *d2 = (
const char *)memchr(d1 + 1,
'.', rem);
48 size_t rem2 = token_len - (size_t)(d2 + 1 - token);
49 if (memchr(d2 + 1,
'.', rem2))
53 *signing_len = (size_t)(d2 - token);
62static bool pc_jwt_header_alg_is_hs256(
const char *header,
size_t hlen)
71 const char *p = strstr((
const char *)buf,
"\"alg\"");
77 while (*p ==
' ' || *p ==
':' || *p ==
'\t')
86 return strncmp(p,
"HS256", 5) == 0 && p[5] ==
'"';
89bool pc_jwt_verify_hs256(
const char *token,
size_t token_len,
const uint8_t *secret,
size_t secret_len)
99 if (!pc_jwt_split(token, token_len, &signing_len, &sig, &sig_len))
105 const char *d1 = (
const char *)memchr(token,
'.', token_len);
106 if (!pc_jwt_header_alg_is_hs256(token, (
size_t)(d1 - token)))
118 pc_hmac_sha256(secret, secret_len, (
const uint8_t *)token, signing_len, mac);
127 return ct_eq(computed, sig, 43);
130bool pc_jwt_bearer_valid(
const char *auth_header,
const uint8_t *secret,
size_t secret_len)
132 if (!auth_header || strncasecmp(auth_header,
"Bearer ", 7) != 0)
136 const char *tok = auth_header + 7;
141 return pc_jwt_verify_hs256(tok, strnlen(tok,
PC_JWT_MAX_LEN + 1), secret, secret_len);
144bool pc_jwt_time_valid(
const char *token,
size_t token_len,
long now_epoch,
long leeway_s)
146 if (!token || now_epoch <= 0)
153 if (pc_jwt_claim_int(token, token_len,
"exp", &exp) && now_epoch - exp > leeway_s)
159 if (pc_jwt_claim_int(token, token_len,
"nbf", &nbf) && nbf - now_epoch > leeway_s)
167bool pc_jwt_verify_hs256_at(
const char *token,
size_t token_len,
const uint8_t *secret,
size_t secret_len,
168 long now_epoch,
long leeway_s)
170 return pc_jwt_verify_hs256(token, token_len, secret, secret_len) &&
171 pc_jwt_time_valid(token, token_len, now_epoch, leeway_s);
174bool pc_jwt_bearer_valid_at(
const char *auth_header,
const uint8_t *secret,
size_t secret_len,
long now_epoch,
177 if (!auth_header || strncasecmp(auth_header,
"Bearer ", 7) != 0)
181 const char *tok = auth_header + 7;
186 return pc_jwt_verify_hs256_at(tok, strnlen(tok,
PC_JWT_MAX_LEN + 1), secret, secret_len, now_epoch, leeway_s);
189bool pc_jwt_claim_int(
const char *token,
size_t token_len,
const char *name,
long *out)
191 if (!token || !name || !out)
196 const char *d1 = (
const char *)memchr(token,
'.', token_len);
201 size_t rem = token_len - (size_t)(d1 + 1 - token);
202 const char *d2 = (
const char *)memchr(d1 + 1,
'.', rem);
207 const char *payload = d1 + 1;
208 size_t payload_len = (size_t)(d2 - payload);
219 pc_sb sb_key = {key,
sizeof(key), 0,
true};
226 if (kn <= 0 || kn >= (
int)
sizeof(key))
230 const char *p = strstr((
const char *)buf, key);
236 while (*p ==
' ' || *p ==
':' || *p ==
'\t')
246 if (*p <
'0' || *p >
'9')
251 while (*p >=
'0' && *p <=
'9')
253 v = v * 10UL + (
unsigned long)(*p++ -
'0');
255 *out = neg ? (long)(0UL - v) : (long)v;
259bool pc_jwt_claim_str(
const char *token,
size_t token_len,
const char *name,
char *out,
size_t out_cap)
261 if (!token || !name || !out || out_cap == 0)
267 const char *d1 = (
const char *)memchr(token,
'.', token_len);
272 size_t rem = token_len - (size_t)(d1 + 1 - token);
273 const char *d2 = (
const char *)memchr(d1 + 1,
'.', rem);
278 const char *payload = d1 + 1;
279 size_t payload_len = (size_t)(d2 - payload);
290 pc_sb sb_key2 = {key,
sizeof(key), 0,
true};
296 if (kn <= 0 || kn >= (
int)
sizeof(key))
300 const char *p = strstr((
const char *)buf, key);
306 while (*p ==
' ' || *p ==
':' || *p ==
'\t')
316 while (*p && *p !=
'"' && i + 1 < out_cap)
318 if (*p ==
'\\' && p[1])
333bool pc_jwt_scope_allows(
const char *scope_claim,
const char *required)
335 if (!scope_claim || !required || !*required)
340 const char *p = scope_claim;
347 const char *start = p;
348 while (*p && *p !=
' ')
352 if ((
size_t)(p - start) == rlen && memcmp(start, required, rlen) == 0)
size_t pc_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).
size_t pc_base64url_encode(const uint8_t *src, size_t src_len, char *dst)
Encode src_len bytes as base64url (RFC 4648 section 5): '-' / '_' in place of '+' / '/',...
void pc_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t len, uint8_t mac[PC_HMAC_SHA256_LEN])
Compute HMAC-SHA2-256 over a single contiguous buffer.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
#define PC_HMAC_SHA256_LEN
HMAC-SHA2-256 output length in bytes.
Zero-heap JWT (JSON Web Token) bearer-auth verification, HS256.
#define PC_JWT_MAX_LEN
Maximum accepted JWT length in bytes (header.payload.signature).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Bump-append target; ok latches false once an append would overflow cap.