diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h index 1e1fb8b2f..6016c0811 100644 --- a/include/libwebsockets/lws-jws.h +++ b/include/libwebsockets/lws-jws.h @@ -398,8 +398,35 @@ lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max); * Returns either -1 if problems, or the number of bytes written to \p out. * If the section is not the first one, '.' is prepended. */ - LWS_VISIBLE LWS_EXTERN int lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, char *end); + +/** + * 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 com: the compact JWT + * \param len: the length of com + * \param temp: a temp scratchpad + * \param tl: available length of temp scratchpad + * \param out: the output buffer to hold the validated plaintext + * \param out_len: on entry, max length of out; on exit, used length of out + * + * Returns nonzero if the JWT cannot be validated or the plaintext can't fit the + * provided output buffer, or 0 if it is validated as being signed by the + * provided jwk. + * + * If validated, the plaintext in the JWT is copied into out and out_len set to + * the used length. + * + * temp can be discarded or reused after the call returned, it's used to hold + * 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, + char *temp, int tl, char *out, size_t *out_len); ///@} diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c index 3c42259f4..161e1ff67 100644 --- a/lib/jose/jws/jws.c +++ b/lib/jose/jws/jws.c @@ -949,3 +949,88 @@ lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len) return n >= len - 1; } + +int +lws_jwt_signed_validate(struct lws_context *ctx, const char *alg_list, + struct lws_jwk *jwk, 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; + + memset(&jws, 0, sizeof(jws)); + lws_jose_init(&jose); + + /* + * Decode the b64.b64[.b64] compact serialization + * blocks + */ + + n = lws_jws_compact_decode(com, len, &jws.map, &jws.map_b64, temp, &tl); + if (n != 3) { + lwsl_err("%s: concat_map failed: %d\n", __func__, (int)n); + goto bail; + } + + /* + * 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) { + lwsl_err("%s: JOSE parse failed\n", __func__); + goto bail; + } + + /* + * Insist to see an alg in there that we list as acceptable + */ + + lws_tokenize_init(&ts, alg_list, LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_RFC7230_DELIMS); + n = strlen(jose.alg->alg); + + do { + ts.e = lws_tokenize(&ts); + if (ts.e == LWS_TOKZE_TOKEN && ts.token_len == n && + !strncmp(jose.alg->alg, ts.token, ts.token_len)) + break; + } while (ts.e != LWS_TOKZE_ENDED); + + if (ts.e != LWS_TOKZE_TOKEN) { + lwsl_err("%s: JOSE using alg %s (accepted: %s)\n", __func__, + jose.alg->alg, alg_list); + goto bail; + } + + /* we liked the alg... now how about the crypto? */ + + if (lws_jws_sig_confirm(&jws.map_b64, &jws.map, jwk, ctx) < 0) { + lwsl_notice("%s: confirm JWT sig failed\n", + __func__); + goto bail; + } + + /* yeah, it's validated... see about copying it out */ + + if (*out_len < jws.map.len[LJWS_PYLD] + 1) { + /* we don't have enough room */ + r = 2; + goto bail; + } + + memcpy(out, jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]); + *out_len = jws.map.len[LJWS_PYLD]; + out[jws.map.len[LJWS_PYLD]] = '\0'; + + r = 0; + +bail: + lws_jws_destroy(&jws); + lws_jose_destroy(&jose); + + return r; +}