| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236 |
- /*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010 - 2019 Andy Green <[email protected]>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
- #include "private-lib-core.h"
- #include "private-lib-jose-jws.h"
- /*
- * Currently only support flattened or compact (implicitly single signature)
- */
- static const char * const jws_json[] = {
- "protected", /* base64u */
- "header", /* JSON */
- "payload", /* base64u payload */
- "signature", /* base64u signature */
- //"signatures[].protected",
- //"signatures[].header",
- //"signatures[].signature"
- };
- enum lws_jws_json_tok {
- LJWSJT_PROTECTED,
- LJWSJT_HEADER,
- LJWSJT_PAYLOAD,
- LJWSJT_SIGNATURE,
- // LJWSJT_SIGNATURES_PROTECTED,
- // LJWSJT_SIGNATURES_HEADER,
- // LJWSJT_SIGNATURES_SIGNATURE,
- };
- /* parse a JWS complete or flattened JSON object */
- struct jws_cb_args {
- struct lws_jws *jws;
- char *temp;
- int *temp_len;
- };
- static signed char
- lws_jws_json_cb(struct lejp_ctx *ctx, char reason)
- {
- struct jws_cb_args *args = (struct jws_cb_args *)ctx->user;
- int n, m;
- if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
- return 0;
- switch (ctx->path_match - 1) {
- /* strings */
- case LJWSJT_PROTECTED: /* base64u: JOSE: must contain 'alg' */
- m = LJWS_JOSE;
- goto append_string;
- case LJWSJT_PAYLOAD: /* base64u */
- m = LJWS_PYLD;
- goto append_string;
- case LJWSJT_SIGNATURE: /* base64u */
- m = LJWS_SIG;
- goto append_string;
- case LJWSJT_HEADER: /* unprotected freeform JSON */
- break;
- default:
- return -1;
- }
- return 0;
- append_string:
- if (*args->temp_len < ctx->npos) {
- lwsl_err("%s: out of parsing space\n", __func__);
- return -1;
- }
- /*
- * We keep both b64u and decoded in temp mapped using map / map_b64,
- * the jws signature is actually over the b64 content not the plaintext,
- * and we can't do it until we see the protected alg.
- */
- if (!args->jws->map_b64.buf[m]) {
- args->jws->map_b64.buf[m] = args->temp;
- args->jws->map_b64.len[m] = 0;
- }
- memcpy(args->temp, ctx->buf, ctx->npos);
- args->temp += ctx->npos;
- *args->temp_len -= ctx->npos;
- args->jws->map_b64.len[m] += ctx->npos;
- if (reason == LEJPCB_VAL_STR_END) {
- args->jws->map.buf[m] = args->temp;
- n = lws_b64_decode_string_len(
- (const char *)args->jws->map_b64.buf[m],
- args->jws->map_b64.len[m],
- (char *)args->temp, *args->temp_len);
- if (n < 0) {
- lwsl_err("%s: b64 decode failed: in len %d, m %d\n", __func__, (int)args->jws->map_b64.len[m], m);
- return -1;
- }
- args->temp += n;
- *args->temp_len -= n;
- args->jws->map.len[m] = n;
- }
- return 0;
- }
- static int
- lws_jws_json_parse(struct lws_jws *jws, const uint8_t *buf, int len,
- char *temp, int *temp_len)
- {
- struct jws_cb_args args;
- struct lejp_ctx jctx;
- int m = 0;
- args.jws = jws;
- args.temp = temp;
- args.temp_len = temp_len;
- lejp_construct(&jctx, lws_jws_json_cb, &args, jws_json,
- LWS_ARRAY_SIZE(jws_json));
- m = lejp_parse(&jctx, (uint8_t *)buf, len);
- lejp_destruct(&jctx);
- if (m < 0) {
- lwsl_notice("%s: parse returned %d\n", __func__, m);
- return -1;
- }
- return 0;
- }
- void
- lws_jws_init(struct lws_jws *jws, struct lws_jwk *jwk,
- struct lws_context *context)
- {
- memset(jws, 0, sizeof(*jws));
- jws->context = context;
- jws->jwk = jwk;
- }
- static void
- lws_jws_map_bzero(struct lws_jws_map *map)
- {
- int n;
- /* no need to scrub first jose header element (it can be canned then) */
- for (n = 1; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++)
- if (map->buf[n])
- lws_explicit_bzero((void *)map->buf[n], map->len[n]);
- }
- void
- lws_jws_destroy(struct lws_jws *jws)
- {
- lws_jws_map_bzero(&jws->map);
- jws->jwk = NULL;
- }
- int
- lws_jws_dup_element(struct lws_jws_map *map, int idx, char *temp, int *temp_len,
- const void *in, size_t in_len, size_t actual_alloc)
- {
- if (!actual_alloc)
- actual_alloc = in_len;
- if ((size_t)*temp_len < actual_alloc)
- return -1;
- memcpy(temp, in, in_len);
- map->len[idx] = (uint32_t)in_len;
- map->buf[idx] = temp;
- *temp_len -= (int)actual_alloc;
- return 0;
- }
- int
- lws_jws_encode_b64_element(struct lws_jws_map *map, int idx,
- char *temp, int *temp_len, const void *in,
- size_t in_len)
- {
- int n;
- if (*temp_len < lws_base64_size((int)in_len))
- return -1;
- n = lws_jws_base64_enc(in, in_len, temp, *temp_len);
- if (n < 0)
- return -1;
- map->len[idx] = n;
- map->buf[idx] = temp;
- *temp_len -= n;
- return 0;
- }
- int
- lws_jws_randomize_element(struct lws_context *context, struct lws_jws_map *map,
- int idx, char *temp, int *temp_len, size_t random_len,
- size_t actual_alloc)
- {
- if (!actual_alloc)
- actual_alloc = random_len;
- if ((size_t)*temp_len < actual_alloc)
- return -1;
- map->len[idx] = (uint32_t)random_len;
- map->buf[idx] = temp;
- if (lws_get_random(context, temp, random_len) != random_len) {
- lwsl_err("Problem getting random\n");
- return -1;
- }
- *temp_len -= (int)actual_alloc;
- return 0;
- }
- int
- lws_jws_alloc_element(struct lws_jws_map *map, int idx, char *temp,
- int *temp_len, size_t len, size_t actual_alloc)
- {
- if (!actual_alloc)
- actual_alloc = len;
- if ((size_t)*temp_len < actual_alloc)
- return -1;
- map->len[idx] = (uint32_t)len;
- map->buf[idx] = temp;
- *temp_len -= (int)actual_alloc;
- return 0;
- }
- int
- lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max)
- {
- int n;
- n = lws_b64_encode_string_url(in, (int)in_len, out, (int)out_max - 1);
- if (n < 0) {
- lwsl_notice("%s: in len %d too large for %d out buf\n",
- __func__, (int)in_len, (int)out_max);
- return n; /* too large for output buffer */
- }
- /* trim the terminal = */
- while (n && out[n - 1] == '=')
- n--;
- out[n] = '\0';
- return n;
- }
- int
- lws_jws_b64_compact_map(const char *in, int len, struct lws_jws_map *map)
- {
- int me = 0;
- memset(map, 0, sizeof(*map));
- map->buf[me] = (char *)in;
- map->len[me] = 0;
- while (len--) {
- if (*in++ == '.') {
- if (++me == LWS_JWS_MAX_COMPACT_BLOCKS)
- return -1;
- map->buf[me] = (char *)in;
- map->len[me] = 0;
- continue;
- }
- map->len[me]++;
- }
- return me + 1;
- }
- /* b64 in, map contains decoded elements, if non-NULL,
- * map_b64 set to b64 elements
- */
- int
- lws_jws_compact_decode(const char *in, int len, struct lws_jws_map *map,
- struct lws_jws_map *map_b64, char *out,
- int *out_len)
- {
- int blocks, n, m = 0;
- if (!map_b64)
- map_b64 = map;
- memset(map_b64, 0, sizeof(*map_b64));
- memset(map, 0, sizeof(*map));
- blocks = lws_jws_b64_compact_map(in, len, map_b64);
- if (blocks > LWS_JWS_MAX_COMPACT_BLOCKS)
- return -1;
- while (m < blocks) {
- n = lws_b64_decode_string_len(map_b64->buf[m], map_b64->len[m],
- out, *out_len);
- if (n < 0) {
- lwsl_err("%s: b64 decode failed\n", __func__);
- return -1;
- }
- /* replace the map entry with the decoded content */
- if (n)
- map->buf[m] = out;
- else
- map->buf[m] = NULL;
- map->len[m++] = n;
- out += n;
- *out_len -= n;
- if (*out_len < 1)
- return -1;
- }
- return blocks;
- }
- static int
- lws_jws_compact_decode_map(struct lws_jws_map *map_b64, struct lws_jws_map *map,
- char *out, int *out_len)
- {
- int n, m = 0;
- for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
- n = lws_b64_decode_string_len(map_b64->buf[m], map_b64->len[m],
- out, *out_len);
- if (n < 0) {
- lwsl_err("%s: b64 decode failed\n", __func__);
- return -1;
- }
- /* replace the map entry with the decoded content */
- map->buf[m] = out;
- map->len[m++] = n;
- out += n;
- *out_len -= n;
- if (*out_len < 1)
- return -1;
- }
- return 0;
- }
- int
- lws_jws_encode_section(const char *in, size_t in_len, int first, char **p,
- char *end)
- {
- int n, len = lws_ptr_diff(end, (*p)) - 1;
- char *p_entry = *p;
- if (len < 3)
- return -1;
- if (!first)
- *(*p)++ = '.';
- n = lws_jws_base64_enc(in, in_len, *p, len - 1);
- if (n < 0)
- return -1;
- *p += n;
- return lws_ptr_diff((*p), p_entry);
- }
- int
- lws_jws_compact_encode(struct lws_jws_map *map_b64, /* b64-encoded */
- const struct lws_jws_map *map, /* non-b64 */
- char *buf, int *len)
- {
- int n, m;
- for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
- if (!map->buf[n]) {
- map_b64->buf[n] = NULL;
- map_b64->len[n] = 0;
- continue;
- }
- m = lws_jws_base64_enc(map->buf[n], map->len[n], buf, *len);
- if (m < 0)
- return -1;
- buf += m;
- *len -= m;
- if (*len < 1)
- return -1;
- }
- return 0;
- }
- /*
- * This takes both a base64 -encoded map and a plaintext map.
- *
- * JWS demands base-64 encoded elements for hash computation and at least for
- * the JOSE header and signature, decoded versions too.
- */
- int
- lws_jws_sig_confirm(struct lws_jws_map *map_b64, struct lws_jws_map *map,
- struct lws_jwk *jwk, struct lws_context *context)
- {
- enum enum_genrsa_mode padding = LGRSAM_PKCS1_1_5;
- char temp[256];
- int n, h_len, b = 3, temp_len = sizeof(temp);
- uint8_t digest[LWS_GENHASH_LARGEST];
- struct lws_genhash_ctx hash_ctx;
- struct lws_genec_ctx ecdsactx;
- struct lws_genrsa_ctx rsactx;
- struct lws_genhmac_ctx ctx;
- struct lws_jose jose;
- lws_jose_init(&jose);
- /* only valid if no signature or key */
- if (!map_b64->buf[LJWS_SIG] && !map->buf[LJWS_UHDR])
- b = 2;
- if (lws_jws_parse_jose(&jose, map->buf[LJWS_JOSE], map->len[LJWS_JOSE],
- temp, &temp_len) < 0 || !jose.alg) {
- lwsl_notice("%s: parse failed\n", __func__);
- return -1;
- }
- if (!strcmp(jose.alg->alg, "none")) {
- /* "none" compact serialization has 2 blocks: jose.payload */
- if (b != 2 || jwk)
- return -1;
- /* the lack of a key matches the lack of a signature */
- return 0;
- }
- /* all other have 3 blocks: jose.payload.sig */
- if (b != 3 || !jwk) {
- lwsl_notice("%s: %d blocks\n", __func__, b);
- return -1;
- }
- switch (jose.alg->algtype_signing) {
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
- padding = LGRSAM_PKCS1_OAEP_PSS;
- /* fallthru */
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
- /* RSASSA-PKCS1-v1_5 or OAEP using SHA-256/384/512 */
- if (jwk->kty != LWS_GENCRYPTO_KTY_RSA)
- return -1;
- /* 6(RSA): compute the hash of the payload into "digest" */
- if (lws_genhash_init(&hash_ctx, jose.alg->hash_type))
- return -1;
- /*
- * JWS Signing Input value:
- *
- * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
- * BASE64URL(JWS Payload)
- */
- if (lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
- map_b64->len[LJWS_JOSE]) ||
- lws_genhash_update(&hash_ctx, ".", 1) ||
- lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
- map_b64->len[LJWS_PYLD]) ||
- lws_genhash_destroy(&hash_ctx, digest)) {
- lws_genhash_destroy(&hash_ctx, NULL);
- return -1;
- }
- // h_len = lws_genhash_size(jose.alg->hash_type);
- if (lws_genrsa_create(&rsactx, jwk->e, context, padding,
- LWS_GENHASH_TYPE_UNKNOWN)) {
- lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
- __func__);
- return -1;
- }
- n = lws_genrsa_hash_sig_verify(&rsactx, digest,
- jose.alg->hash_type,
- (uint8_t *)map->buf[LJWS_SIG],
- map->len[LJWS_SIG]);
- lws_genrsa_destroy(&rsactx);
- if (n < 0) {
- lwsl_notice("%s: decrypt fail\n", __func__);
- return -1;
- }
- break;
- case LWS_JOSE_ENCTYPE_NONE: /* HSxxx */
- /* SHA256/384/512 HMAC */
- h_len = (int)lws_genhmac_size(jose.alg->hmac_type);
- /* 6) compute HMAC over payload */
- if (lws_genhmac_init(&ctx, jose.alg->hmac_type,
- jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
- jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len))
- return -1;
- /*
- * JWS Signing Input value:
- *
- * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
- * BASE64URL(JWS Payload)
- */
- if (lws_genhmac_update(&ctx, map_b64->buf[LJWS_JOSE],
- map_b64->len[LJWS_JOSE]) ||
- lws_genhmac_update(&ctx, ".", 1) ||
- lws_genhmac_update(&ctx, map_b64->buf[LJWS_PYLD],
- map_b64->len[LJWS_PYLD]) ||
- lws_genhmac_destroy(&ctx, digest)) {
- lws_genhmac_destroy(&ctx, NULL);
- return -1;
- }
- /* 7) Compare the computed and decoded hashes */
- if (lws_timingsafe_bcmp(digest, map->buf[2], h_len)) {
- lwsl_notice("digest mismatch\n");
- return -1;
- }
- break;
- case LWS_JOSE_ENCTYPE_ECDSA:
- /* ECDSA using SHA-256/384/512 */
- /* Confirm the key coming in with this makes sense */
- /* has to be an EC key :-) */
- if (jwk->kty != LWS_GENCRYPTO_KTY_EC)
- return -1;
- /* key must state its curve */
- if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
- return -1;
- /* key must match the selected alg curve */
- if (strcmp((const char *)jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
- jose.alg->curve_name))
- return -1;
- /*
- * JWS Signing Input value:
- *
- * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
- * BASE64URL(JWS Payload)
- *
- * Validating the JWS Signature is a bit different from the
- * previous examples. We need to split the 64 member octet
- * sequence of the JWS Signature (which is base64url decoded
- * from the value encoded in the JWS representation) into two
- * 32 octet sequences, the first representing R and the second
- * S. We then pass the public key (x, y), the signature (R, S),
- * and the JWS Signing Input (which is the initial substring of
- * the JWS Compact Serialization representation up until but not
- * including the second period character) to an ECDSA signature
- * verifier that has been configured to use the P-256 curve with
- * the SHA-256 hash function.
- */
- if (lws_genhash_init(&hash_ctx, jose.alg->hash_type) ||
- lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
- map_b64->len[LJWS_JOSE]) ||
- lws_genhash_update(&hash_ctx, ".", 1) ||
- lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
- map_b64->len[LJWS_PYLD]) ||
- lws_genhash_destroy(&hash_ctx, digest)) {
- lws_genhash_destroy(&hash_ctx, NULL);
- return -1;
- }
- h_len = (int)lws_genhash_size(jose.alg->hash_type);
- if (lws_genecdsa_create(&ecdsactx, context, NULL)) {
- lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
- __func__);
- return -1;
- }
- if (lws_genecdsa_set_key(&ecdsactx, jwk->e)) {
- lws_genec_destroy(&ecdsactx);
- lwsl_notice("%s: ec key import fail\n", __func__);
- return -1;
- }
- n = lws_genecdsa_hash_sig_verify_jws(&ecdsactx, digest,
- jose.alg->hash_type,
- jose.alg->keybits_fixed,
- (uint8_t *)map->buf[LJWS_SIG],
- map->len[LJWS_SIG]);
- lws_genec_destroy(&ecdsactx);
- if (n < 0) {
- lwsl_notice("%s: verify fail\n", __func__);
- return -1;
- }
- break;
- default:
- lwsl_err("%s: unknown alg from jose\n", __func__);
- return -1;
- }
- return 0;
- }
- /* it's already a b64 map, we will make a temp plain version */
- int
- lws_jws_sig_confirm_compact_b64_map(struct lws_jws_map *map_b64,
- struct lws_jwk *jwk,
- struct lws_context *context,
- char *temp, int *temp_len)
- {
- struct lws_jws_map map;
- int n;
- n = lws_jws_compact_decode_map(map_b64, &map, temp, temp_len);
- if (n > 3 || n < 0)
- return -1;
- return lws_jws_sig_confirm(map_b64, &map, jwk, context);
- }
- /*
- * it's already a compact / concatenated b64 string, we will make a temp
- * plain version
- */
- int
- lws_jws_sig_confirm_compact_b64(const char *in, size_t len,
- struct lws_jws_map *map, struct lws_jwk *jwk,
- struct lws_context *context,
- char *temp, int *temp_len)
- {
- struct lws_jws_map map_b64;
- int n;
- if (lws_jws_b64_compact_map(in, (int)len, &map_b64) < 0)
- return -1;
- n = lws_jws_compact_decode(in, (int)len, map, &map_b64, temp, temp_len);
- if (n > 3 || n < 0)
- return -1;
- return lws_jws_sig_confirm(&map_b64, map, jwk, context);
- }
- /* it's already plain, we will make a temp b64 version */
- int
- lws_jws_sig_confirm_compact(struct lws_jws_map *map, struct lws_jwk *jwk,
- struct lws_context *context, char *temp,
- int *temp_len)
- {
- struct lws_jws_map map_b64;
- if (lws_jws_compact_encode(&map_b64, map, temp, temp_len) < 0)
- return -1;
- return lws_jws_sig_confirm(&map_b64, map, jwk, context);
- }
- int
- lws_jws_sig_confirm_json(const char *in, size_t len,
- struct lws_jws *jws, struct lws_jwk *jwk,
- struct lws_context *context,
- char *temp, int *temp_len)
- {
- if (lws_jws_json_parse(jws, (const uint8_t *)in,
- (int)len, temp, temp_len)) {
- lwsl_err("%s: lws_jws_json_parse failed\n", __func__);
- return -1;
- }
- return lws_jws_sig_confirm(&jws->map_b64, &jws->map, jwk, context);
- }
- int
- lws_jws_sign_from_b64(struct lws_jose *jose, struct lws_jws *jws,
- char *b64_sig, size_t sig_len)
- {
- enum enum_genrsa_mode pad = LGRSAM_PKCS1_1_5;
- uint8_t digest[LWS_GENHASH_LARGEST];
- struct lws_genhash_ctx hash_ctx;
- struct lws_genec_ctx ecdsactx;
- struct lws_genrsa_ctx rsactx;
- uint8_t *buf;
- int n, m;
- if (jose->alg->hash_type == LWS_GENHASH_TYPE_UNKNOWN &&
- jose->alg->hmac_type == LWS_GENHMAC_TYPE_UNKNOWN &&
- !strcmp(jose->alg->alg, "none"))
- return 0;
- if (lws_genhash_init(&hash_ctx, jose->alg->hash_type) ||
- lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_JOSE],
- jws->map_b64.len[LJWS_JOSE]) ||
- lws_genhash_update(&hash_ctx, ".", 1) ||
- lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_PYLD],
- jws->map_b64.len[LJWS_PYLD]) ||
- lws_genhash_destroy(&hash_ctx, digest)) {
- lws_genhash_destroy(&hash_ctx, NULL);
- return -1;
- }
- switch (jose->alg->algtype_signing) {
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
- pad = LGRSAM_PKCS1_OAEP_PSS;
- /* fallthru */
- case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
- if (jws->jwk->kty != LWS_GENCRYPTO_KTY_RSA)
- return -1;
- if (lws_genrsa_create(&rsactx, jws->jwk->e, jws->context,
- pad, LWS_GENHASH_TYPE_UNKNOWN)) {
- lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
- __func__);
- return -1;
- }
- n = jws->jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len;
- buf = lws_malloc(lws_base64_size(n), "jws sign");
- if (!buf)
- return -1;
- n = lws_genrsa_hash_sign(&rsactx, digest, jose->alg->hash_type,
- buf, n);
- lws_genrsa_destroy(&rsactx);
- if (n < 0) {
- lwsl_err("%s: lws_genrsa_hash_sign failed\n", __func__);
- lws_free(buf);
- return -1;
- }
- n = lws_jws_base64_enc((char *)buf, n, b64_sig, sig_len);
- lws_free(buf);
- if (n < 0) {
- lwsl_err("%s: lws_jws_base64_enc failed\n", __func__);
- }
- return n;
- case LWS_JOSE_ENCTYPE_NONE:
- return lws_jws_base64_enc((char *)digest,
- lws_genhash_size(jose->alg->hash_type),
- b64_sig, sig_len);
- case LWS_JOSE_ENCTYPE_ECDSA:
- /* ECDSA using SHA-256/384/512 */
- /* the key coming in with this makes sense, right? */
- /* has to be an EC key :-) */
- if (jws->jwk->kty != LWS_GENCRYPTO_KTY_EC)
- return -1;
- /* key must state its curve */
- if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
- return -1;
- /* must have all his pieces for a private key */
- if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf ||
- !jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf ||
- !jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
- return -1;
- /* key must match the selected alg curve */
- if (strcmp((const char *)
- jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
- jose->alg->curve_name))
- return -1;
- if (lws_genecdsa_create(&ecdsactx, jws->context, NULL)) {
- lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
- __func__);
- return -1;
- }
- if (lws_genecdsa_set_key(&ecdsactx, jws->jwk->e)) {
- lws_genec_destroy(&ecdsactx);
- lwsl_notice("%s: ec key import fail\n", __func__);
- return -1;
- }
- m = lws_gencrypto_bits_to_bytes(jose->alg->keybits_fixed) * 2;
- buf = lws_malloc(m, "jws sign");
- if (!buf)
- return -1;
- n = lws_genecdsa_hash_sign_jws(&ecdsactx, digest,
- jose->alg->hash_type,
- jose->alg->keybits_fixed,
- (uint8_t *)buf, m);
- lws_genec_destroy(&ecdsactx);
- if (n < 0) {
- lws_free(buf);
- lwsl_notice("%s: lws_genecdsa_hash_sign_jws fail\n",
- __func__);
- return -1;
- }
- n = lws_jws_base64_enc((char *)buf, m, b64_sig, sig_len);
- lws_free(buf);
- return n;
- default:
- break;
- }
- /* unknown key type */
- return -1;
- }
- /*
- * Flattened JWS JSON:
- *
- * {
- * "payload": "<payload contents>",
- * "protected": "<integrity-protected header contents>",
- * "header": <non-integrity-protected header contents>,
- * "signature": "<signature contents>"
- * }
- */
- int
- lws_jws_write_flattened_json(struct lws_jws *jws, char *flattened, size_t len)
- {
- size_t n = 0;
- if (len < 1)
- return 1;
- n += lws_snprintf(flattened + n, len - n , "{\"payload\": \"");
- lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_PYLD],
- jws->map_b64.len[LJWS_PYLD], len - n);
- n += strlen(flattened + n);
- n += lws_snprintf(flattened + n, len - n , "\",\n \"protected\": \"");
- lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_JOSE],
- jws->map_b64.len[LJWS_JOSE], len - n);
- n += strlen(flattened + n);
- if (jws->map_b64.buf[LJWS_UHDR]) {
- n += lws_snprintf(flattened + n, len - n , "\",\n \"header\": ");
- lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_UHDR],
- jws->map_b64.len[LJWS_UHDR], len - n);
- n += strlen(flattened + n);
- }
- n += lws_snprintf(flattened + n, len - n , "\",\n \"signature\": \"");
- lws_strnncpy(flattened + n, jws->map_b64.buf[LJWS_SIG],
- jws->map_b64.len[LJWS_SIG], len - n);
- n += strlen(flattened + n);
- n += lws_snprintf(flattened + n, len - n , "\"}\n");
- return (n >= len - 1);
- }
- int
- lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len)
- {
- size_t n = 0;
- if (len < 1)
- return 1;
- lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_JOSE],
- jws->map_b64.len[LJWS_JOSE], len - n);
- n += strlen(compact + n);
- if (n >= len - 1)
- return 1;
- compact[n++] = '.';
- lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_PYLD],
- jws->map_b64.len[LJWS_PYLD], len - n);
- n += strlen(compact + n);
- if (n >= len - 1)
- return 1;
- compact[n++] = '.';
- lws_strnncpy(compact + n, jws->map_b64.buf[LJWS_SIG],
- jws->map_b64.len[LJWS_SIG], len - n);
- n += strlen(compact + n);
- return n >= len - 1;
- }
- int
- 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;
- int otl = tl, r = 1;
- struct lws_jws jws;
- size_t n;
- memset(&jws, 0, sizeof(jws));
- lws_jose_init(&jose);
- /*
- * Decode the b64.b64[.b64] compact serialization
- * blocks
- */
- n = lws_jws_compact_decode(com, (int)len, &jws.map, &jws.map_b64,
- temp, &tl);
- if (n != 3) {
- lwsl_err("%s: concat_map failed: %d\n", __func__, (int)n);
- 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], temp, &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;
- }
- 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;
- }
- int
- lws_jwt_token_sanity(const char *in, size_t in_len,
- const char *iss, const char *aud,
- const char *csrf_in,
- char *sub, size_t sub_len, unsigned long *expiry_unix_time)
- {
- unsigned long now = lws_now_secs(), exp;
- const char *cp;
- size_t len;
- /*
- * It has our issuer?
- */
- if (lws_json_simple_strcmp(in, in_len, "\"iss\":", iss)) {
- lwsl_notice("%s: iss mismatch\n", __func__);
- return 1;
- }
- /*
- * ... it is indended for us to consume? (this is set
- * to the public base url for this sai instance)
- */
- if (lws_json_simple_strcmp(in, in_len, "\"aud\":", aud)) {
- lwsl_notice("%s: aud mismatch\n", __func__);
- return 1;
- }
- /*
- * ...it's not too early for it?
- */
- cp = lws_json_simple_find(in, in_len, "\"nbf\":", &len);
- if (!cp || (unsigned long)atol(cp) > now) {
- lwsl_notice("%s: nbf fail\n", __func__);
- return 1;
- }
- /*
- * ... and not too late for it?
- */
- cp = lws_json_simple_find(in, in_len, "\"exp\":", &len);
- exp = (unsigned long)atol(cp);
- if (!cp || (unsigned long)atol(cp) < now) {
- lwsl_notice("%s: exp fail %lu vs %lu\n", __func__,
- cp ? (unsigned long)atol(cp) : 0, now);
- return 1;
- }
- /*
- * Caller cares about subject? Then we must have it, and it can't be
- * empty.
- */
- if (sub) {
- cp = lws_json_simple_find(in, in_len, "\"sub\":", &len);
- if (!cp || !len) {
- lwsl_notice("%s: missing subject\n", __func__);
- return 1;
- }
- lws_strnncpy(sub, cp, len, sub_len);
- }
- /*
- * If caller has been told a Cross Site Request Forgery (CSRF) nonce,
- * require this JWT to express the same CSRF... this makes generated
- * links for dangerous privileged auth'd actions expire with the JWT
- * that was accessing the site when the links were generated. And it
- * leaves an attacker not knowing what links to synthesize unless he
- * can read the token or pages generated with it.
- *
- * Using this is very good for security, but it implies you must refresh
- * generated pages still when the auth token is expiring (and the user
- * must log in again).
- */
- if (csrf_in &&
- lws_json_simple_strcmp(in, in_len, "\"csrf\":", csrf_in)) {
- lwsl_notice("%s: csrf mismatch\n", __func__);
- return 1;
- }
- if (expiry_unix_time)
- *expiry_unix_time = exp;
- return 0;
- }
|