| /* |
| * libwebsockets - small server side websockets and web server implementation |
| * |
| * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com> |
| * |
| * 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. |
| * |
| * JOSE is actually specified as part of JWS RFC7515. JWE references RFC7515 |
| * to specify its JOSE JSON object. So it lives in ./lib/jose/jws/jose.c. |
| */ |
| |
| #include "private-lib-core.h" |
| #include "jose/private-lib-jose.h" |
| |
| #include <stdint.h> |
| |
| static const char * const jws_jose[] = { |
| "alg", /* REQUIRED */ |
| "jku", |
| "jwk", |
| "kid", |
| "x5u", |
| "x5c", |
| "x5t", |
| "x5t#S256", |
| "typ", |
| "cty", |
| "crit", |
| |
| /* valid for JWE only below here */ |
| |
| "recipients[].header", |
| "recipients[].header.alg", |
| "recipients[].header.kid", |
| "recipients[].encrypted_key", |
| |
| "enc", |
| "zip", /* ("DEF" = deflate) */ |
| |
| "epk", /* valid for JWE ECDH only */ |
| "apu", /* valid for JWE ECDH only */ |
| "apv", /* valid for JWE ECDH only */ |
| "iv", /* valid for JWE AES only */ |
| "tag", /* valid for JWE AES only */ |
| "p2s", /* valid for JWE PBES2 only */ |
| "p2c" /* valid for JWE PBES2 only */ |
| }; |
| |
| struct jose_cb_args { |
| struct lws_jose *jose; |
| |
| struct lejp_ctx jwk_jctx; /* fake lejp context used to parse epk */ |
| struct lws_jwk_parse_state jps; /* fake jwk parse state */ |
| |
| char *temp; |
| int *temp_len; |
| |
| unsigned int is_jwe; |
| unsigned int recipients_array; |
| |
| int recip; |
| }; |
| |
| /* |
| * JWE A.4.7 Complete JWE JSON Serialization example |
| * |
| * LEJPCB_CONSTRUCTED |
| * LEJPCB_START |
| * LEJPCB_OBJECT_START |
| * |
| * protected LEJPCB_PAIR_NAME |
| * protected LEJPCB_VAL_STR_START |
| * protected LEJPCB_VAL_STR_END |
| * |
| * unprotected LEJPCB_PAIR_NAME |
| * unprotected LEJPCB_OBJECT_START |
| * unprotected.jku LEJPCB_PAIR_NAME |
| * unprotected.jku LEJPCB_VAL_STR_START |
| * unprotected.jku LEJPCB_VAL_STR_END |
| * unprotected.jku LEJPCB_OBJECT_END |
| * |
| * recipients LEJPCB_PAIR_NAME |
| * recipients[] LEJPCB_ARRAY_START |
| * |
| * recipients[] LEJPCB_OBJECT_START |
| * recipients[].header LEJPCB_PAIR_NAME |
| * recipients[].header LEJPCB_OBJECT_START |
| * recipients[].header.alg LEJPCB_PAIR_NAME |
| * recipients[].header.alg LEJPCB_VAL_STR_START |
| * recipients[].header.alg LEJPCB_VAL_STR_END |
| * recipients[].header.kid LEJPCB_PAIR_NAME |
| * recipients[].header.kid LEJPCB_VAL_STR_START |
| * recipients[].header.kid LEJPCB_VAL_STR_END |
| * recipients[] LEJPCB_OBJECT_END |
| * recipients[].encrypted_key LEJPCB_PAIR_NAME |
| * recipients[].encrypted_key LEJPCB_VAL_STR_START |
| * recipients[].encrypted_key LEJPCB_VAL_STR_CHUNK |
| * recipients[].encrypted_key LEJPCB_VAL_STR_END |
| * recipients[] LEJPCB_OBJECT_END (ctx->sp = 1) |
| * |
| * recipients[] LEJPCB_OBJECT_START |
| * recipients[].header LEJPCB_PAIR_NAME |
| * recipients[].header LEJPCB_OBJECT_START |
| * recipients[].header.alg LEJPCB_PAIR_NAME |
| * recipients[].header.alg LEJPCB_VAL_STR_START |
| * recipients[].header.alg LEJPCB_VAL_STR_END |
| * recipients[].header.kid LEJPCB_PAIR_NAME |
| * recipients[].header.kid LEJPCB_VAL_STR_START |
| * recipients[].header.kid LEJPCB_VAL_STR_END |
| * recipients[] LEJPCB_OBJECT_END |
| * recipients[].encrypted_key LEJPCB_PAIR_NAME |
| * recipients[].encrypted_key LEJPCB_VAL_STR_START |
| * recipients[].encrypted_key LEJPCB_VAL_STR_END |
| * recipients[] LEJPCB_OBJECT_END (ctx->sp = 1) |
| * |
| * recipients[] LEJPCB_ARRAY_END |
| * |
| * iv LEJPCB_PAIR_NAME |
| * iv LEJPCB_VAL_STR_START |
| * iv LEJPCB_VAL_STR_END |
| * ciphertext LEJPCB_PAIR_NAME |
| * ciphertext LEJPCB_VAL_STR_START |
| * ciphertext LEJPCB_VAL_STR_END |
| * tag LEJPCB_PAIR_NAME |
| * tag LEJPCB_VAL_STR_START |
| * tag LEJPCB_VAL_STR_END |
| * |
| * tag LEJPCB_OBJECT_END |
| * tag LEJPCB_COMPLETE |
| * tag LEJPCB_DESTRUCTED |
| * |
| */ |
| |
| /* |
| * RFC7516 7.2.2 |
| * |
| * Note that when using the flattened syntax, just as when using the |
| * general syntax, any unprotected Header Parameter values can reside in |
| * either the "unprotected" member or the "header" member, or in both. |
| */ |
| |
| static signed char |
| lws_jws_jose_cb(struct lejp_ctx *ctx, char reason) |
| { |
| struct jose_cb_args *args = (struct jose_cb_args *)ctx->user; |
| int n; //, dest; |
| |
| /* |
| * In JOSE JSON, the element "epk" contains a fully-formed JWK. |
| * |
| * For JOSE paths beginning "epk.", we pass them through to a JWK |
| * LEJP subcontext to parse using the JWK parser directly. |
| */ |
| |
| if (args->is_jwe && !strncmp(ctx->path, "epk.", 4)) { |
| memcpy(args->jwk_jctx.path, ctx->path + 4, |
| sizeof(ctx->path) - 4); |
| memcpy(args->jwk_jctx.buf, ctx->buf, ctx->npos); |
| args->jwk_jctx.npos = ctx->npos; |
| |
| if (!ctx->path_match) |
| args->jwk_jctx.path_match = 0; |
| lejp_check_path_match(&args->jwk_jctx); |
| |
| if (args->jwk_jctx.path_match) |
| args->jwk_jctx.pst[args->jwk_jctx.pst_sp]. |
| callback(&args->jwk_jctx, reason); |
| } |
| |
| // lwsl_notice("%s: %s %d (%d)\n", __func__, ctx->path, reason, ctx->sp); |
| |
| /* at the end of each recipients[] entry, bump recipients count */ |
| |
| if (args->is_jwe && reason == LEJPCB_OBJECT_END && ctx->sp == 1 && |
| !strcmp(ctx->path, "recipients[]")) |
| args->jose->recipients++; |
| |
| if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) |
| return 0; |
| |
| //dest = ctx->path_match - 1; |
| |
| switch (ctx->path_match - 1) { |
| |
| /* strings */ |
| |
| case LJJHI_ALG: /* REQUIRED */ |
| |
| /* |
| * look up whether we support this alg and point the caller at |
| * its definition if so |
| */ |
| |
| if (!args->is_jwe && |
| lws_gencrypto_jws_alg_to_definition(ctx->buf, |
| &args->jose->alg)) { |
| lwsl_notice("%s: unknown alg '%s'\n", __func__, |
| ctx->buf); |
| |
| return -1; |
| } |
| |
| if (args->is_jwe && |
| lws_gencrypto_jwe_alg_to_definition(ctx->buf, |
| &args->jose->alg)) { |
| lwsl_notice("%s: unknown JWE alg '%s'\n", __func__, |
| ctx->buf); |
| |
| return -1; |
| } |
| |
| return 0; |
| |
| case LJJHI_TYP: /* Optional: string: media type */ |
| if (strcmp(ctx->buf, "JWT")) |
| return -1; |
| break; |
| |
| case LJJHI_JKU: /* Optional: string */ |
| case LJJHI_KID: /* Optional: string */ |
| case LJJHI_X5U: /* Optional: string: url of public key cert / chain */ |
| case LJJHI_CTY: /* Optional: string: content media type */ |
| |
| /* base64 */ |
| |
| case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */ |
| |
| /* base64-url */ |
| |
| case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */ |
| case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of actual cert */ |
| |
| /* array of strings */ |
| |
| case LJJHI_CRIT: /* Optional for send, REQUIRED: array of strings: |
| * mustn't contain standardized strings or null set */ |
| break; |
| |
| /* jwk child */ |
| |
| case LJJHI_JWK: /* Optional: jwk JSON object: public key: */ |
| |
| /* past here, JWE only */ |
| |
| case LJJHI_RECIPS_HDR: |
| if (!args->is_jwe) { |
| lwsl_info("%s: recipients in jws\n", __func__); |
| return -1; |
| } |
| args->recipients_array = 1; |
| break; |
| |
| case LJJHI_RECIPS_HDR_ALG: |
| case LJJHI_RECIPS_HDR_KID: |
| break; |
| |
| case LJJHI_RECIPS_EKEY: |
| if (!args->is_jwe) { |
| lwsl_info("%s: recipients in jws\n", __func__); |
| return -1; |
| } |
| args->recipients_array = 1; |
| //dest = ; |
| goto append_string; |
| |
| case LJJHI_ENC: /* JWE only: Mandatory: string */ |
| if (!args->is_jwe) { |
| lwsl_info("%s: enc in jws\n", __func__); |
| return -1; |
| } |
| if (lws_gencrypto_jwe_enc_to_definition(ctx->buf, |
| &args->jose->enc_alg)) { |
| lwsl_notice("%s: unknown enc '%s'\n", __func__, |
| ctx->buf); |
| |
| return -1; |
| } |
| break; |
| |
| case LJJHI_ZIP: /* JWE only: Optional: string ("DEF" = deflate) */ |
| if (!args->is_jwe) |
| return -1; |
| goto append_string; |
| |
| case LJJHI_EPK: /* Additional arg for JWE ECDH */ |
| if (!args->is_jwe) |
| return -1; |
| /* Ephemeral key... this JSON subsection is actually a JWK */ |
| lwsl_err("LJJHI_EPK\n"); |
| break; |
| |
| case LJJHI_APU: /* Additional arg for JWE ECDH */ |
| if (!args->is_jwe) |
| return -1; |
| /* Agreement Party U */ |
| goto append_string; |
| |
| case LJJHI_APV: /* Additional arg for JWE ECDH */ |
| if (!args->is_jwe) |
| return -1; |
| /* Agreement Party V */ |
| goto append_string; |
| |
| case LJJHI_IV: /* Additional arg for JWE AES */ |
| if (!args->is_jwe) |
| return -1; |
| goto append_string; |
| |
| case LJJHI_TAG: /* Additional arg for JWE AES */ |
| if (!args->is_jwe) |
| return -1; |
| goto append_string; |
| |
| case LJJHI_P2S: /* Additional arg for JWE PBES2 */ |
| if (!args->is_jwe) |
| return -1; |
| goto append_string; |
| case LJJHI_P2C: /* Additional arg for JWE PBES2 */ |
| if (!args->is_jwe) |
| return -1; |
| goto append_string; |
| |
| /* ignore what we don't understand */ |
| |
| default: |
| return 0; |
| } |
| |
| return 0; |
| |
| append_string: |
| |
| if (*args->temp_len < ctx->npos) { |
| lwsl_err("%s: out of parsing space\n", __func__); |
| return -1; |
| } |
| |
| if (!args->jose->e[ctx->path_match - 1].buf) { |
| args->jose->e[ctx->path_match - 1].buf = (uint8_t *)args->temp; |
| args->jose->e[ctx->path_match - 1].len = 0; |
| } |
| |
| memcpy(args->temp, ctx->buf, ctx->npos); |
| args->temp += ctx->npos; |
| *args->temp_len -= ctx->npos; |
| args->jose->e[ctx->path_match - 1].len += ctx->npos; |
| |
| if (reason == LEJPCB_VAL_STR_END) { |
| n = lws_b64_decode_string_len( |
| (const char *)args->jose->e[ctx->path_match - 1].buf, |
| args->jose->e[ctx->path_match - 1].len, |
| (char *)args->jose->e[ctx->path_match - 1].buf, |
| args->jose->e[ctx->path_match - 1].len + 1); |
| if (n < 0) { |
| lwsl_err("%s: b64 decode failed\n", __func__); |
| return -1; |
| } |
| |
| args->temp -= args->jose->e[ctx->path_match - 1].len - n - 1; |
| *args->temp_len += |
| args->jose->e[ctx->path_match - 1].len - n - 1; |
| |
| args->jose->e[ctx->path_match - 1].len = n; |
| } |
| |
| return 0; |
| } |
| |
| void |
| lws_jose_init(struct lws_jose *jose) |
| { |
| memset(jose, 0, sizeof(*jose)); |
| } |
| |
| static void |
| lws_jose_recip_destroy(struct lws_jws_recpient *r) |
| { |
| lws_jwk_destroy(&r->jwk_ephemeral); |
| lws_jwk_destroy(&r->jwk); |
| } |
| |
| void |
| lws_jose_destroy(struct lws_jose *jose) |
| { |
| int n; |
| |
| for (n = 0; n < (int)LWS_ARRAY_SIZE(jose->recipient); n++) |
| lws_jose_recip_destroy(&jose->recipient[n]); |
| } |
| |
| |
| static int |
| lws_jose_parse(struct lws_jose *jose, const uint8_t *buf, int n, |
| char *temp, int *temp_len, int is_jwe) |
| { |
| struct lejp_ctx jctx; |
| struct jose_cb_args args; |
| int m; |
| |
| if (is_jwe) |
| /* prepare a context for JOSE epk ephemeral jwk parsing */ |
| lws_jwk_init_jps(&args.jwk_jctx, &args.jps, |
| &jose->recipient[jose->recipients].jwk_ephemeral, |
| NULL, NULL); |
| |
| args.is_jwe = is_jwe; |
| args.temp = temp; |
| args.temp_len = temp_len; |
| args.jose = jose; |
| args.recip = 0; |
| args.recipients_array = 0; |
| jose->recipients = 0; |
| |
| lejp_construct(&jctx, lws_jws_jose_cb, &args, jws_jose, |
| LWS_ARRAY_SIZE(jws_jose)); |
| |
| m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, n); |
| lejp_destruct(&jctx); |
| if (m < 0) { |
| lwsl_notice("%s: parse %.*s returned %d\n", __func__, n, buf, m); |
| return -1; |
| } |
| |
| if (!args.recipients_array && jose->recipient[0].unprot[LJJHI_ALG].buf) |
| /* if no explicit recipients[], we got one */ |
| jose->recipients++; |
| |
| return 0; |
| } |
| |
| int |
| lws_jws_parse_jose(struct lws_jose *jose, |
| const char *buf, int len, char *temp, int *temp_len) |
| { |
| return lws_jose_parse(jose, (const uint8_t *)buf, len, |
| temp, temp_len, 0); |
| } |
| |
| int |
| lws_jwe_parse_jose(struct lws_jose *jose, |
| const char *buf, int len, char *temp, int *temp_len) |
| { |
| return lws_jose_parse(jose, |
| (const uint8_t *)buf, len, temp, temp_len, 1); |
| } |
| |
| int |
| lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, |
| char *out, size_t out_len) |
| { |
| struct lws_jwk *jwk; |
| char *end = out + out_len - 1; |
| int n, m, f, sub = 0, vl; |
| |
| /* JOSE requires an alg */ |
| if (!jose->alg || !jose->alg->alg) |
| goto bail; |
| |
| *out++ = '{'; |
| |
| for (n = 0; n < LWS_COUNT_JOSE_HDR_ELEMENTS; n++) { |
| switch (n) { |
| |
| /* strings */ |
| |
| case LJJHI_ALG: /* REQUIRED */ |
| case LJJHI_JKU: /* Optional: string */ |
| case LJJHI_KID: /* Optional: string */ |
| case LJJHI_TYP: /* Optional: string: media type */ |
| case LJJHI_CTY: /* Optional: string: content media type */ |
| case LJJHI_X5U: /* Optional: string: pubkey cert / chain URL */ |
| case LJJHI_ENC: /* JWE only: Optional: string */ |
| case LJJHI_ZIP: /* JWE only: Optional: string ("DEF"=deflate) */ |
| if (jose->e[n].buf) { |
| out += lws_snprintf(out, end - out, |
| "%s\"%s\":\"%s\"", sub ? ",\n" : "", |
| jws_jose[n], jose->e[n].buf); |
| sub = 1; |
| } |
| break; |
| |
| case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */ |
| case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of cert */ |
| case LJJHI_APU: /* Additional arg for JWE ECDH: b64url */ |
| case LJJHI_APV: /* Additional arg for JWE ECDH: b64url */ |
| case LJJHI_IV: /* Additional arg for JWE AES: b64url */ |
| case LJJHI_TAG: /* Additional arg for JWE AES: b64url */ |
| case LJJHI_P2S: /* Additional arg for JWE PBES2: b64url: salt */ |
| if (jose->e[n].buf) { |
| out += lws_snprintf(out, end - out, |
| "%s\"%s\":\"", sub ? ",\n" : "", |
| jws_jose[n]); |
| sub = 1; |
| m = lws_b64_encode_string_url((const char *) |
| jose->e[n].buf, jose->e[n].len, |
| out, end - out); |
| if (m < 0) |
| return -1; |
| out += m; |
| out += lws_snprintf(out, end - out, "\""); |
| } |
| break; |
| |
| case LJJHI_P2C: /* Additional arg for JWE PBES2: int: count */ |
| break; /* don't support atm */ |
| |
| case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */ |
| if (jose->e[n].buf) { |
| out += lws_snprintf(out, end - out, |
| "%s\"%s\":\"", sub ? ",\n" : "", |
| jws_jose[n]); |
| sub = 1; |
| m = lws_b64_encode_string((const char *) |
| jose->e[n].buf, jose->e[n].len, |
| out, end - out); |
| if (m < 0) |
| return -1; |
| out += m; |
| out += lws_snprintf(out, end - out, "\""); |
| } |
| break; |
| |
| case LJJHI_EPK: /* Additional arg for JWE ECDH: eph pubkey */ |
| case LJJHI_JWK: /* Optional: jwk JSON object: public key: */ |
| |
| jwk = n == LJJHI_EPK ? &jose->recipient[0].jwk_ephemeral : aux_jwk; |
| if (!jwk || !jwk->kty) |
| break; |
| |
| out += lws_snprintf(out, end - out, "%s\"%s\":", |
| sub ? ",\n" : "", jws_jose[n]); |
| sub = 1; |
| vl = end - out; |
| m = lws_jwk_export(jwk, 0, out, &vl); |
| if (m < 0) { |
| lwsl_notice("%s: failed to export key\n", |
| __func__); |
| |
| return -1; |
| } |
| out += m; |
| break; |
| |
| case LJJHI_CRIT:/* Optional for send, REQUIRED: array of strings: |
| * mustn't contain standardized strings or null set */ |
| if (!jose->e[n].buf) |
| break; |
| |
| out += lws_snprintf(out, end - out, |
| "%s\"%s\":[", sub ? ",\n" : "", jws_jose[n]); |
| sub = 1; |
| |
| m = 0; |
| f = 1; |
| while ((unsigned int)m < jose->e[n].len && (end - out) > 1) { |
| if (jose->e[n].buf[m] == ' ') { |
| if (!f) |
| *out++ = '\"'; |
| |
| m++; |
| f = 1; |
| continue; |
| } |
| |
| if (f) { |
| if (m) |
| *out++ = ','; |
| *out++ = '\"'; |
| f = 0; |
| } |
| |
| *out++ = jose->e[n].buf[m]; |
| m++; |
| } |
| |
| break; |
| } |
| } |
| |
| *out++ = '}'; |
| |
| if (out > end - 2) |
| return -1; |
| |
| return out_len - (end - out) - 1; |
| |
| bail: |
| return -1; |
| } |