libwebsockets/lib/jose/jwe/jwe-ecdh-es-aeskw.c
Andy Green c9731c5f17 type comparisons: fixes
This is a huge patch that should be a global NOP.

For unix type platforms it enables -Wconversion to issue warnings (-> error)
for all automatic casts that seem less than ideal but are normally concealed
by the toolchain.

This is things like passing an int to a size_t argument.  Once enabled, I
went through all args on my default build (which build most things) and
tried to make the removed default cast explicit.

With that approach it neither change nor bloat the code, since it compiles
to whatever it was doing before, just with the casts made explicit... in a
few cases I changed some length args from int to size_t but largely left
the causes alone.

From now on, new code that is relying on less than ideal casting
will complain and nudge me to improve it by warnings.
2021-01-05 10:56:38 +00:00

617 lines
20 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2020 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 = (int)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, (unsigned int)enc_hlen) !=
(size_t)enc_hlen) {
lwsl_err("Problem getting random\n");
goto bail;
}
/* wrap with the derived key */
el.buf = derived;
el.len = (unsigned int)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, (unsigned int)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] = (unsigned int)enc_hlen + 8;
/* Wrapped CEK is in EKEY. Random CEK is in cek. */
} else /* direct derived CEK is in cek */
memcpy(cek, derived, (unsigned int)enc_hlen);
/* rewrite the protected JOSE header to have the epk pieces */
jwe->jws.map.buf[LJWE_JOSE] = temp;
m = n = lws_snprintf(temp, (size_t)*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), (size_t)*temp_len, "}");
*temp_len -= n + 1;
m += n;
jwe->jws.map.len[LJWE_JOSE] = (unsigned int)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, (unsigned int)ekbytes);
lws_explicit_bzero(derived, (unsigned int)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 = (int)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,
(unsigned int)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, (unsigned int)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],
(int)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, (unsigned int)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 = (int)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 = (unsigned int)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, (unsigned int)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],
(int)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, (unsigned int)ekbytes);
/* cleanse the shared secret */
lws_explicit_bzero(shared_secret, (unsigned int)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);
}