24 functions · JWT/JWS + JWKS · MIT

xjwt Function Reference

JSON Web Token (RFC 7519) / JWS (RFC 7515) signing and verification: build.bat xjwt compiles it in. HS256/384/512 (HMAC), RS256/384/512 (RSA PKCS#1; PKCS#8/PKCS#1 private keys), ES256 (ECDSA P-256, DER signatures per RFC 7518 §3.4, two-way OpenSSL interop), and JWKS (RFC 7517) key selection. Claims travel as xrt xvalue* — zero bridging in the script layer.

Signing (9 functions)

xjwtSign config-style

Full-control signing: injects iss/aud/sub/jti/exp/iat and the header kid (iat always). The header is built via JSON serialization (kid auto-escaped, no fixed-length limit); signature output capacity is checked (covers RSA-8192). Note: modifies the claims object in place.

char* xjwtSign(const xjwtconfig* pConfig, xvalue* claims);
Example · issue an access token
xjwtconfig cfg;
xjwtConfigInit(&cfg);
cfg.Alg = XJWT_ALG_RS256;
cfg.KeyPem = sPrivateKeyPem;   /* loaded at startup */
cfg.KeyId = "web-2026-09";
cfg.Issuer = "https://myapp.example.com";
cfg.Audience = "myapp-web";
cfg.ExpireSeconds = 900;

xvalue* claims = xrtValueObject();
xrtValueObjectSetNew(claims, xrtStrView("scope"),
    xrtValueString(xrtStrView("profile orders")));
char* sToken = xjwtSign(&cfg, claims);  /* free with xrtFree */
xrtValueRelease(claims);

xjwtHs256 / Hs384 / Hs512 / Rs256 / Rs384 / Rs512 / Es256 one-shot

Seven one-shot signers (claims + key + lifetime in seconds). This family injects no iss/aud — when the verifier's xjwtcheck checks Issuer/Audience, use xjwtSign instead. HS takes a key as a C string (embedded '\0' truncates; no binary keys); RS takes a PKCS#8/PKCS#1 private-key PEM; ES256 takes a SEC1/PKCS#8 private-key PEM.

char* xjwtHs256(xvalue* claims, const char* secret, int expireSeconds);

xjwtConfigInit init

Zeroes the config and defaults to HS256. KeyId ≥ 256 characters is rejected at signing (matching the verifier-side cap).

Verification (4 functions)

xjwtVerify PEM path

One-step verify: header alg decode → claims checks (non-integer exp/nbf rejected; iss/aud constant-time; aud accepts strings and arrays) → signature. HS takes a key string, RS/ES a public-key PEM; key type is cross-checked against the algorithm family (anti-confusion). Returns claims on success (release with xrtValueRelease), NULL with an error code on failure.

xvalue* xjwtVerify(const char* token, const char* keyPem, const xjwtcheck* check);
Example · auth middleware
xjwtcheck check;
xjwtCheckInit(&check);
check.Issuer = "https://myapp.example.com";
check.Audience = "myapp-web";
check.ClockLeeway = 30;

xvalue* claims = xjwtVerify(sBearerToken, sPublicKeyPem, &check);
if ( claims == NULL ) {
    if ( xjwtLastError() == XJWT_ERROR_EXPIRED )
        return 401;   /* expired */
    return 401;           /* tampered / confused / bad signature */
}
char aUser[64];
xjwtClaimString(claims, "sub", aUser, sizeof(aUser));
xrtValueRelease(claims);

xjwtKeyParse / xjwtKeyFree / xjwtVerifyKey key cache

High-frequency verification: parse the public key once (RSA SPKI/PKCS#1 or EC P-256 SPKI; private keys and certificates rejected); the cache object is read-only and shareable across threads. VerifyKey rejects the HS family and cross-checks algorithm family against key type. Heap-pointer pattern — the natural fit for the script layer.

xjwtkey* xjwtKeyParse(const char* publicPem); void xjwtKeyFree(xjwtkey* key); xvalue* xjwtVerifyKey(const char* token, const xjwtkey* key, const xjwtcheck* check);

JWKS (3 functions)

xjwtJwksParse / xjwtJwksFree key set

Parse a JWKS (RFC 7517) JSON document: RSA n/e and EC x/y (P-256 only; other curves skipped; coordinates must be exactly 32 bytes). At most 16 keys — more rejects the whole set; malformed entries (e.g. n without e) are freed immediately, nothing lingers.

xjwtjwks* xjwtJwksParse(const char* json); void xjwtJwksFree(xjwtjwks* jwks);

xjwtVerifyJwks keyed verify

Selects a key by strict kid match against the token header (no kid → first key; kid ≥ 256 rejected everywhere). Explicitly rejects the HS family; RS* ↔ RSA and ES256 ↔ EC type cross-checks. Rotation retry: on xjwtLastError() == XJWT_ERROR_KEY_NOT_FOUND, refetch the JWKS and verify once more.

xvalue* xjwtVerifyJwks(const char* token, const xjwtjwks* jwks, const xjwtcheck* check);

Claims & errors (3 functions)

xjwtClaimsValid manual flow

Standalone claims check (exp/nbf/iss/aud): must be a JSON object; exp/nbf present-but-non-integer is always rejected; aud accepts strings and arrays (any match wins).

xjwtClaimString extraction

Safe string-claim extraction: truncates to cap-1 and NUL-terminates; returns false when the claim is missing or not a string.

bool xjwtClaimString(const xvalue* claims, const char* key, char* pOut, size_t iCap);

xjwtLastError error read

Returns the xjwt-domain error code of the current execution context (XJWT_ERROR_*), 0 when the current error belongs to another domain. Read immediately after a failure.

Decoding & utilities (5 functions)

xjwtDecode / xjwtDecodeHeader decode

Verification-free decoding (trusted sources / debugging): the former returns claims and can report the header alg; the latter returns the header object and can report alg + kid (kid is a heap copy, free with xrtFree). Claims/header must be JSON objects; with pKid, a kid ≥ 256 rejects the whole call.

xjwtCheckInit / xjwtAlgName / xjwtAlgParse utilities

Zeroes the check struct (NowOverride injects a test clock, ClockLeeway is the tolerance in seconds); converts between the algorithm enum and its name.

void xjwtCheckInit(xjwtcheck* pCheck); const char* xjwtAlgName(int alg); int xjwtAlgParse(const char* name);
Security baseline: non-integer exp/nbf rejected, alg=none and algorithm confusion rejected on all paths (JWKS included), constant-time iss/aud/HS, strict kid matching with three-state length handling. 117 tests (OpenSSL two-way interop, RSA-8192, 2000-case fuzz, measured steady-state zero leak). Full source at lib/xjwt; data-couples with xoauth2 for OIDC login.