| /* |
| * libwebsockets - small server side websockets and web server implementation |
| * |
| * Copyright (C) 2010 - 2021 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. |
| * |
| * cose_key code |
| */ |
| |
| #include "private-lib-core.h" |
| //#include "private-lib-jose.h" |
| |
| #define lwsl_cose lwsl_notice |
| #define lwsl_hexdump_cose lwsl_hexdump_notice |
| |
| // #define VERBOSE 1 |
| |
| struct lws_cose_key_parse_state { |
| struct lws_cose_key *ck; |
| /**< single key created here if pkey_set is NULL */ |
| char buf[(8192 / 8) + 1]; |
| /**< enough for 8Kb key, only needed during parse */ |
| lws_cose_key_import_callback per_key_cb; |
| lws_dll2_owner_t *pkey_set; |
| /**< if non-NULL, expects a [ key set ], else single key */ |
| void *user; |
| size_t pos; |
| int cose_state; |
| cose_param_t seen[16]; |
| int seen_count; |
| int gencrypto_eidx; |
| int meta_idx; |
| unsigned short possible; |
| }; |
| |
| /* |
| * A COSE key representation is a CBOR map with a specified structure. The |
| * keys are |
| * |
| * LWSCOSE_WKK_KTY MUST int / tstr |
| * LWSCOSE_WKK_KID OPT bstr |
| * LWSCOSE_WKK_ALG OPT int / tstr |
| * LWSCOSE_WKK_KEY_OPS OPT [ + (int / tstr) ] |
| * LWSCOSE_WKK_BASE_IV OPT bstr |
| */ |
| |
| #if defined(_DEBUG) |
| |
| static const char *meta_names[] = { |
| "kty", "kid", "use", "key_ops", "base_iv", "alg" |
| }; |
| |
| static const char *oct_names[] = { |
| "k" |
| }; |
| |
| static const char *rsa_names[] = { |
| "e", "n", "d", "p", "q", "dp", "dq", "qi", "other", "ri", "di", "ti" |
| }; |
| |
| static const char *ec_names[] = { |
| "crv", "x", "d", "y", |
| }; |
| |
| void |
| lws_cose_key_dump(const struct lws_cose_key *ck) |
| { |
| const char **enames; |
| char hex[2048]; |
| int elems; |
| int n; |
| |
| (void)enames; |
| (void)meta_names; |
| |
| switch (ck->gencrypto_kty) { |
| |
| case LWS_GENCRYPTO_KTY_OCT: |
| elems = LWS_GENCRYPTO_OCT_KEYEL_COUNT; |
| enames = oct_names; |
| break; |
| case LWS_GENCRYPTO_KTY_RSA: |
| elems = LWS_GENCRYPTO_RSA_KEYEL_COUNT; |
| enames = rsa_names; |
| break; |
| case LWS_GENCRYPTO_KTY_EC: |
| elems = LWS_GENCRYPTO_EC_KEYEL_COUNT; |
| enames = ec_names; |
| break; |
| |
| default: |
| lwsl_err("%s: jwk %p: unknown type\n", __func__, ck); |
| |
| return; |
| } |
| |
| lwsl_cose("%s: cose_key %p, kty: %lld (gc %d)\n", __func__, ck, |
| (long long)ck->kty, ck->gencrypto_kty); |
| |
| for (n = 0; n < LWS_COUNT_COSE_KEY_ELEMENTS; n++) { |
| if (ck->meta[n].buf) { |
| lws_hex_from_byte_array(ck->meta[n].buf, ck->meta[n].len, |
| hex, sizeof(hex)); |
| lwsl_cose(" meta: %s: %s\n", meta_names[n], hex); |
| } |
| } |
| |
| for (n = 0; n < elems; n++) { |
| if (ck->e[n].buf) { |
| lws_hex_from_byte_array(ck->e[n].buf, ck->e[n].len, |
| hex, sizeof(hex)); |
| lwsl_cose(" e: %s: %s\n", enames[n], hex); |
| } |
| } |
| } |
| #endif |
| |
| static const char * const kty_strings[] = { NULL, |
| "OKP", "EC2", "RSA", "SYMMETRIC", "HSS_LMS", "WALNUTDSA" |
| }; |
| |
| int |
| lws_cose_key_checks(const lws_cose_key_t *key, int64_t kty, cose_param_t alg, |
| int key_op, const char *crv) |
| { |
| const struct lws_gencrypto_keyelem *ke; |
| |
| /* |
| * we ourselves have to have a very clear idea what we need, even if |
| * matches are optional in the key itself |
| */ |
| assert(key); |
| assert(kty); |
| assert(alg); |
| assert(key_op); |
| assert((kty != LWSCOSE_WKKTV_OKP && kty != LWSCOSE_WKKTV_EC2) || crv); |
| |
| /* RFC8152 8.1: |
| * |
| * The 'kty' field MUST be present, and it MUST be '...'. |
| * |
| * But kty can come as an int or a string, but we convert well-known |
| * kty ints to the corresponding string representation at key import |
| */ |
| if (!kty || kty >= (int)LWS_ARRAY_SIZE(kty_strings)) { |
| /* we don't understand it */ |
| lwsl_notice("%s: unknown kty %d\n", __func__, (int)kty); |
| goto bail; |
| } |
| |
| ke = &key->meta[COSEKEY_META_KTY]; |
| if (ke->buf && (strlen(kty_strings[kty]) != ke->len || |
| memcmp(kty_strings[kty], ke->buf, ke->len))) { |
| lwsl_notice("%s: key is of wrong kty\n", __func__); |
| lwsl_hexdump_notice(ke->buf, ke->len); |
| goto bail; |
| } |
| |
| /* ... |
| * If the 'alg' field is present, it MUST match the ... signature |
| * algorithm being used. |
| * |
| * We attempt to convert key alg text representations to a well-known |
| * index, if we can't, then we don't know the alg anyway and should fail |
| * it |
| */ |
| |
| if (!key->cose_alg && key->meta[COSEKEY_META_ALG].buf) { |
| lwsl_notice("%s: alg fail 1\n", __func__); |
| goto bail; |
| } |
| |
| if (key->cose_alg && /* accept it being absent altogether */ |
| key->cose_alg != alg) { |
| lwsl_notice("%s: alg fail 2\n", __func__); |
| |
| goto bail; |
| } |
| |
| /* ... |
| * If the 'key_ops' field is present, it MUST include 'sign' / 'verify' |
| * when creating /verifying an ... signature. |
| */ |
| |
| ke = &key->meta[COSEKEY_META_KEY_OPS]; |
| if (ke->buf && ke->len) { |
| uint32_t n; |
| |
| for (n = 0; n < ke->len; n++) |
| if (ke->buf[n] == key_op) |
| break; |
| |
| if (n == ke->len) |
| goto bail; |
| } |
| |
| /* |
| * If it's related to EC, check there is a curve associated with the |
| * key, and check it is what we expect |
| */ |
| |
| if (kty == LWSCOSE_WKKTV_OKP || kty == LWSCOSE_WKKTV_EC2) { |
| ke = &key->e[LWS_GENCRYPTO_EC_KEYEL_CRV]; |
| |
| if (!ke->buf) |
| goto bail; |
| if (ke->len != strlen(crv)) |
| goto bail; |
| if (memcmp(ke->buf, crv, ke->len)) |
| goto bail; |
| } |
| |
| /* We're willing to use this key for this operation */ |
| |
| return 0; |
| |
| bail: |
| lwsl_notice("%s: key rejected\n", __func__); |
| |
| return 1; |
| } |
| |
| |
| static int |
| lws_ck_set_el(struct lws_gencrypto_keyelem *e, char *in, size_t len) |
| { |
| e->buf = lws_malloc(len + 1, "ck"); |
| if (!e->buf) |
| return -1; |
| |
| memcpy(e->buf, in, len); |
| e->buf[len] = '\0'; |
| e->len = (uint32_t)len; |
| |
| return 0; |
| } |
| |
| static struct { |
| const char *curve; |
| cose_param_t cose_id; |
| } cose_curves[] = { |
| { "P-256", LWSCOSE_WKEC_P256 }, |
| { "P-384", LWSCOSE_WKEC_P384 }, |
| { "P-521", LWSCOSE_WKEC_P521 }, |
| { "X25519", LWSCOSE_WKEC_X25519 }, |
| { "X448", LWSCOSE_WKEC_X448 }, |
| { "ED25519", LWSCOSE_WKEC_ED25519 }, |
| { "ED448", LWSCOSE_WKEC_ED448 }, |
| { "SECP256K1", LWSCOSE_WKEC_SECP256K1 }, |
| }; |
| |
| /* 0 means failed */ |
| |
| static cose_param_t |
| lws_cose_curve_name_to_id(const char *curve) |
| { |
| int n; |
| |
| for (n = 0; n < (int)LWS_ARRAY_SIZE(cose_curves); n++) |
| if (!strcmp(cose_curves[n].curve, curve)) |
| return cose_curves[n].cose_id; |
| |
| return 0; |
| } |
| |
| static const char * |
| lws_cose_curve_id_to_name(cose_param_t id) |
| { |
| int n; |
| |
| for (n = 0; n < (int)LWS_ARRAY_SIZE(cose_curves); n++) |
| if (cose_curves[n].cose_id == id) |
| return cose_curves[n].curve; |
| |
| return 0; |
| } |
| |
| static const char * const wk_algs[] = { |
| "ES256", "ES384", "ES512" |
| }; |
| static signed char wk_alg_indexes[] = { |
| LWSCOSE_WKAECDSA_ALG_ES256, |
| LWSCOSE_WKAECDSA_ALG_ES384, |
| LWSCOSE_WKAECDSA_ALG_ES512, |
| }; |
| |
| static signed char |
| cb_cose_key(struct lecp_ctx *ctx, char reason) |
| { |
| struct lws_cose_key_parse_state *cps = |
| (struct lws_cose_key_parse_state *)ctx->user; |
| struct lws_gencrypto_keyelem *ke = NULL; |
| const char *p; |
| int n; |
| |
| #if defined(VERBOSE) |
| lwsl_notice("%s: reason %d, path %s, ord %u, ppos %d\n", __func__, |
| reason & 0x3f, |
| ctx->path, ctx->st[ctx->sp - 1].ordinal, |
| ctx->pst[ctx->pst_sp].ppos); |
| #endif |
| |
| switch (reason) { |
| case LECPCB_OBJECT_START: |
| if (cps->ck) |
| break; |
| goto ak; |
| case LECPCB_ARRAY_ITEM_START: |
| if (cps->pkey_set && ctx->pst[ctx->pst_sp].ppos == 2) { |
| ak: |
| cps->ck = lws_zalloc(sizeof(*cps->ck), __func__); |
| if (!cps->ck) |
| goto bail; |
| cps->cose_state = 0; |
| cps->meta_idx = -1; |
| cps->gencrypto_eidx = -1; |
| cps->seen_count = 0; |
| |
| if (cps->pkey_set) |
| lws_dll2_add_tail(&cps->ck->list, cps->pkey_set); |
| } |
| break; |
| case LECPCB_ARRAY_ITEM_END: |
| if (cps->pkey_set && ctx->pst[ctx->pst_sp].ppos == 2) { |
| if (cps->per_key_cb) |
| cps->per_key_cb(cps->ck, cps->user); |
| } |
| break; |
| case LECPCB_TAG_START: |
| if (ctx->item.u.u64 != LWSCOAP_CONTENTFORMAT_COSE_KEY) { |
| lwsl_warn("%s: unexpected tag\n", __func__); |
| goto bail; |
| } |
| break; |
| |
| case LECPCB_VAL_NUM_INT: |
| case LECPCB_VAL_NUM_UINT: |
| if (!ctx->sp) { |
| lwsl_warn("%s: unexpected uint %d, ppos %d\n", |
| __func__, ctx->sp, ctx->pst[ctx->sp].ppos); |
| goto bail; |
| } |
| |
| if (!lecp_parse_map_is_key(ctx)) { |
| const char *kty_str; |
| |
| /* value part of map */ |
| |
| switch (cps->cose_state) { |
| case LWSCOSE_WKK_KTY: |
| assert(cps->ck); |
| cps->ck->kty = (int)ctx->item.u.u64; |
| |
| /* convert the cose key type to gencrypto one */ |
| switch (ctx->item.u.u64) { |
| case LWSCOSE_WKKTV_OKP: |
| cps->ck->gencrypto_kty = |
| LWS_GENCRYPTO_KTY_EC; |
| kty_str = "OKP"; |
| break; |
| case LWSCOSE_WKKTV_EC2: |
| kty_str = "EC2"; |
| cps->ck->gencrypto_kty = |
| LWS_GENCRYPTO_KTY_EC; |
| break; |
| case LWSCOSE_WKKTV_RSA: |
| kty_str = "RSA"; |
| cps->ck->gencrypto_kty = |
| LWS_GENCRYPTO_KTY_RSA; |
| break; |
| case LWSCOSE_WKKTV_SYMMETRIC: |
| kty_str = "SYMMETRIC"; |
| cps->ck->gencrypto_kty = |
| LWS_GENCRYPTO_KTY_OCT; |
| break; |
| // case LWSCOSE_WKKTV_HSS_LMS: |
| // case LWSCOSE_WKKTV_WALNUTDSA: |
| default: |
| lwsl_warn("%s: unknown kty\n", __func__); |
| goto bail; |
| } |
| |
| /* store the string version of the key type */ |
| |
| ke = &cps->ck->meta[COSEKEY_META_KTY]; |
| ke->len = (uint32_t)strlen(kty_str); |
| ke->buf = lws_malloc(ke->len + 1, __func__); |
| if (!ke->buf) |
| goto bail; |
| memcpy(ke->buf, kty_str, ke->len + 1); |
| break; |
| case LWSCOSE_WKK_ALG: |
| /* |
| * He can tie the key to a cose alg code |
| */ |
| cps->ck->cose_alg = (int)ctx->item.u.u64; |
| break; |
| case LWSCOSE_WKK_KEY_OPS: |
| if (!cps->pkey_set && |
| (ctx->pst[ctx->sp].ppos != 3 || |
| strcmp(ctx->path, ".[]"))) { |
| lwsl_warn("%s: unexpected kops\n", |
| __func__); |
| goto bail; |
| } |
| if (cps->pkey_set && |
| (ctx->pst[ctx->sp].ppos != 5 || |
| strcmp(ctx->path, "[].[]"))) { |
| lwsl_warn("%s: unexpected kops\n", |
| __func__); |
| goto bail; |
| } |
| break; |
| case LWSCOSE_WKOKP_CRV: |
| cps->ck->cose_curve = (int)ctx->item.u.u64; |
| p = lws_cose_curve_id_to_name(cps->ck->cose_curve); |
| if (p) { |
| ke = &cps->ck->e[LWS_GENCRYPTO_EC_KEYEL_CRV]; |
| ke->len = (uint32_t)strlen(p); |
| ke->buf = lws_malloc(ke->len + 1, __func__); |
| if (!ke->buf) |
| goto bail; |
| memcpy(ke->buf, p, ke->len); |
| ke->buf[ke->len] = '\0'; |
| } |
| break; |
| default: |
| lwsl_warn("%s: uint not allowed in state %d\n", |
| __func__, cps->cose_state); |
| /* int not allowed in this state */ |
| goto bail; |
| } |
| |
| cps->cose_state = 0; |
| break; |
| } |
| |
| /* key part of map pair */ |
| |
| /* |
| * Disallow any of these coming more than once |
| */ |
| cps->cose_state = (int)ctx->item.u.u64; |
| for (n = 0 ; n < cps->seen_count; n++) |
| if (cps->seen[n] == cps->cose_state) { |
| /* dupe */ |
| lwsl_warn("%s: duplicate map name %d\n", |
| __func__, cps->cose_state); |
| goto bail; |
| } |
| |
| if (cps->seen_count >= (int)LWS_ARRAY_SIZE(cps->seen)) |
| goto bail; |
| cps->seen[cps->seen_count++] = cps->cose_state; |
| |
| cps->meta_idx = -1; |
| switch ((int)ctx->item.u.u64) { |
| case LWSCOSE_WKK_KTY: |
| cps->meta_idx = COSEKEY_META_KTY; |
| break; |
| case LWSCOSE_WKK_KID: |
| cps->meta_idx = COSEKEY_META_KID; |
| break; |
| case LWSCOSE_WKK_ALG: |
| cps->meta_idx = COSEKEY_META_ALG; |
| break; |
| case LWSCOSE_WKK_KEY_OPS: |
| cps->meta_idx = COSEKEY_META_KEY_OPS; |
| break; |
| case LWSCOSE_WKK_BASE_IV: |
| cps->meta_idx = COSEKEY_META_BASE_IV; |
| break; |
| |
| default: |
| cps->gencrypto_eidx = -1; |
| |
| switch (cps->ck->kty) { |
| case LWSCOSE_WKKTV_OKP: |
| switch ((int)ctx->item.u.u64) { |
| case LWSCOSE_WKOKP_CRV: |
| cps->cose_state = LWSCOSE_WKOKP_CRV; |
| break; |
| case LWSCOSE_WKOKP_X: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_EC_KEYEL_X; |
| break; |
| case LWSCOSE_WKOKP_D: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_EC_KEYEL_D; |
| break; |
| default: |
| goto bail; |
| } |
| break; |
| case LWSCOSE_WKKTV_EC2: |
| switch ((int)ctx->item.u.u64) { |
| case LWSCOSE_WKECKP_CRV: |
| cps->cose_state = LWSCOSE_WKOKP_CRV; |
| break; |
| case LWSCOSE_WKECKP_X: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_EC_KEYEL_X; |
| break; |
| case LWSCOSE_WKECKP_Y: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_EC_KEYEL_Y; |
| break; |
| case LWSCOSE_WKECKP_D: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_EC_KEYEL_D; |
| break; |
| default: |
| goto bail; |
| } |
| break; |
| case LWSCOSE_WKKTV_RSA: |
| switch ((int)ctx->item.u.u64) { |
| case LWSCOSE_WKKPRSA_N: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_N; |
| break; |
| case LWSCOSE_WKKPRSA_E: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_E; |
| break; |
| case LWSCOSE_WKKPRSA_D: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_D; |
| break; |
| case LWSCOSE_WKKPRSA_P: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_P; |
| break; |
| case LWSCOSE_WKKPRSA_Q: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_Q; |
| break; |
| case LWSCOSE_WKKPRSA_DP: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_DP; |
| break; |
| case LWSCOSE_WKKPRSA_DQ: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_DQ; |
| break; |
| case LWSCOSE_WKKPRSA_QINV: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_QI; |
| break; |
| case LWSCOSE_WKKPRSA_OTHER: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_OTHER; |
| break; |
| case LWSCOSE_WKKPRSA_RI: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_RI; |
| break; |
| case LWSCOSE_WKKPRSA_DI: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_DI; |
| break; |
| case LWSCOSE_WKKPRSA_TI: |
| cps->gencrypto_eidx = |
| LWS_GENCRYPTO_RSA_KEYEL_TI; |
| break; |
| default: |
| goto bail; |
| } |
| break; |
| case LWSCOSE_WKKTV_SYMMETRIC: |
| if (ctx->item.u.i64 != -1 && |
| ctx->item.u.u64 != LWSCOSE_WKSYMKP_KEY_VALUE) |
| goto bail; |
| |
| cps->gencrypto_eidx = LWS_GENCRYPTO_OCT_KEYEL_K; |
| break; |
| default: |
| lwsl_warn("%s: unknown kty\n", __func__); |
| goto bail; |
| } |
| break; |
| } |
| break; |
| |
| case LECPCB_VAL_BLOB_START: |
| if (!ctx->sp || !(ctx->st[ctx->sp - 1].ordinal & 1)) { |
| lwsl_warn("%s: unexpected blob\n", __func__); |
| goto bail; |
| } |
| |
| if (cps->cose_state == COSEKEY_META_KID) |
| break; |
| |
| /* |
| * Validate the association of the blob now, collect it into |
| * the temp buf in cps and then alloc and copy it into the |
| * related key element when it's at the end and the size known |
| */ |
| |
| cps->pos = 0; |
| if (cps->gencrypto_eidx >= 0) { |
| if (cps->ck->e[cps->gencrypto_eidx].buf) { |
| lwsl_warn("%s: e[%d] set twice %d\n", __func__, |
| cps->gencrypto_eidx, |
| cps->ck->e[cps->gencrypto_eidx].len); |
| /* key elements must only come at most once */ |
| goto bail; |
| } |
| break; |
| } |
| if (cps->meta_idx >= 0) |
| break; |
| |
| goto bail; |
| |
| case LECPCB_VAL_BLOB_CHUNK: |
| case LECPCB_VAL_BLOB_END: |
| if (cps->pos + ctx->npos > sizeof(cps->buf)) { |
| lwsl_warn("%s: oversize blob\n", __func__); |
| goto bail; |
| } |
| memcpy(cps->buf + cps->pos, ctx->buf, ctx->npos); |
| cps->pos += ctx->npos; |
| |
| if (reason == LECPCB_VAL_BLOB_CHUNK) |
| break; |
| |
| /* we have the key element data, let's make the ck element */ |
| if (cps->gencrypto_eidx >= 0) { |
| |
| if (cps->ck->e[cps->gencrypto_eidx].buf) |
| break; |
| |
| lws_ck_set_el(&cps->ck->e[cps->gencrypto_eidx], |
| (char *)cps->buf, cps->pos); |
| cps->gencrypto_eidx = -1; |
| break; |
| } |
| |
| |
| if (cps->meta_idx >= 0) { |
| lws_ck_set_el(&cps->ck->meta[cps->meta_idx], |
| (char *)cps->buf, cps->pos); |
| cps->meta_idx = -1; |
| } |
| cps->pos = 0; |
| break; |
| case LECPCB_VAL_STR_END: |
| if (cps->cose_state == LWSCOSE_WKOKP_CRV) { |
| cps->ck->cose_curve = lws_cose_curve_name_to_id(ctx->buf); |
| ke = &cps->ck->e[LWS_GENCRYPTO_EC_KEYEL_CRV]; |
| ke->len = ctx->npos; |
| ke->buf = lws_malloc(ctx->npos, __func__); |
| if (!ke->buf) |
| goto bail; |
| memcpy(ke->buf, ctx->buf, ctx->npos); |
| } |
| |
| if (!lecp_parse_map_is_key(ctx) && |
| cps->cose_state == LWSCOSE_WKK_ALG) { |
| size_t n; |
| |
| for (n = 0; n < LWS_ARRAY_SIZE(wk_algs); n++) |
| if (ctx->npos == strlen(wk_algs[n]) && |
| !memcmp(ctx->buf, wk_algs[n], ctx->npos)) { |
| cps->ck->cose_alg = wk_alg_indexes[n]; |
| break; |
| } |
| |
| if (n == LWS_ARRAY_SIZE(wk_algs)) |
| /* key is for an alg we don't understand */ |
| lwsl_warn("%s: key for unknown alg %.*s\n", |
| __func__, (int)ctx->npos, ctx->buf); |
| |
| ke = &cps->ck->meta[COSEKEY_META_ALG]; |
| ke->len = ctx->npos; |
| ke->buf = lws_malloc(ctx->npos, __func__); |
| if (!ke->buf) |
| goto bail; |
| memcpy(ke->buf, ctx->buf, ctx->npos); |
| } |
| |
| break; |
| } |
| |
| return 0; |
| |
| bail: |
| lwsl_warn("%s: bail\n", __func__); |
| lws_cose_key_destroy(&cps->ck); |
| |
| if (cps->pkey_set) { |
| lws_cose_key_set_destroy(cps->pkey_set); |
| cps->pkey_set = NULL; |
| } |
| |
| return -1; |
| } |
| |
| void |
| lws_cose_key_destroy_elements(struct lws_gencrypto_keyelem *el, int m) |
| { |
| int n; |
| |
| if (!el) |
| return; |
| |
| for (n = 0; n < m; n++) |
| if (el[n].buf) { |
| /* wipe all key material when it goes out of scope */ |
| lws_explicit_bzero(el[n].buf, el[n].len); |
| lws_free_set_NULL(el[n].buf); |
| el[n].len = 0; |
| } |
| } |
| |
| void |
| lws_cose_key_destroy(struct lws_cose_key **pck) |
| { |
| struct lws_cose_key *ck = *pck; |
| |
| if (!ck) |
| return; |
| |
| lws_dll2_remove(&ck->list); |
| |
| lws_cose_key_destroy_elements(ck->e, LWS_ARRAY_SIZE(ck->e)); |
| lws_cose_key_destroy_elements(ck->meta, LWS_ARRAY_SIZE(ck->meta)); |
| |
| lws_free_set_NULL(*pck); |
| } |
| |
| static int |
| lws_cose_key_set_memb_remove(struct lws_dll2 *d, void *user) |
| { |
| lws_cose_key_t *ck = lws_container_of(d, lws_cose_key_t, list); |
| |
| lws_dll2_remove(d); |
| lws_cose_key_destroy(&ck); |
| |
| return 0; |
| } |
| |
| void |
| lws_cose_key_set_destroy(lws_dll2_owner_t *o) |
| { |
| lws_dll2_foreach_safe(o, NULL, lws_cose_key_set_memb_remove); |
| } |
| |
| lws_cose_key_t * |
| lws_cose_key_from_set(lws_dll2_owner_t *set, const uint8_t *kid, size_t kl) |
| { |
| lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(set)) { |
| lws_cose_key_t *ck = lws_container_of(p, lws_cose_key_t, list); |
| struct lws_gencrypto_keyelem *ke = &ck->meta[COSEKEY_META_KID]; |
| |
| if (!kid) /* always the first then */ |
| return ck; |
| |
| if (ke->buf && ke->len == (uint32_t)kl && |
| !memcmp(ke->buf, kid, ke->len)) |
| return ck; |
| |
| } lws_end_foreach_dll(p); |
| |
| return NULL; |
| } |
| |
| lws_cose_key_t * |
| lws_cose_key_generate(struct lws_context *context, cose_param_t cose_kty, |
| int use_mask, int bits, const char *curve, |
| const uint8_t *kid, size_t kl) |
| { |
| struct lws_gencrypto_keyelem *ke; |
| lws_cose_key_t *ck; |
| size_t sn; |
| int n; |
| |
| ck = lws_zalloc(sizeof(*ck), __func__); |
| if (!ck) |
| return NULL; |
| |
| ck->kty = cose_kty; |
| ck->private_key = 1; |
| |
| if (use_mask & 0xfffe) { |
| int count = 0; |
| |
| for (n = 1; n < 15; n++) |
| if (use_mask & (1 << n)) |
| count++; |
| ke = &ck->meta[COSEKEY_META_KEY_OPS]; |
| ke->buf = lws_malloc((size_t)count, __func__); |
| if (!ke->buf) |
| goto fail; |
| ke->len = (uint32_t)count; |
| count = 0; |
| for (n = 1; n < 15; n++) |
| if (use_mask & (1 << n)) |
| ke->buf[count++] = (uint8_t)n; |
| } |
| |
| if (kid) { |
| ke = &ck->meta[COSEKEY_META_KID]; |
| ke->buf = lws_malloc(kl, __func__); |
| ke->len = (uint32_t)kl; |
| memcpy(ke->buf, kid, ke->len); |
| } |
| |
| switch (cose_kty) { |
| case LWSCOSE_WKKTV_RSA: |
| { |
| struct lws_genrsa_ctx ctx; |
| |
| memset(&ctx, 0, sizeof(ctx)); |
| ck->gencrypto_kty = LWS_GENCRYPTO_KTY_RSA; |
| |
| lwsl_notice("%s: generating %d bit RSA key\n", |
| __func__, bits); |
| n = lws_genrsa_new_keypair(context, &ctx, |
| LGRSAM_PKCS1_1_5, |
| ck->e, bits); |
| lws_genrsa_destroy(&ctx); |
| if (n) { |
| lwsl_err("%s: problem generating RSA key\n", |
| __func__); |
| goto fail; |
| } |
| } |
| break; |
| case LWSCOSE_WKKTV_SYMMETRIC: |
| |
| ck->gencrypto_kty = LWS_GENCRYPTO_KTY_OCT; |
| sn = (unsigned int)lws_gencrypto_bits_to_bytes(bits); |
| ke = &ck->e[LWS_GENCRYPTO_OCT_KEYEL_K]; |
| ke->buf = lws_malloc(sn, "oct"); |
| if (!ke->buf) |
| goto fail; |
| ke->len = (uint32_t)sn; |
| if (lws_get_random(context, ke->buf, sn) != sn) { |
| lwsl_err("%s: problem getting random\n", __func__); |
| goto fail; |
| } |
| break; |
| |
| case LWSCOSE_WKKTV_OKP: |
| case LWSCOSE_WKKTV_EC2: |
| { |
| struct lws_genec_ctx ctx; |
| |
| ck->gencrypto_kty = LWS_GENCRYPTO_KTY_EC; |
| |
| if (!curve) { |
| lwsl_err("%s: must have a named curve\n", __func__); |
| |
| goto fail; |
| } |
| |
| if (lws_genecdsa_create(&ctx, context, NULL)) |
| goto fail; |
| |
| ctx.genec_alg = LEGENEC_ECDSA; |
| lwsl_notice("%s: generating ECDSA key on curve %s\n", __func__, |
| curve); |
| |
| n = lws_genecdsa_new_keypair(&ctx, curve, ck->e); |
| lws_genec_destroy(&ctx); |
| if (n) { |
| lwsl_err("%s: problem generating ECDSA key\n", __func__); |
| goto fail; |
| } |
| /* trim the trailing NUL */ |
| ck->e[LWS_GENCRYPTO_EC_KEYEL_CRV].len = (uint32_t)strlen(curve); |
| } |
| break; |
| |
| default: |
| lwsl_err("%s: unknown kty\n", __func__); |
| goto fail; |
| } |
| |
| return ck; |
| |
| fail: |
| lws_free_set_NULL(ck); |
| |
| return NULL; |
| } |
| |
| struct lws_cose_key * |
| lws_cose_key_import(lws_dll2_owner_t *pkey_set, lws_cose_key_import_callback cb, |
| void *user, const uint8_t *in, size_t len) |
| { |
| struct lws_cose_key_parse_state cps; |
| struct lecp_ctx ctx; |
| int m; |
| |
| memset(&cps, 0, sizeof(cps)); |
| |
| cps.per_key_cb = cb; |
| cps.user = user; |
| cps.pkey_set = pkey_set; |
| cps.gencrypto_eidx = -1; |
| |
| lecp_construct(&ctx, cb_cose_key, &cps, NULL, 0); |
| m = lecp_parse(&ctx, in, len); |
| lecp_destruct(&ctx); |
| |
| if (m < 0) { |
| lwsl_notice("%s: parse got %d\n", __func__, m); |
| if (cps.pkey_set) |
| lws_cose_key_set_destroy(cps.pkey_set); |
| |
| return NULL; |
| } |
| |
| switch (cps.ck->gencrypto_kty) { |
| case LWS_GENCRYPTO_KTY_UNKNOWN: |
| lwsl_notice("%s: missing or unknown ktys\n", __func__); |
| goto bail; |
| default: |
| break; |
| } |
| |
| return cps.ck; |
| |
| bail: |
| lws_cose_key_destroy(&cps.ck); |
| return NULL; |
| } |
| |
| /* gencrypto element orering -> cose key parameters */ |
| |
| static const signed char ckp[3][12] = { |
| { /* LWS_GENCRYPTO_KTY_OCT (1) */ |
| /* LWS_GENCRYPTO_OCT_KEYEL_K */ LWSCOSE_WKSYMKP_KEY_VALUE, |
| }, |
| { /* LWS_GENCRYPTO_KTY_RSA (2) */ |
| /* LWS_GENCRYPTO_RSA_KEYEL_E */ LWSCOSE_WKKPRSA_E, |
| /* LWS_GENCRYPTO_RSA_KEYEL_N */ LWSCOSE_WKKPRSA_N, |
| /* LWS_GENCRYPTO_RSA_KEYEL_D */ LWSCOSE_WKKPRSA_D, |
| /* LWS_GENCRYPTO_RSA_KEYEL_P */ LWSCOSE_WKKPRSA_P, |
| /* LWS_GENCRYPTO_RSA_KEYEL_Q */ LWSCOSE_WKKPRSA_Q, |
| /* LWS_GENCRYPTO_RSA_KEYEL_DP */ LWSCOSE_WKKPRSA_DP, |
| /* LWS_GENCRYPTO_RSA_KEYEL_DQ */ LWSCOSE_WKKPRSA_DQ, |
| /* LWS_GENCRYPTO_RSA_KEYEL_QT */ LWSCOSE_WKKPRSA_QINV, |
| /* LWS_GENCRYPTO_RSA_KEYEL_OTHER */ LWSCOSE_WKKPRSA_OTHER, |
| /* LWS_GENCRYPTO_RSA_KEYEL_RI */ LWSCOSE_WKKPRSA_RI, |
| /* LWS_GENCRYPTO_RSA_KEYEL_DI */ LWSCOSE_WKKPRSA_DI, |
| /* LWS_GENCRYPTO_RSA_KEYEL_TI */ LWSCOSE_WKKPRSA_TI, |
| }, |
| { /* LWS_GENCRYPTO_KTY_EC (3) */ |
| /* LWS_GENCRYPTO_EC_KEYEL_CRV */ LWSCOSE_WKECKP_CRV, |
| /* LWS_GENCRYPTO_EC_KEYEL_X */ LWSCOSE_WKECKP_X, |
| /* LWS_GENCRYPTO_EC_KEYEL_D */ LWSCOSE_WKECKP_D, |
| /* LWS_GENCRYPTO_EC_KEYEL_Y */ LWSCOSE_WKECKP_Y, |
| } |
| }; |
| |
| enum lws_lec_pctx_ret |
| lws_cose_key_export(lws_cose_key_t *ck, lws_lec_pctx_t *ctx, int flags) |
| { |
| cose_param_t pa = 0; |
| int n; |
| |
| if (!ctx->opaque[0]) { |
| |
| ctx->opaque[0] = 1; /* map pair count */ |
| ctx->opaque[1] = 1; /* element index */ |
| ctx->opaque[2] = 0; /* public mask */ |
| ctx->opaque[3] = 0; /* doing AGAIN */ |
| |
| switch (ck->gencrypto_kty) { |
| case LWS_GENCRYPTO_KTY_OCT: |
| /* nothing to differentiate */ |
| ctx->opaque[2] = 1 << LWS_GENCRYPTO_OCT_KEYEL_K; |
| break; |
| case LWS_GENCRYPTO_KTY_RSA: |
| ctx->opaque[2] = 1 << LWS_GENCRYPTO_RSA_KEYEL_E; |
| break; |
| case LWS_GENCRYPTO_KTY_EC: |
| ctx->opaque[2] = (1 << LWS_GENCRYPTO_EC_KEYEL_X) | |
| (1 << LWS_GENCRYPTO_EC_KEYEL_Y); |
| break; |
| default: |
| goto fail; |
| } |
| |
| if (flags & LWSJWKF_EXPORT_PRIVATE) |
| ctx->opaque[2] = 0xffff; |
| |
| /* |
| * We first need to find out how many CBOR map pairs we are |
| * planning to create, so we can set a fixed length map of the |
| * right size. |
| */ |
| |
| for (n = 0; n < (int)LWS_ARRAY_SIZE(ck->e); n++) |
| if ((ctx->opaque[2] & (1 << n)) && ck->e[n].buf) |
| ctx->opaque[0]++; |
| |
| /* |
| * We always issue kty, others may be |
| * |
| * KID / ALG / KEY_OPS / BASE_IV |
| */ |
| |
| if (ck->meta[COSEKEY_META_KID].buf) |
| ctx->opaque[0]++; |
| if (ck->meta[COSEKEY_META_ALG].buf) |
| ctx->opaque[0]++; |
| if (ck->meta[COSEKEY_META_KEY_OPS].buf) |
| ctx->opaque[0]++; |
| if (ck->meta[COSEKEY_META_BASE_IV].buf) |
| ctx->opaque[0]++; |
| |
| lws_lec_int(ctx, LWS_CBOR_MAJTYP_MAP, 0, (uint64_t)ctx->opaque[0]); |
| lws_lec_signed(ctx, LWSCOSE_WKK_KTY); |
| lws_lec_signed(ctx, (int64_t)ck->kty); |
| |
| if (ck->gencrypto_kty == LWS_GENCRYPTO_KTY_EC) { |
| struct lws_gencrypto_keyelem *ke = |
| &ck->e[LWS_GENCRYPTO_EC_KEYEL_CRV]; |
| |
| if (!ke->buf || |
| ck->e[LWS_GENCRYPTO_EC_KEYEL_CRV].len > 10) { |
| lwsl_err("%s: no curve type\n", __func__); |
| goto fail; |
| } |
| |
| pa = lws_cose_curve_name_to_id((const char *)ke->buf); |
| lws_lec_signed(ctx, LWSCOSE_WKECKP_CRV); |
| if (pa) |
| lws_lec_signed(ctx, pa); |
| else |
| lws_lec_printf(ctx, "%.*s", |
| (int)ke->len, ke->buf); |
| } |
| |
| |
| ctx->opaque[1] = COSEKEY_META_KID; |
| } |
| |
| /* |
| * Start from the second key meta, then do any elements that are set |
| */ |
| |
| while (ctx->buf != ctx->end) { |
| struct lws_gencrypto_keyelem *ke = NULL; |
| int cose_key_param = 0; |
| |
| if (lws_lec_scratch(ctx)) |
| break; |
| |
| if (ctx->opaque[1] == LWS_ARRAY_SIZE(ck->e) + |
| LWS_COUNT_COSE_KEY_ELEMENTS) |
| break; |
| |
| if (ctx->opaque[1] >= LWS_COUNT_COSE_KEY_ELEMENTS) { |
| n = ctx->opaque[1] - LWS_COUNT_COSE_KEY_ELEMENTS; |
| |
| if (ck->gencrypto_kty != LWS_GENCRYPTO_KTY_EC || |
| n != LWS_GENCRYPTO_EC_KEYEL_CRV) { |
| /* we didn't already encode his curve */ |
| |
| if ((ctx->opaque[2] & (1 << n)) && |
| ck->e[n].buf && ck->e[n].len) { |
| ke = &ck->e[n]; |
| cose_key_param = ckp[ck->gencrypto_kty - 1][n]; |
| } |
| } |
| } else |
| |
| switch (ctx->opaque[1]) { |
| |
| case COSEKEY_META_KID: /* bstr */ |
| if (ck->meta[COSEKEY_META_KID].buf) { |
| ke = &ck->meta[COSEKEY_META_KID]; |
| cose_key_param = LWSCOSE_WKK_KID; |
| // lwsl_hexdump_notice(ke->buf, ke->len); |
| } |
| break; |
| |
| case COSEKEY_META_ALG: /* int, tstr */ |
| if (ck->meta[COSEKEY_META_ALG].buf) { |
| ke = &ck->meta[COSEKEY_META_ALG]; |
| cose_key_param = LWSCOSE_WKK_ALG; |
| } |
| break; |
| |
| case COSEKEY_META_KEY_OPS: /* [ int ] */ |
| if (!ck->meta[COSEKEY_META_KEY_OPS].buf) |
| break; |
| ke = &ck->meta[COSEKEY_META_KEY_OPS]; |
| |
| n = (int)ke->len; |
| if (n > 10) |
| n = 10; |
| |
| /* |
| * We copy this array into scratch by hand now we |
| * made sure it will fit, we will never need AGAIN |
| */ |
| |
| lws_lec_signed(ctx, LWSCOSE_WKK_KEY_OPS); |
| lws_lec_int(ctx, LWS_CBOR_MAJTYP_ARRAY, 0, (uint64_t)n); |
| memcpy(&ctx->scratch[ctx->scratch_len], ke->buf, |
| (size_t)n); |
| ctx->scratch_len = (uint8_t)(ctx->scratch_len + (uint8_t)n); |
| ke = NULL; |
| break; |
| |
| case COSEKEY_META_BASE_IV: /* bstr */ |
| if (ck->meta[COSEKEY_META_BASE_IV].buf) { |
| ke = &ck->meta[COSEKEY_META_BASE_IV]; |
| cose_key_param = LWSCOSE_WKK_BASE_IV; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (ke && ke->buf && ke->len) { |
| |
| if (!ctx->opaque[3]) |
| lws_lec_signed(ctx, cose_key_param); |
| |
| /* binary string or text string? */ |
| if (ctx->opaque[1] == COSEKEY_META_KID || |
| ctx->opaque[1] == COSEKEY_META_BASE_IV || |
| ctx->opaque[1] >= LWS_COUNT_COSE_KEY_ELEMENTS) |
| n = (int)lws_lec_printf(ctx, "%.*b", |
| (int)ke->len, ke->buf); |
| else |
| n = (int)lws_lec_printf(ctx, "%.*s", |
| (int)ke->len, ke->buf); |
| |
| switch (n) { |
| case LWS_LECPCTX_RET_AGAIN: |
| ctx->opaque[3] = 1; |
| /* dump what we have and come back */ |
| continue; |
| case LWS_LECPCTX_RET_FAIL: |
| goto fail; |
| case LWS_LECPCTX_RET_FINISHED: |
| break; |
| } |
| } |
| |
| /* move on if we finished that guy */ |
| ctx->opaque[1]++; |
| ctx->opaque[3] = 0; |
| } |
| |
| ctx->used = lws_ptr_diff_size_t(ctx->buf, ctx->start); |
| |
| if (ctx->buf == ctx->end || ctx->scratch_len) |
| return LWS_LECPCTX_RET_AGAIN; |
| |
| ctx->opaque[0] = 0; |
| |
| return LWS_LECPCTX_RET_FINISHED; |
| |
| fail: |
| lwsl_notice("%s: failed\n", __func__); |
| |
| ctx->opaque[0] = 0; |
| |
| return LWS_LECPCTX_RET_FAIL; |
| } |