cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
jwt.c
Go to the documentation of this file.
5
6#include <cjson/cJSON.h>
7
8#if defined(CWIST_TLS_MBEDTLS)
9/* mbedTLS backend: HS256 via the md module, ES256 via pk/ecp. Ed25519 has no
10 * mbedTLS 3.6 implementation, so EdDSA sign/verify fails at runtime (the
11 * public API surface is unchanged). */
12#include <mbedtls/pk.h>
13#include <mbedtls/ecp.h>
14#include <mbedtls/ecdsa.h>
15#include <mbedtls/md.h>
16#include <mbedtls/bignum.h>
17#include <openssl/rand.h> /* seam shim: RAND_bytes over CTR-DRBG */
18#include <stdint.h>
19#else
20#include <openssl/hmac.h>
21#include <openssl/sha.h>
22#include <openssl/evp.h>
23#include <openssl/pem.h>
24#include <openssl/bio.h>
25#include <openssl/ec.h>
26#include <openssl/ecdsa.h>
27#include <openssl/bn.h>
28#include <openssl/obj.h>
29#endif
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <time.h>
35
41/* --------------------------------------------------------------------------
42 * Internal: Base64URL helpers
43 * -------------------------------------------------------------------------- */
44
46static size_t b64url_encoded_len(size_t input_len) {
47 return ((input_len + 2) / 3) * 4 + 1; /* +1 for '\0' */
48}
49
51static void b64url_encode(const unsigned char *src, size_t src_len, char *dst) {
52 static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
53 size_t i = 0, j = 0;
54 while (i + 2 < src_len) {
55 uint32_t v = ((uint32_t)src[i] << 16) | ((uint32_t)src[i + 1] << 8) | src[i + 2];
56 dst[j++] = tbl[(v >> 18) & 0x3F];
57 dst[j++] = tbl[(v >> 12) & 0x3F];
58 dst[j++] = tbl[(v >> 6) & 0x3F];
59 dst[j++] = tbl[v & 0x3F];
60 i += 3;
61 }
62 if (i + 1 == src_len) {
63 uint32_t v = (uint32_t)src[i] << 4;
64 dst[j++] = tbl[(v >> 6) & 0x3F];
65 dst[j++] = tbl[v & 0x3F];
66 } else if (i + 2 == src_len) {
67 uint32_t v = ((uint32_t)src[i] << 10) | ((uint32_t)src[i + 1] << 2);
68 dst[j++] = tbl[(v >> 12) & 0x3F];
69 dst[j++] = tbl[(v >> 6) & 0x3F];
70 dst[j++] = tbl[v & 0x3F];
71 }
72 dst[j] = '\0';
73}
74
85static unsigned char *b64url_decode(const char *src, size_t src_len, size_t *out_len) {
86 /* Accept either standard or url-safe alphabet; strip '=' padding. */
87 static const signed char lut[256] = {
88 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
89 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62,
90 -1, 62, -1, 63, /* '+' -> 62, '-' -> 62, '/' & '_' -> 63 */
91 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, /* '0'-'9', '=' -> 0 */
92 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 'A'-'O' */
93 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, /* 'P'-'Z', '_' -> 63 */
94 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 'a'-'o' */
95 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 'p'-'z' */
96 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
97 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
98 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
99 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
100 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
101 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
102 };
103
104 /* Calculate how many padding chars to expect */
105 size_t padded_len = src_len;
106 while (padded_len > 0 && src[padded_len - 1] == '=') {
107 padded_len--;
108 }
109
110 size_t decoded_len = (padded_len * 3) / 4;
111 unsigned char *out = (unsigned char *)cwist_alloc(decoded_len + 1);
112 if (!out) return NULL;
113
114 size_t i = 0, j = 0;
115 while (i + 3 < padded_len) {
116 signed char a = lut[(unsigned char)src[i]];
117 signed char b = lut[(unsigned char)src[i + 1]];
118 signed char c = lut[(unsigned char)src[i + 2]];
119 signed char d = lut[(unsigned char)src[i + 3]];
120 if (a < 0 || b < 0 || c < 0 || d < 0) {
121 cwist_free(out);
122 return NULL;
123 }
124 out[j++] = (unsigned char)((a << 2) | (b >> 4));
125 out[j++] = (unsigned char)((b << 4) | (c >> 2));
126 out[j++] = (unsigned char)((c << 6) | d);
127 i += 4;
128 }
129
130 size_t rem = padded_len - i;
131 if (rem == 2) {
132 signed char a = lut[(unsigned char)src[i]];
133 signed char b = lut[(unsigned char)src[i + 1]];
134 if (a < 0 || b < 0) {
135 cwist_free(out);
136 return NULL;
137 }
138 out[j++] = (unsigned char)((a << 2) | (b >> 4));
139 } else if (rem == 3) {
140 signed char a = lut[(unsigned char)src[i]];
141 signed char b = lut[(unsigned char)src[i + 1]];
142 signed char c = lut[(unsigned char)src[i + 2]];
143 if (a < 0 || b < 0 || c < 0) {
144 cwist_free(out);
145 return NULL;
146 }
147 out[j++] = (unsigned char)((a << 2) | (b >> 4));
148 out[j++] = (unsigned char)((b << 4) | (c >> 2));
149 } else if (rem == 1) {
150 /* RFC 4648 section 4: 1 base64 character (6 bits) cannot encode a whole byte.
151 * A remainder of 1 is malformed and must be rejected. */
152 cwist_free(out);
153 return NULL;
154 }
155
156 out[j] = '\0';
157 *out_len = j;
158 return out;
159}
160
161/* --------------------------------------------------------------------------
162 * Internal: algorithm dispatch (HS256, ES256, Ed25519)
163 * -------------------------------------------------------------------------- */
164
170
171static const char *jwt_alg_name(jwt_alg_t alg) {
172 switch (alg) {
173 case JWT_ALG_HS256: return "HS256";
174 case JWT_ALG_ES256: return "ES256";
175 case JWT_ALG_ED25519: return "EdDSA";
176 }
177 return NULL;
178}
179
181static bool hmac_sha256(const char *key, size_t key_len, const char *msg, size_t msg_len,
182 unsigned char out[32]) {
183#if defined(CWIST_TLS_MBEDTLS)
185 (const unsigned char *)key, key_len,
186 (const unsigned char *)msg, msg_len, out) == 0;
187#else
188 unsigned int out_len = 0;
189 unsigned char *r =
190 HMAC(EVP_sha256(), key, (int)key_len, (const unsigned char *)msg, msg_len, out, &out_len);
191 return r != NULL && out_len == 32;
192#endif
193}
194
195#if defined(CWIST_TLS_MBEDTLS)
196
198static int jwt_mbedtls_rng(void *p_rng, unsigned char *buf, size_t len) {
199 (void)p_rng;
200 return len <= (size_t)INT32_MAX && RAND_bytes(buf, (int)len) == 1 ? 0 : -1;
201}
202
204static bool jwt_load_private_key(mbedtls_pk_context *pk, const char *pem) {
206 return mbedtls_pk_parse_key(pk, (const unsigned char *)pem, strlen(pem) + 1, NULL, 0,
207 jwt_mbedtls_rng, NULL) == 0;
208}
209
211static bool jwt_load_public_key(mbedtls_pk_context *pk, const char *pem) {
213 if (mbedtls_pk_parse_public_key(pk, (const unsigned char *)pem, strlen(pem) + 1) == 0) {
214 return true;
215 }
218 return mbedtls_pk_parse_key(pk, (const unsigned char *)pem, strlen(pem) + 1, NULL, 0,
219 jwt_mbedtls_rng, NULL) == 0;
220}
221
223static bool jwt_key_is_p256(const mbedtls_pk_context *pk) {
225 mbedtls_pk_ec(*pk)->MBEDTLS_PRIVATE(grp).id == MBEDTLS_ECP_DP_SECP256R1;
226}
227
229static bool jwt_sha256(const char *msg, size_t msg_len, unsigned char out[32]) {
230 return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), (const unsigned char *)msg,
231 msg_len, out) == 0;
232}
233
235static unsigned char *jwt_mbedtls_es256_sign(const char *key, const unsigned char *hash,
236 size_t *out_len) {
238 if (!jwt_load_private_key(&pk, key)) return NULL;
239 if (!jwt_key_is_p256(&pk)) {
241 return NULL;
242 }
244 mbedtls_mpi r, s;
247 unsigned char *sig = NULL;
248 if (mbedtls_ecdsa_sign(&kp->MBEDTLS_PRIVATE(grp), &r, &s, &kp->MBEDTLS_PRIVATE(d), hash, 32,
249 jwt_mbedtls_rng, NULL) == 0) {
250 sig = (unsigned char *)cwist_alloc(64);
251 if (sig && (mbedtls_mpi_write_binary(&r, sig, 32) != 0 ||
252 mbedtls_mpi_write_binary(&s, sig + 32, 32) != 0)) {
254 sig = NULL;
255 }
256 if (sig) *out_len = 64;
257 }
261 return sig;
262}
263
265static bool jwt_mbedtls_es256_verify(const char *key, const unsigned char *hash,
266 const unsigned char *sig, size_t sig_len) {
267 if (sig_len != 64) return false;
269 if (!jwt_load_public_key(&pk, key)) return false;
270 if (!jwt_key_is_p256(&pk)) {
272 return false;
273 }
275 mbedtls_mpi r, s;
278 bool ok = mbedtls_mpi_read_binary(&r, sig, 32) == 0 &&
279 mbedtls_mpi_read_binary(&s, sig + 32, 32) == 0 &&
280 mbedtls_ecdsa_verify(&kp->MBEDTLS_PRIVATE(grp), hash, 32, &kp->MBEDTLS_PRIVATE(Q), &r,
281 &s) == 0;
285 return ok;
286}
287
288#else /* BoringSSL backend */
289
291static EVP_PKEY *jwt_load_private_key(const char *pem) {
292 BIO *bio = BIO_new_mem_buf(pem, (int)strlen(pem));
293 if (!bio) return NULL;
295 BIO_free(bio);
296 return key;
297}
298
300static EVP_PKEY *jwt_load_public_key(const char *pem) {
301 BIO *bio = BIO_new_mem_buf(pem, (int)strlen(pem));
302 if (!bio) return NULL;
304 if (!key) {
305 /* The first read consumed the buffer; rewind before the fallback. */
306 BIO_reset(bio);
308 }
309 BIO_free(bio);
310 return key;
311}
312
314static bool jwt_key_is_p256(EVP_PKEY *key) {
315 if (EVP_PKEY_id(key) != EVP_PKEY_EC) return false;
317 if (!ec) return false;
320}
321
322#endif /* CWIST_TLS_MBEDTLS */
323
329static unsigned char *jwt_sign_bytes(jwt_alg_t alg, const char *key, const char *msg,
330 size_t msg_len, size_t *out_len) {
331 if (alg == JWT_ALG_HS256) {
332 unsigned char *sig = (unsigned char *)cwist_alloc(32);
333 if (!sig) return NULL;
334 if (!hmac_sha256(key, strlen(key), msg, msg_len, sig)) {
336 return NULL;
337 }
338 *out_len = 32;
339 return sig;
340 }
341
342#if defined(CWIST_TLS_MBEDTLS)
343 if (alg == JWT_ALG_ES256) {
344 unsigned char hash[32];
345 if (!jwt_sha256(msg, msg_len, hash)) return NULL;
346 return jwt_mbedtls_es256_sign(key, hash, out_len);
347 }
348 /* EdDSA: mbedTLS 3.6 LTS has no Ed25519; report signing failure rather
349 * than emitting an unverifiable token. */
350 return NULL;
351#else
352 EVP_PKEY *pkey = jwt_load_private_key(key);
353 if (!pkey) return NULL;
354 if (alg == JWT_ALG_ES256 && !jwt_key_is_p256(pkey)) {
355 EVP_PKEY_free(pkey);
356 return NULL;
357 }
358 if (alg == JWT_ALG_ED25519 && EVP_PKEY_id(pkey) != EVP_PKEY_ED25519) {
359 EVP_PKEY_free(pkey);
360 return NULL;
361 }
362
363 EVP_MD_CTX *ctx = EVP_MD_CTX_new();
364 if (!ctx) {
365 EVP_PKEY_free(pkey);
366 return NULL;
367 }
368 const EVP_MD *md = (alg == JWT_ALG_ES256) ? EVP_sha256() : NULL;
369 unsigned char *sig = NULL;
370 if (EVP_DigestSignInit(ctx, NULL, md, NULL, pkey) != 1) goto fail;
371
372 size_t raw_len = 0;
373 if (EVP_DigestSign(ctx, NULL, &raw_len, (const unsigned char *)msg, msg_len) != 1) goto fail;
374 {
375 unsigned char *raw = (unsigned char *)cwist_alloc(raw_len);
376 if (!raw) goto fail;
377 if (EVP_DigestSign(ctx, raw, &raw_len, (const unsigned char *)msg, msg_len) != 1) {
378 cwist_free(raw);
379 goto fail;
380 }
381 if (alg == JWT_ALG_ED25519) {
382 sig = raw; /* Ed25519 signs directly in JOSE form (64 bytes) */
383 *out_len = raw_len;
384 } else {
385 /* Convert the ASN.1 DER ECDSA signature to raw 32-byte R||S. */
386 sig = (unsigned char *)cwist_alloc(64);
387 if (!sig) {
388 cwist_free(raw);
389 goto fail;
390 }
392 cwist_free(raw);
393 if (!ecsig) {
395 sig = NULL;
396 goto fail;
397 }
398 const BIGNUM *r, *s;
399 ECDSA_SIG_get0(ecsig, &r, &s);
400 if (BN_bn2bin_padded(sig, 32, r) != 1 ||
401 BN_bn2bin_padded(sig + 32, 32, s) != 1) {
404 sig = NULL;
405 goto fail;
406 }
408 *out_len = 64;
409 }
410 }
411
412 EVP_MD_CTX_free(ctx);
413 EVP_PKEY_free(pkey);
414 return sig;
415
416fail:
417 EVP_MD_CTX_free(ctx);
418 EVP_PKEY_free(pkey);
419 return NULL;
420#endif /* CWIST_TLS_MBEDTLS */
421}
422
429static bool jwt_verify_bytes(jwt_alg_t alg, const char *key, const char *msg, size_t msg_len,
430 const unsigned char *sig, size_t sig_len) {
431
432 if (alg == JWT_ALG_HS256) {
433 unsigned char expected[32];
434 if (sig_len != 32) return false;
435 if (!hmac_sha256(key, strlen(key), msg, msg_len, expected)) return false;
436 int cmp = 0;
437 for (int i = 0; i < 32; i++) {
438 cmp |= expected[i] ^ sig[i];
439 }
440 return cmp == 0;
441 }
442
443#if defined(CWIST_TLS_MBEDTLS)
444 if (alg == JWT_ALG_ES256) {
445 unsigned char hash[32];
446 if (!jwt_sha256(msg, msg_len, hash)) return false;
448 }
449 /* EdDSA: mbedTLS 3.6 LTS has no Ed25519; verification always fails. */
450 return false;
451#else
452 bool ok = false;
453 EVP_MD_CTX *ctx = NULL;
454 EVP_PKEY *pkey = jwt_load_public_key(key);
455 if (!pkey) return false;
456 if (alg == JWT_ALG_ES256 && !jwt_key_is_p256(pkey)) goto fail;
457 if (alg == JWT_ALG_ED25519 && EVP_PKEY_id(pkey) != EVP_PKEY_ED25519) goto fail;
458
459 ctx = EVP_MD_CTX_new();
460 if (!ctx) goto fail;
461 const EVP_MD *md = (alg == JWT_ALG_ES256) ? EVP_sha256() : NULL;
462 if (EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey) != 1) goto out_ctx;
463
464 if (alg == JWT_ALG_ED25519) {
465 ok = EVP_DigestVerify(ctx, sig, sig_len, (const unsigned char *)msg, msg_len) == 1;
466 } else {
467 /* Re-encode raw R||S as the ASN.1 DER the EVP interface expects. */
468 if (sig_len != 64) goto out_ctx;
469 BIGNUM *r = BN_bin2bn(sig, 32, NULL);
470 BIGNUM *s = BN_bin2bn(sig + 32, 32, NULL);
472 if (!r || !s || !ecsig || ECDSA_SIG_set0(ecsig, r, s) != 1) {
473 BN_free(r);
474 BN_free(s);
476 goto out_ctx;
477 }
478 uint8_t *der = NULL;
479 size_t der_len = 0;
480 if (ECDSA_SIG_to_bytes(&der, &der_len, ecsig) == 1) {
481 ok = EVP_DigestVerify(ctx, der, der_len, (const unsigned char *)msg, msg_len) == 1;
483 }
484 ECDSA_SIG_free(ecsig); /* takes r and s */
485 }
486
487out_ctx:
488 EVP_MD_CTX_free(ctx);
489fail:
490 EVP_PKEY_free(pkey);
491 return ok;
492#endif /* CWIST_TLS_MBEDTLS */
493}
494
495/* --------------------------------------------------------------------------
496 * JWT claims (opaque struct)
497 * -------------------------------------------------------------------------- */
498
499struct cwist_jwt_claims {
500 cJSON *json;
501};
502
503/* --------------------------------------------------------------------------
504 * Public API
505 * -------------------------------------------------------------------------- */
506
516static char *jwt_sign_common(jwt_alg_t alg, const char *payload_json, const char *key,
517 const char *kid, long exp_seconds) {
518 if (!payload_json || !key) return NULL;
519
520 /* --- Build header JSON ------------------------------------------------ */
521 cJSON *header = cJSON_CreateObject();
522 if (!header) return NULL;
523 cJSON_AddStringToObject(header, "alg", jwt_alg_name(alg));
524 cJSON_AddStringToObject(header, "typ", "JWT");
525 if (kid) {
526 cJSON_AddStringToObject(header, "kid", kid);
527 }
528 char *header_json = cJSON_PrintUnformatted(header);
529 cJSON_Delete(header);
530 if (!header_json) return NULL;
531
532 /* --- Optionally inject exp/iat into payload --------------------------- */
533 cJSON *payload = cJSON_Parse(payload_json);
534 if (!payload) {
536 return NULL;
537 }
538
539 if (exp_seconds > 0) {
540 time_t now = time(NULL);
541 /* Only add if not already present */
542 if (!cJSON_GetObjectItemCaseSensitive(payload, "iat")) {
543 cJSON_AddNumberToObject(payload, "iat", (double)now);
544 }
545 if (!cJSON_GetObjectItemCaseSensitive(payload, "exp")) {
546 cJSON_AddNumberToObject(payload, "exp", (double)(now + exp_seconds));
547 }
548 }
549
551 cJSON_Delete(payload);
552 if (!final_payload_json) {
554 return NULL;
555 }
556
557 /* --- Base64URL encode header and payload ------------------------------ */
558 size_t hdr_enc_len = b64url_encoded_len(strlen(header_json));
559 size_t pay_enc_len = b64url_encoded_len(strlen(final_payload_json));
560
563 if (!hdr_enc || !pay_enc) {
566 return NULL;
567 }
568
569 b64url_encode((const unsigned char *)header_json, strlen(header_json), hdr_enc);
570 b64url_encode((const unsigned char *)final_payload_json, strlen(final_payload_json), pay_enc);
573
574 /* --- Build "header.payload" signing input using sstring --------------- */
576 if (!signing_input) {
577 return NULL;
578 }
582
583 /* --- Compute the signature -------------------------------------------- */
584 size_t sig_len = 0;
585 unsigned char *sig_raw = jwt_sign_bytes(alg, key, signing_input->data, signing_input->size,
586 &sig_len);
587 if (!sig_raw) {
589 return NULL;
590 }
591
592 size_t sig_enc_len = b64url_encoded_len(sig_len);
594 if (!sig_enc) {
597 return NULL;
598 }
599 b64url_encode(sig_raw, sig_len, sig_enc);
601
602 /* --- Assemble final token using sstring ------------------------------- */
604 if (!token) {
606 return NULL;
607 }
611
612 char *result = cwist_strdup(token->data);
613
616
617 return result;
618}
619
620char *cwist_jwt_sign(const char *payload_json, const char *secret, long exp_seconds) {
621 return jwt_sign_common(JWT_ALG_HS256, payload_json, secret, NULL, exp_seconds);
622}
623
624char *cwist_jwt_sign_es256(const char *payload_json, const char *pem_private_key, const char *kid,
625 long exp_seconds) {
626 return jwt_sign_common(JWT_ALG_ES256, payload_json, pem_private_key, kid, exp_seconds);
627}
628
630 const char *kid, long exp_seconds) {
631 return jwt_sign_common(JWT_ALG_ED25519, payload_json, pem_private_key, kid, exp_seconds);
632}
633
641static cwist_jwt_claims *jwt_verify_common(jwt_alg_t alg, const char *token, const char *key) {
642 if (!token || !key) return NULL;
643
644 /* --- Split the token into three parts --------------------------------- */
645 /* We need mutable copies to split on '.' */
646 char *tok_copy = cwist_strdup(token);
647 if (!tok_copy) return NULL;
648
649 char *dot1 = strchr(tok_copy, '.');
650 if (!dot1) {
652 return NULL;
653 }
654 *dot1 = '\0';
655
656 char *dot2 = strchr(dot1 + 1, '.');
657 if (!dot2) {
659 return NULL;
660 }
661 *dot2 = '\0';
662
663 /* --- Validate the header: must declare exactly our alg (RFC 8725 3.1) - */
664 size_t hdr_json_len = 0;
665 unsigned char *hdr_json = b64url_decode(tok_copy, strlen(tok_copy), &hdr_json_len);
666 if (!hdr_json) {
668 return NULL;
669 }
670 cJSON *hdr = cJSON_ParseWithLength((const char *)hdr_json, hdr_json_len);
672 if (!hdr) {
674 return NULL;
675 }
678 strcmp(alg_item->valuestring, jwt_alg_name(alg)) == 0;
679 cJSON_Delete(hdr);
680 if (!alg_ok) {
682 return NULL;
683 }
684
685 const char *pay_enc = dot1 + 1;
686 const char *sig_enc = dot2 + 1;
687
688 /* --- Decode the provided signature ------------------------------------ */
689 size_t provided_sig_len = 0;
690 unsigned char *provided_sig = b64url_decode(sig_enc, strlen(sig_enc), &provided_sig_len);
691 if (!provided_sig) {
693 return NULL;
694 }
695
696 /* --- Verify over the original "hdr_enc.pay_enc" signing input --------- */
697 size_t first_two_len = (size_t)(dot2 - tok_copy);
698 bool sig_ok = jwt_verify_bytes(alg, key, token, first_two_len, provided_sig,
701 if (!sig_ok) {
703 return NULL; /* signature mismatch */
704 }
705
706 /* --- Decode and parse the payload ------------------------------------- */
707 size_t pay_json_len = 0;
708 unsigned char *pay_json = b64url_decode(pay_enc, strlen(pay_enc), &pay_json_len);
710
711 if (!pay_json) return NULL;
712
713 cJSON *json = cJSON_ParseWithLength((const char *)pay_json, pay_json_len);
715
716 if (!json) return NULL;
717
718 /* --- Validate "exp" and "nbf" claims with leeway for clock skew/high-RTT --- */
719#define CWIST_JWT_TIME_LEEWAY 300
720 time_t now = time(NULL);
723 time_t exp_time = (time_t)exp_item->valuedouble;
725 cJSON_Delete(json);
726 return NULL; /* token expired */
727 }
728 }
731 time_t nbf_time = (time_t)nbf_item->valuedouble;
733 cJSON_Delete(json);
734 return NULL; /* token not yet valid */
735 }
736 }
737
738 /* --- Build and return claims object ----------------------------------- */
739 cwist_jwt_claims *claims = (cwist_jwt_claims *)cwist_alloc(sizeof(cwist_jwt_claims));
740 if (!claims) {
741 cJSON_Delete(json);
742 return NULL;
743 }
744 claims->json = json;
745 return claims;
746}
747
748cwist_jwt_claims *cwist_jwt_verify(const char *token, const char *secret) {
749 return jwt_verify_common(JWT_ALG_HS256, token, secret);
750}
751
753 return jwt_verify_common(JWT_ALG_ES256, token, pem_key);
754}
755
757 return jwt_verify_common(JWT_ALG_ED25519, token, pem_key);
758}
759
766const char *cwist_jwt_claims_get(const cwist_jwt_claims *claims, const char *key) {
767 if (!claims || !key) return NULL;
768 cJSON *item = cJSON_GetObjectItemCaseSensitive(claims->json, key);
769 if (!item) return NULL;
770 if (cJSON_IsString(item)) return item->valuestring;
771 /* For numeric claims return the raw JSON representation */
772 return NULL;
773}
774
779void cwist_jwt_claims_destroy(cwist_jwt_claims *claims) {
780 if (!claims) return;
781 cJSON_Delete(claims->json);
782 cwist_free(claims);
783}
784
785/* --------------------------------------------------------------------------
786 * Sequenced JWT transport helpers
787 * -------------------------------------------------------------------------- */
788
801 size_t *out_count) {
802 if (!token || chunk_payload_size == 0 || !out_count) return NULL;
803 *out_count = 0;
804
805 size_t token_len = strlen(token);
806 if (token_len == 0) return NULL;
807
810 return NULL;
811 }
812
813 cwist_jwt_chunk_t *chunks =
815 if (!chunks) {
817 return NULL;
818 }
819
820 for (size_t i = 0; i < msg.count; i++) {
821 chunks[i].data = msg.chunks[i];
822 chunks[i].len = msg.chunk_lens[i];
823 msg.chunks[i] = NULL; /* ownership transferred */
824 }
825
826 size_t count = msg.count;
828 *out_count = count;
829 return chunks;
830}
831
838 if (!chunks) return;
839 for (size_t i = 0; i < count; i++) cwist_free(chunks[i].data);
840 cwist_free(chunks);
841}
842
852char *cwist_jwt_join_chunks(const cwist_jwt_chunk_t *chunks, size_t count) {
853 if (!chunks || count == 0) return NULL;
854
856 if (!a) return NULL;
857
858 for (size_t i = 0; i < count; i++) {
860 if (!cwist_seq_chunk_parse(chunks[i].data, chunks[i].len, &chunk)) {
862 return NULL;
863 }
866 return NULL;
867 }
868 }
869
870 const uint8_t *data = NULL;
871 size_t len = 0;
872 bool ok = cwist_seq_assembler_get_data(a, &data, &len);
873 char *token = NULL;
874 if (ok && len > 0) {
875 token = (char *)cwist_alloc(len + 1);
876 if (token) {
877 memcpy(token, data, len);
878 token[len] = '\0';
879 }
880 }
881
883 return token;
884}
885
899 size_t *out_count) {
900 if (!payload_json || !secret || chunk_payload_size == 0 || !out_count) return NULL;
901 *out_count = 0;
902
904 if (!token) return NULL;
905
908 return chunks;
909}
910
922 const char *secret) {
923 if (!chunks || count == 0 || !secret) return NULL;
924 char *token = cwist_jwt_join_chunks(chunks, count);
925 if (!token) return NULL;
928 return claims;
929}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
#define CWIST_DEFER_FREE
Attach to a pointer variable's declaration to cwist_free() it automatically when that variable's encl...
Definition alloc.h:101
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Safe string type with helper methods.
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
Definition sstring.c:802
cwist_error_t cwist_sstring_append(cwist_sstring *str, const char *data)
Append a C string.
Definition sstring.c:559
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
Definition sstring.c:823
mbedTLS TLS backend: EVP subset (HMAC digest tags, AES-256-CBC).
struct cwist_mtls_evp_md EVP_MD
Definition evp.h:19
#define EVP_sha256
Definition evp.h:25
mbedTLS TLS backend: HMAC subset (HMAC-SHA256, one-shot + streaming).
#define HMAC
Definition hmac.h:21
const char * cwist_jwt_claims_get(const cwist_jwt_claims *claims, const char *key)
Retrieve one string-valued claim from a verified JWT payload.
Definition jwt.c:766
void cwist_jwt_claims_destroy(cwist_jwt_claims *claims)
Destroy a verified claims object and its parsed JSON payload.
Definition jwt.c:779
cwist_jwt_chunk_t * cwist_jwt_split_chunks(const char *token, uint16_t chunk_payload_size, size_t *out_count)
Split a JWT token into sequenced transport chunks.
Definition jwt.c:800
char * cwist_jwt_join_chunks(const cwist_jwt_chunk_t *chunks, size_t count)
Reassemble a JWT token from sequenced transport chunks.
Definition jwt.c:852
cwist_jwt_claims * cwist_jwt_verify_es256(const char *token, const char *pem_key)
Verify an ES256-signed JWT token and return the decoded claims.
Definition jwt.c:752
char * cwist_jwt_sign(const char *payload_json, const char *secret, long exp_seconds)
Sign a JWT token with HMAC-SHA256.
Definition jwt.c:620
cwist_jwt_claims * cwist_jwt_verify(const char *token, const char *secret)
Verify a JWT token and return the decoded claims.
Definition jwt.c:748
cwist_jwt_chunk_t * cwist_jwt_sign_chunks(const char *payload_json, const char *secret, long exp_seconds, uint16_t chunk_payload_size, size_t *out_count)
Sign a payload as an HS256 JWT and split it into sequenced transport chunks.
Definition jwt.c:897
jwt_alg_t
Definition jwt.c:165
@ JWT_ALG_ED25519
Definition jwt.c:168
@ JWT_ALG_ES256
Definition jwt.c:167
@ JWT_ALG_HS256
Definition jwt.c:166
char * cwist_jwt_sign_es256(const char *payload_json, const char *pem_private_key, const char *kid, long exp_seconds)
Sign a JWT token with ES256 (ECDSA over P-256 with SHA-256).
Definition jwt.c:624
#define CWIST_JWT_TIME_LEEWAY
cwist_jwt_claims * cwist_jwt_verify_chunks(const cwist_jwt_chunk_t *chunks, size_t count, const char *secret)
Reassemble sequenced chunks into a token, verify it, and return its claims.
Definition jwt.c:921
void cwist_jwt_chunks_free(cwist_jwt_chunk_t *chunks, size_t count)
Free a chunk array produced by cwist_jwt_split_chunks() or cwist_jwt_sign_chunks().
Definition jwt.c:837
cwist_jwt_claims * cwist_jwt_verify_ed25519(const char *token, const char *pem_key)
Verify an Ed25519-signed JWT token and return the decoded claims.
Definition jwt.c:756
char * cwist_jwt_sign_ed25519(const char *payload_json, const char *pem_private_key, const char *kid, long exp_seconds)
Sign a JWT token with EdDSA (Ed25519, RFC 8037).
Definition jwt.c:629
JWT (JSON Web Token) generation and verification.
struct cwist_jwt_claims cwist_jwt_claims
Opaque JWT claims object returned after successful verification.
Definition jwt.h:25
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
#define RAND_bytes
Definition rand.h:17
TCP-like sequenced message fragmentation and reassembly.
bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out)
Parse a sequence chunk from raw bytes.
Definition seq.c:63
cwist_seq_assembler_t * cwist_seq_assembler_create(void)
Create an empty assembler.
Definition seq.c:196
void cwist_seq_assembler_destroy(cwist_seq_assembler_t *a)
Destroy an assembler and its internal buffer.
Definition seq.c:223
void cwist_seq_message_free(cwist_seq_message_t *msg)
Free all buffers allocated by cwist_seq_split().
Definition seq.c:178
bool cwist_seq_assembler_get_data(cwist_seq_assembler_t *a, const uint8_t **out_data, size_t *out_len)
Borrow the assembled message when complete.
Definition seq.c:390
bool cwist_seq_assembler_feed(cwist_seq_assembler_t *a, const cwist_seq_chunk_t *chunk)
Feed one parsed chunk into the assembler.
Definition seq.c:268
struct cwist_seq_assembler cwist_seq_assembler_t
Opaque stateful assembler that reorders chunks and detects duplicates.
Definition seq.h:69
bool cwist_seq_split(const uint8_t *data, size_t len, uint16_t chunk_payload_size, cwist_seq_message_t *out)
Split data into sequenced chunks.
Definition seq.c:125
One sequenced JWT chunk produced by cwist_jwt_split_chunks().
Definition jwt.h:80
size_t len
Length of data.
Definition jwt.h:82
uint8_t * data
Chunk bytes (header + payload).
Definition jwt.h:81
Parsed view of one sequenced chunk.
Definition seq.h:45
Split a message into sequenced chunks.
Definition seq.h:60