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

/*
* 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);
}