You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
20 KiB
617 lines
20 KiB
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "private-lib-core.h"
|
|
#include "private-lib-jose-jwe.h"
|
|
|
|
/*
|
|
* From RFC7518 JWA
|
|
*
|
|
* 4.6. Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static
|
|
* (ECDH-ES)
|
|
*
|
|
* This section defines the specifics of key agreement with Elliptic
|
|
* Curve Diffie-Hellman Ephemeral Static [RFC6090], in combination with
|
|
* the Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A]. The
|
|
* key agreement result can be used in one of two ways:
|
|
*
|
|
* 1. directly as the Content Encryption Key (CEK) for the "enc"
|
|
* algorithm, in the Direct Key Agreement mode, or
|
|
*
|
|
* 2. as a symmetric key used to wrap the CEK with the "A128KW",
|
|
* "A192KW", or "A256KW" algorithms, in the Key Agreement with Key
|
|
* Wrapping mode.
|
|
*
|
|
* A new ephemeral public key value MUST be generated for each key
|
|
* agreement operation.
|
|
*
|
|
* In Direct Key Agreement mode, the output of the Concat KDF MUST be a
|
|
* key of the same length as that used by the "enc" algorithm. In this
|
|
* case, the empty octet sequence is used as the JWE Encrypted Key
|
|
* value. The "alg" (algorithm) Header Parameter value "ECDH-ES" is
|
|
* used in the Direct Key Agreement mode.
|
|
*
|
|
* In Key Agreement with Key Wrapping mode, the output of the Concat KDF
|
|
* MUST be a key of the length needed for the specified key wrapping
|
|
* algorithm. In this case, the JWE Encrypted Key is the CEK wrapped
|
|
* with the agreed-upon key.
|
|
*
|
|
* The following "alg" (algorithm) Header Parameter values are used to
|
|
* indicate that the JWE Encrypted Key is the result of encrypting the
|
|
* CEK using the result of the key agreement algorithm as the key
|
|
* encryption key for the corresponding key wrapping algorithm:
|
|
*
|
|
* +-----------------+-------------------------------------------------+
|
|
* | "alg" Param | Key Management Algorithm |
|
|
* | Value | |
|
|
* +-----------------+-------------------------------------------------+
|
|
* | ECDH-ES+A128KW | ECDH-ES using Concat KDF and CEK wrapped with |
|
|
* | | "A128KW" |
|
|
* | ECDH-ES+A192KW | ECDH-ES using Concat KDF and CEK wrapped with |
|
|
* | | "A192KW" |
|
|
* | ECDH-ES+A256KW | ECDH-ES using Concat KDF and CEK wrapped with |
|
|
* | | "A256KW" |
|
|
* +-----------------+-------------------------------------------------+
|
|
*
|
|
* 4.6.1. Header Parameters Used for ECDH Key Agreement
|
|
*
|
|
* The following Header Parameter names are used for key agreement as
|
|
* defined below.
|
|
*
|
|
* 4.6.1.1. "epk" (Ephemeral Public Key) Header Parameter
|
|
*
|
|
* The "epk" (ephemeral public key) value created by the originator for
|
|
* the use in key agreement algorithms. This key is represented as a
|
|
* JSON Web Key [JWK] public key value. It MUST contain only public key
|
|
* parameters and SHOULD contain only the minimum JWK parameters
|
|
* necessary to represent the key; other JWK parameters included can be
|
|
* checked for consistency and honored, or they can be ignored. This
|
|
* Header Parameter MUST be present and MUST be understood and processed
|
|
* by implementations when these algorithms are used.
|
|
*
|
|
* 4.6.1.2. "apu" (Agreement PartyUInfo) Header Parameter
|
|
*
|
|
* The "apu" (agreement PartyUInfo) value for key agreement algorithms
|
|
* using it (such as "ECDH-ES"), represented as a base64url-encoded
|
|
* string. When used, the PartyUInfo value contains information about
|
|
* the producer. Use of this Header Parameter is OPTIONAL. This Header
|
|
* Parameter MUST be understood and processed by implementations when
|
|
* these algorithms are used.
|
|
*
|
|
* 4.6.1.3. "apv" (Agreement PartyVInfo) Header Parameter
|
|
*
|
|
* The "apv" (agreement PartyVInfo) value for key agreement algorithms
|
|
* using it (such as "ECDH-ES"), represented as a base64url encoded
|
|
* string. When used, the PartyVInfo value contains information about
|
|
* the recipient. Use of this Header Parameter is OPTIONAL. This
|
|
* Header Parameter MUST be understood and processed by implementations
|
|
* when these algorithms are used.
|
|
*
|
|
* 4.6.2. Key Derivation for ECDH Key Agreement
|
|
*
|
|
* The key derivation process derives the agreed-upon key from the
|
|
* shared secret Z established through the ECDH algorithm, per
|
|
* Section 6.2.2.2 of [NIST.800-56A].
|
|
*
|
|
* Key derivation is performed using the Concat KDF, as defined in
|
|
* Section 5.8.1 of [NIST.800-56A], where the Digest Method is SHA-256.
|
|
* The Concat KDF parameters are set as follows:
|
|
*
|
|
* Z
|
|
* This is set to the representation of the shared secret Z as an
|
|
* octet sequence.
|
|
*
|
|
* keydatalen
|
|
* This is set to the number of bits in the desired output key. For
|
|
* "ECDH-ES", this is length of the key used by the "enc" algorithm.
|
|
* For "ECDH-ES+A128KW", "ECDH-ES+A192KW", and "ECDH-ES+A256KW", this
|
|
* is 128, 192, and 256, respectively.
|
|
*
|
|
* AlgorithmID
|
|
* The AlgorithmID value is of the form Datalen || Data, where Data
|
|
* is a variable-length string of zero or more octets, and Datalen is
|
|
* a fixed-length, big-endian 32-bit counter that indicates the
|
|
* length (in octets) of Data. In the Direct Key Agreement case,
|
|
* Data is set to the octets of the ASCII representation of the "enc"
|
|
* Header Parameter value. In the Key Agreement with Key Wrapping
|
|
* case, Data is set to the octets of the ASCII representation of the
|
|
* "alg" (algorithm) Header Parameter value.
|
|
*
|
|
* PartyUInfo
|
|
* The PartyUInfo value is of the form Datalen || Data, where Data is
|
|
* a variable-length string of zero or more octets, and Datalen is a
|
|
* fixed-length, big-endian 32-bit counter that indicates the length
|
|
* (in octets) of Data. If an "apu" (agreement PartyUInfo) Header
|
|
* Parameter is present, Data is set to the result of base64url
|
|
* decoding the "apu" value and Datalen is set to the number of
|
|
* octets in Data. Otherwise, Datalen is set to 0 and Data is set to
|
|
* the empty octet sequence.
|
|
*
|
|
* PartyVInfo
|
|
* The PartyVInfo value is of the form Datalen || Data, where Data is
|
|
* a variable-length string of zero or more octets, and Datalen is a
|
|
* fixed-length, big-endian 32-bit counter that indicates the length
|
|
* (in octets) of Data. If an "apv" (agreement PartyVInfo) Header
|
|
* Parameter is present, Data is set to the result of base64url
|
|
* decoding the "apv" value and Datalen is set to the number of
|
|
* octets in Data. Otherwise, Datalen is set to 0 and Data is set to
|
|
* the empty octet sequence.
|
|
*
|
|
* SuppPubInfo
|
|
* This is set to the keydatalen represented as a 32-bit big-endian
|
|
* integer.
|
|
*
|
|
* SuppPrivInfo
|
|
* This is set to the empty octet sequence.
|
|
*
|
|
* Applications need to specify how the "apu" and "apv" Header
|
|
* Parameters are used for that application. The "apu" and "apv" values
|
|
* MUST be distinct, when used. Applications wishing to conform to
|
|
* [NIST.800-56A] need to provide values that meet the requirements of
|
|
* that document, e.g., by using values that identify the producer and
|
|
* consumer. Alternatively, applications MAY conduct key derivation in
|
|
* a manner similar to "Diffie-Hellman Key Agreement Method" [RFC2631]:
|
|
* in that case, the "apu" parameter MAY either be omitted or represent
|
|
* a random 512-bit value (analogous to PartyAInfo in Ephemeral-Static
|
|
* mode in RFC 2631) and the "apv" parameter SHOULD NOT be present.
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* - ECDH-ES[-variant] comes in the jose "alg" and just covers key agreement.
|
|
* The "enc" action is completely separate and handled elsewhere. However
|
|
* the key size throughout is determined by the needs of the "enc" action.
|
|
*
|
|
* - The jwe->jws.jwk is the PEER - the encryption consumer's - public key.
|
|
*
|
|
* - The public part of the ephemeral key comes out in jose.jwk_ephemeral
|
|
*
|
|
* - Return shared secret length or < 0 for error
|
|
*
|
|
* - Unwrapped CEK in EKEY. If any, wrapped CEK in "wrapped".
|
|
*
|
|
* - Caller responsibility to cleanse EKEY.
|
|
*/
|
|
|
|
static int
|
|
lws_jwe_encrypt_ecdh(struct lws_jwe *jwe, char *temp, int *temp_len,
|
|
uint8_t *cek)
|
|
{
|
|
uint8_t shared_secret[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES],
|
|
derived[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
|
|
int m, n, ret = -1, ot = *temp_len, ss_len = sizeof(shared_secret),
|
|
// kw_hlen = lws_genhash_size(jwe->jose.alg->hash_type),
|
|
enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type),
|
|
ekbytes = 32; //jwe->jose.alg->keybits_fixed / 8;
|
|
struct lws_genec_ctx ecctx;
|
|
struct lws_jwk *ephem = &jwe->jose.recipient[jwe->recip].jwk_ephemeral;
|
|
|
|
if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_EC) {
|
|
lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
|
|
|
|
return -1;
|
|
}
|
|
|
|
ephem->kty = LWS_GENCRYPTO_KTY_EC;
|
|
ephem->private_key = 1;
|
|
|
|
/* Generate jose.jwk_ephemeral on the peer public key curve */
|
|
|
|
if (lws_genecdh_create(&ecctx, jwe->jws.context, NULL))
|
|
goto bail;
|
|
|
|
/* ephemeral context gets random key on same curve as recip pubkey */
|
|
if (lws_genecdh_new_keypair(&ecctx, LDHS_OURS, (const char *)
|
|
jwe->jws.jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
|
|
ephem->e))
|
|
goto bail;
|
|
|
|
/* peer context gets js->jwk key */
|
|
if (lws_genecdh_set_key(&ecctx, jwe->jws.jwk->e, LDHS_THEIRS)) {
|
|
lwsl_err("%s: setting peer pubkey failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* combine our ephemeral key and the peer pubkey to get the secret */
|
|
|
|
if (lws_genecdh_compute_shared_secret(&ecctx, shared_secret, &ss_len)) {
|
|
lwsl_notice("%s: lws_genecdh_compute_shared_secret failed\n",
|
|
__func__);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* The private part of the ephemeral key is finished with...
|
|
* cleanse and free it. We need to keep the public part around so we
|
|
* can publish it with the JWE as "epk".
|
|
*/
|
|
|
|
lws_explicit_bzero(ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].buf,
|
|
ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].len);
|
|
lws_free_set_NULL(ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].buf);
|
|
ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].len = 0;
|
|
ephem->private_key = 0;
|
|
|
|
/*
|
|
* Derive the CEK from the shared secret... amount of bytes written to
|
|
* derived matches bitcount in jwe->jose.enc_alg->keybits_fixed
|
|
*
|
|
* In Direct Key Agreement mode, the output of the Concat KDF MUST be a
|
|
* key of the same length as that used by the "enc" algorithm.
|
|
*/
|
|
|
|
if (lws_jwa_concat_kdf(jwe,
|
|
jwe->jose.alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE,
|
|
derived, shared_secret, ss_len)) {
|
|
lwsl_notice("%s: lws_jwa_concat_kdf failed\n", __func__);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
/* in P-521 case, we get a 66-byte shared secret for a 64-byte key */
|
|
if (ss_len < enc_hlen) {
|
|
lwsl_err("%s: concat KDF bad derived key len %d\n", __func__,
|
|
ss_len);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* For "ECDH-ES", that was it, and we use what we just wrapped in
|
|
* wrapped as the CEK without publishing it.
|
|
*
|
|
* For "ECDH-ES-AES[128,192,256]KW", we generate a new, random CEK and
|
|
* then wrap it using the key we just wrapped, and make the wrapped
|
|
* version available in EKEY.
|
|
*/
|
|
|
|
if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
|
|
struct lws_gencrypto_keyelem el;
|
|
struct lws_genaes_ctx aesctx;
|
|
|
|
/* generate the actual CEK in cek */
|
|
|
|
if (lws_get_random(jwe->jws.context, cek, enc_hlen) !=
|
|
(size_t)enc_hlen) {
|
|
lwsl_err("Problem getting random\n");
|
|
goto bail;
|
|
}
|
|
|
|
/* wrap with the derived key */
|
|
|
|
el.buf = derived;
|
|
el.len = enc_hlen / 2;
|
|
|
|
if (lws_genaes_create(&aesctx, LWS_GAESO_ENC, LWS_GAESM_KW, &el,
|
|
1, NULL)) {
|
|
|
|
lwsl_notice("%s: lws_genaes_create\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* wrap CEK into EKEY */
|
|
|
|
n = lws_genaes_crypt(&aesctx, cek, enc_hlen,
|
|
(void *)jwe->jws.map.buf[LJWE_EKEY],
|
|
NULL, NULL, NULL, 0);
|
|
m = lws_genaes_destroy(&aesctx, NULL, 0);
|
|
if (n < 0) {
|
|
lwsl_err("%s: encrypt cek fail\n", __func__);
|
|
goto bail;
|
|
}
|
|
if (m < 0) {
|
|
lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
jwe->jws.map.len[LJWE_EKEY] = enc_hlen + 8;
|
|
|
|
/* Wrapped CEK is in EKEY. Random CEK is in cek. */
|
|
|
|
} else /* direct derived CEK is in cek */
|
|
memcpy(cek, derived, enc_hlen);
|
|
|
|
/* rewrite the protected JOSE header to have the epk pieces */
|
|
|
|
jwe->jws.map.buf[LJWE_JOSE] = temp;
|
|
|
|
m = n = lws_snprintf(temp, *temp_len,
|
|
"{\"alg\":\"%s\", \"enc\":\"%s\", \"epk\":",
|
|
jwe->jose.alg->alg, jwe->jose.enc_alg->alg);
|
|
*temp_len -= n;
|
|
|
|
n = lws_jwk_export(ephem, 0, temp + (ot - *temp_len), temp_len);
|
|
if (n < 0) {
|
|
lwsl_err("%s: ephemeral export failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
m += n;
|
|
|
|
n = lws_snprintf(temp + (ot - *temp_len), *temp_len, "}");
|
|
*temp_len -= n + 1;
|
|
m += n;
|
|
jwe->jws.map.len[LJWE_JOSE] = m;
|
|
|
|
/* create a b64 version of the JOSE header, needed later for AAD */
|
|
|
|
if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
|
|
temp + (ot - *temp_len), temp_len,
|
|
jwe->jws.map.buf[LJWE_JOSE],
|
|
jwe->jws.map.len[LJWE_JOSE]))
|
|
return -1;
|
|
|
|
ret = enc_hlen;
|
|
|
|
bail:
|
|
lws_genec_destroy(&ecctx);
|
|
|
|
/* cleanse the shared secret (watch out for cek at parent too) */
|
|
lws_explicit_bzero(shared_secret, ekbytes);
|
|
lws_explicit_bzero(derived, ekbytes);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
lws_jwe_encrypt_ecdh_cbc_hs(struct lws_jwe *jwe, char *temp, int *temp_len)
|
|
{
|
|
int ss_len, // kw_hlen = lws_genhash_size(jwe->jose.alg->hash_type),
|
|
enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
|
|
uint8_t cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
|
|
int ekbytes = jwe->jose.alg->keybits_fixed / 8;
|
|
int n, ot = *temp_len, ret = -1;
|
|
|
|
/* if we will produce an EKEY, make space for it */
|
|
|
|
if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
|
|
if (lws_jws_alloc_element(&jwe->jws.map, LJWE_EKEY,
|
|
temp + (ot - *temp_len), temp_len,
|
|
enc_hlen + 8, 0))
|
|
goto bail;
|
|
}
|
|
|
|
/* decrypt the CEK */
|
|
|
|
ss_len = lws_jwe_encrypt_ecdh(jwe, temp + (ot - *temp_len), temp_len, cek);
|
|
if (ss_len < 0) {
|
|
lwsl_err("%s: lws_jwe_encrypt_ecdh failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/* cek contains the unwrapped CEK. EKEY may contain wrapped CEK */
|
|
|
|
/* make space for the payload encryption pieces */
|
|
|
|
if (lws_jws_alloc_element(&jwe->jws.map, LJWE_ATAG,
|
|
temp + (ot - *temp_len),
|
|
temp_len, enc_hlen / 2, 0))
|
|
goto bail;
|
|
|
|
if (lws_jws_alloc_element(&jwe->jws.map, LJWE_IV,
|
|
temp + (ot - *temp_len),
|
|
temp_len, LWS_JWE_AES_IV_BYTES, 0))
|
|
goto bail;
|
|
|
|
/* Perform the authenticated encryption on CTXT...
|
|
* ...the AAD is b64u(protected JOSE header) */
|
|
|
|
n = lws_jwe_encrypt_cbc_hs(jwe, cek,
|
|
(uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
|
|
jwe->jws.map_b64.len[LJWE_JOSE]);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: lws_jwe_encrypt_cbc_hs failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
bail:
|
|
/* if fail or direct CEK, cleanse and remove EKEY */
|
|
if (ret || jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE) {
|
|
if (jwe->jws.map.len[LJWE_EKEY])
|
|
lws_explicit_bzero((void *)jwe->jws.map.buf[LJWE_EKEY],
|
|
jwe->jws.map.len[LJWE_EKEY]);
|
|
jwe->jws.map.len[LJWE_EKEY] = 0;
|
|
}
|
|
|
|
lws_explicit_bzero(cek, ekbytes);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* jwe->jws.jwk is recipient private key
|
|
*
|
|
* If kw mode, then EKEY is the wrapped CEK
|
|
*
|
|
*
|
|
*/
|
|
|
|
static int
|
|
lws_jwe_auth_and_decrypt_ecdh(struct lws_jwe *jwe)
|
|
{
|
|
uint8_t shared_secret[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES],
|
|
derived[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
|
|
int ekbytes = jwe->jose.enc_alg->keybits_fixed / 8,
|
|
enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
|
|
struct lws_genec_ctx ecctx;
|
|
int n, ret = -1, ss_len = sizeof(shared_secret);
|
|
|
|
if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_EC) {
|
|
lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (jwe->jose.recipient[jwe->recip].jwk_ephemeral.kty !=
|
|
LWS_GENCRYPTO_KTY_EC) {
|
|
lwsl_err("%s: missing epk\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Recompute the shared secret...
|
|
*
|
|
* - direct: it's the CEK
|
|
*
|
|
* - aeskw: apply it as AES keywrap to EKEY to get the CEK
|
|
*/
|
|
|
|
/* Generate jose.jwk_ephemeral on the peer public key curve */
|
|
|
|
if (lws_genecdh_create(&ecctx, jwe->jws.context, NULL))
|
|
goto bail;
|
|
|
|
/* Load our private key into our side of the ecdh context */
|
|
|
|
if (lws_genecdh_set_key(&ecctx, jwe->jws.jwk->e, LDHS_OURS)) {
|
|
lwsl_err("%s: setting our private key failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* Import the ephemeral public key into the peer side */
|
|
if (lws_genecdh_set_key(&ecctx,
|
|
jwe->jose.recipient[jwe->recip].jwk_ephemeral.e,
|
|
LDHS_THEIRS)) {
|
|
lwsl_err("%s: setting epk pubkey failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* combine their ephemeral key and our private key to get the secret */
|
|
|
|
if (lws_genecdh_compute_shared_secret(&ecctx, shared_secret, &ss_len)) {
|
|
lwsl_notice("%s: lws_genecdh_compute_shared_secret failed\n",
|
|
__func__);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
lws_genec_destroy(&ecctx);
|
|
|
|
if (ss_len < enc_hlen) {
|
|
lwsl_err("%s: ss_len %d ekbytes %d\n", __func__, ss_len, enc_hlen);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Derive the CEK from the shared secret... amount of bytes written to
|
|
* cek[] matches bitcount in jwe->jose.enc_alg->keybits_fixed
|
|
*/
|
|
|
|
if (lws_jwa_concat_kdf(jwe,
|
|
jwe->jose.alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE,
|
|
derived, shared_secret, ss_len)) {
|
|
lwsl_notice("%s: lws_jwa_concat_kdf failed\n", __func__);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* "ECDH-ES": derived is the CEK
|
|
* "ECDH-ES-AES[128,192,256]KW": wrapped key is in EKEY,
|
|
* "derived" contains KEK
|
|
*/
|
|
|
|
if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
|
|
struct lws_gencrypto_keyelem el;
|
|
struct lws_genaes_ctx aesctx;
|
|
int m;
|
|
|
|
/* Confirm space for EKEY */
|
|
|
|
if (jwe->jws.map.len[LJWE_EKEY] < (unsigned int)enc_hlen) {
|
|
lwsl_err("%s: missing EKEY\n", __func__);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
/* unwrap with the KEK we derived */
|
|
|
|
el.buf = derived;
|
|
el.len = enc_hlen / 2;
|
|
|
|
if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_KW,
|
|
&el, 1, NULL)) {
|
|
|
|
lwsl_notice("%s: lws_genaes_create\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* decrypt the EKEY to end up with CEK in "shared_secret" */
|
|
|
|
n = lws_genaes_crypt(&aesctx,
|
|
(const uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
|
|
jwe->jws.map.len[LJWE_EKEY],
|
|
(uint8_t *)shared_secret,
|
|
NULL, NULL, NULL, 0);
|
|
m = lws_genaes_destroy(&aesctx, NULL, 0);
|
|
if (n < 0) {
|
|
lwsl_err("%s: decrypt cek fail\n", __func__);
|
|
goto bail;
|
|
}
|
|
if (m < 0) {
|
|
lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
|
|
goto bail;
|
|
}
|
|
} else
|
|
memcpy(shared_secret, derived, enc_hlen);
|
|
|
|
/* either way, the recovered CEK is in shared_secret */
|
|
|
|
if (lws_jwe_auth_and_decrypt_cbc_hs(jwe, shared_secret,
|
|
(uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
|
|
jwe->jws.map_b64.len[LJWE_JOSE]) < 0) {
|
|
lwsl_err("%s: lws_jwe_auth_and_decrypt_cbc_hs fail\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
/* if all went well, then CTXT is now the plaintext */
|
|
ret = 0;
|
|
|
|
bail:
|
|
/* cleanse wrapped on stack that contained the CEK / wrapped key */
|
|
lws_explicit_bzero(derived, ekbytes);
|
|
/* cleanse the shared secret */
|
|
lws_explicit_bzero(shared_secret, ekbytes);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
lws_jwe_auth_and_decrypt_ecdh_cbc_hs(struct lws_jwe *jwe,
|
|
char *temp, int *temp_len)
|
|
{
|
|
/* create a b64 version of the JOSE header, needed later for AAD */
|
|
|
|
if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
|
|
temp, temp_len,
|
|
jwe->jws.map.buf[LJWE_JOSE],
|
|
jwe->jws.map.len[LJWE_JOSE]))
|
|
return -1;
|
|
|
|
return lws_jwe_auth_and_decrypt_ecdh(jwe);
|
|
}
|