diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h index 6016c0811..13fd26a36 100644 --- a/include/libwebsockets/lws-jws.h +++ b/include/libwebsockets/lws-jws.h @@ -406,8 +406,8 @@ lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, * lws_jwt_signed_validate() - check a compact JWT against a key and alg * * \param ctx: the lws_context - * \param alg_list: the expected alg name, like "ES512" * \param jwk: the key for checking the signature + * \param alg_list: the expected alg name, like "ES512" * \param com: the compact JWT * \param len: the length of com * \param temp: a temp scratchpad @@ -426,7 +426,32 @@ lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, * transformations of the B64 JWS in the JWT. */ LWS_VISIBLE LWS_EXTERN int -lws_jwt_signed_validate(struct lws_context *ctx, const char *alg_list, - struct lws_jwk *jwk, const char *com, size_t len, +lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk, + const char *alg_list, const char *com, size_t len, char *temp, int tl, char *out, size_t *out_len); + +/** + * lws_jwt_sign_compact() - generate a compact JWT using a key and alg + * + * \param ctx: the lws_context + * \param jwk: the signing key + * \param alg: the signing alg name, like "ES512" + * \param out: the output buffer to hold the signed JWT in compact form + * \param out_len: on entry, the length of out; on exit, the used amount of out + * \param temp: a temp scratchpad + * \param tl: available length of temp scratchpad + * \param format: a printf style format specification + * \param ...: zero or more args for the format specification + * + * Creates a JWT in a single step, from the format string and args through to + * outputting a well-formed compact JWT representation in out. + * + * Returns 0 if all is well and *out_len is the amount of data in out, else + * nonzero if failed. Temp must be large enough to hold various intermediate + * representations. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, + const char *alg, char *out, size_t *out_len, char *temp, + int tl, const char *format, ...) LWS_FORMAT(8); ///@} diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c index 161e1ff67..27ca873af 100644 --- a/lib/jose/jws/jws.c +++ b/lib/jose/jws/jws.c @@ -951,14 +951,15 @@ lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len) } int -lws_jwt_signed_validate(struct lws_context *ctx, const char *alg_list, - struct lws_jwk *jwk, const char *com, size_t len, +lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk, + const char *alg_list, const char *com, size_t len, char *temp, int tl, char *out, size_t *out_len) { struct lws_tokenize ts; struct lws_jose jose; struct lws_jws jws; size_t n, r = 1; + int otl = tl; memset(&jws, 0, sizeof(jws)); lws_jose_init(&jose); @@ -974,13 +975,15 @@ lws_jwt_signed_validate(struct lws_context *ctx, const char *alg_list, goto bail; } + temp += otl - tl; + otl = tl; + /* * Parse the JOSE header */ if (lws_jws_parse_jose(&jose, jws.map.buf[LJWS_JOSE], - jws.map.len[LJWS_JOSE], - lws_concat_temp(temp, tl), &tl) < 0) { + jws.map.len[LJWS_JOSE], temp, &tl) < 0) { lwsl_err("%s: JOSE parse failed\n", __func__); goto bail; } @@ -1034,3 +1037,112 @@ bail: return r; } + +int +lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, + const char *alg, char *out, size_t *out_len, char *temp, + int tl, const char *format, ...) +{ + int n, r = 1, otl = tl; + struct lws_jose jose; + struct lws_jws jws; + va_list ap; + char *q; + + lws_jws_init(&jws, jwk, ctx); + lws_jose_init(&jose); + + if (lws_gencrypto_jws_alg_to_definition(alg, &jose.alg)) { + lwsl_err("%s: unknown alg %s\n", __func__, alg); + + goto bail; + } + + /* create JOSE header, also needed for output */ + + if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, temp, &tl, + strlen(alg) + 10, 0)) { + lwsl_err("%s: temp space too small\n", __func__); + return 1; + } + + jws.map.len[LJWS_JOSE] = lws_snprintf((char *)jws.map.buf[LJWS_JOSE], + tl, "{\"alg\":\"%s\"}", alg); + + temp += otl - tl; + otl = tl; + + va_start(ap, format); + n = vsnprintf(NULL, 0, format, ap); + va_end(ap); + if (n + 2 >= tl) + goto bail; + + q = lws_malloc(n + 2, __func__); + if (!q) + goto bail; + + va_start(ap, format); + vsnprintf(q, n + 2, format, ap); + va_end(ap); + + /* add the plaintext from stdin to the map and a b64 version */ + + jws.map.buf[LJWS_PYLD] = q; + jws.map.len[LJWS_PYLD] = n; + + if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD, temp, &tl, + jws.map.buf[LJWS_PYLD], + jws.map.len[LJWS_PYLD])) + goto bail1; + + temp += otl - tl; + otl = tl; + + /* add the b64 JOSE header to the b64 map */ + + if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, temp, &tl, + jws.map.buf[LJWS_JOSE], + jws.map.len[LJWS_JOSE])) + goto bail1; + + temp += otl - tl; + otl = tl; + + /* prepare the space for the b64 signature in the map */ + + if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, temp, &tl, + lws_base64_size(LWS_JWE_LIMIT_KEY_ELEMENT_BYTES), + 0)) + goto bail1; + + /* sign the plaintext */ + + n = lws_jws_sign_from_b64(&jose, &jws, + (char *)jws.map_b64.buf[LJWS_SIG], + jws.map_b64.len[LJWS_SIG]); + if (n < 0) + goto bail1; + + /* set the actual b64 signature size */ + jws.map_b64.len[LJWS_SIG] = n; + + /* create the compact JWS representation */ + if (lws_jws_write_compact(&jws, out, *out_len)) + goto bail1; + + *out_len = strlen(out); + + r = 0; + +bail1: + lws_free(q); + +bail: + jws.map.buf[LJWS_PYLD] = NULL; + jws.map.len[LJWS_PYLD] = 0; + lws_jws_destroy(&jws); + lws_jose_destroy(&jose); + + return r; +} diff --git a/minimal-examples/api-tests/api-test-jose/jws.c b/minimal-examples/api-tests/api-test-jose/jws.c index 55ffcc971..cb1cb1e8b 100644 --- a/minimal-examples/api-tests/api-test-jose/jws.c +++ b/minimal-examples/api-tests/api-test-jose/jws.c @@ -685,6 +685,43 @@ test_jws_ES512(struct lws_context *context) goto bail1; } + /* jwt test */ + + { + unsigned long long ull = lws_now_secs(); + char buf[8192]; + size_t cml = 2048, cml2 = 2048; + + if (lws_jwt_sign_compact(context, &jwk, "ES512", + (char *)buf, &cml2, + (char *)buf + 2048, 4096, + "{\"iss\":\"warmcat.com\",\"aud\":" + "\"https://libwebsockets.org/sai\"," + "\"iat\":%llu," + "\"nbf\":%llu," + "\"exp\":%llu," + "\"sub\":\"manage\"}", ull, + ull - 60, ull + (30 * 24 * 3600) + )) { + lwsl_err("%s: failed to create JWT\n", __func__); + goto bail1; + } + + lwsl_notice("%s: jwt test '%s'\n", __func__, buf); + + if (lws_jwt_signed_validate(context, &jwk, "ES512", + (const char *)buf, cml2, + (char *)buf + 2048, 2048, + (char *)buf + 4096, &cml)) { + lwsl_err("%s: failed to parse JWT\n", __func__); + + goto bail1; + } + + lwsl_notice("%s: jwt valid, payload '%s'\n", + __func__, buf + 4096); + } + /* end */ ret = 0;