diff --git a/src/jwe.c b/src/jwe.c index 3cc4f2583..81f837983 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -38,7 +38,7 @@ typedef enum { JWE_ALG_A192KW, JWE_ALG_A256KW, JWE_ALG_DIR, - // JWE_ALG_ECDH_ES, + JWE_ALG_ECDH_ES, // JWE_ALG_ECDH_ES_A128KW, // JWE_ALG_ECDH_ES_A192KW, // JWE_ALG_ECDH_ES_A256KW, @@ -114,6 +114,10 @@ enum jwe_elt { struct jose_fields { struct buffer *tag; struct buffer *iv; + struct buffer *epk; + EVP_PKEY *pubkey; + struct buffer *apu; + struct buffer *apv; }; @@ -137,11 +141,14 @@ static inline int parse_alg_enc(struct buffer *buf, struct alg_enc *array) } /* - * Look for field in JSON and base64url decode its - * content in buffer . - * The field might not be found, it won't be raised as an error. + * Look for field in JSON and if it is found, + * allocate an buffer and base64url decode the field's content in it. + * If is set to 1, an error will be raised if the field is not + * found. + * Returns 0 in case of success, 1 in case of error (decoding or alloc error). */ -static inline int decode_jose_field(struct buffer *decoded_jose, const char *field_name, struct buffer *out) +static inline int decode_jose_field(struct buffer *decoded_jose, const char *field_name, + struct buffer **out, int mandatory) { struct buffer *trash = get_trash_chunk(); int size = 0; @@ -153,20 +160,31 @@ static inline int decode_jose_field(struct buffer *decoded_jose, const char *fie b_orig(trash), b_size(trash)); if (size != -1) { trash->data = size; - size = base64urldec(b_orig(trash), b_data(trash), - b_orig(out), b_size(out)); - if (size < 0) + + *out = alloc_trash_chunk(); + if (!*out) return 1; - out->data = size; - } + + size = base64urldec(b_orig(trash), b_data(trash), + b_orig(*out), b_size(*out)); + if (size < 0) { + free_trash_chunk(*out); + *out = NULL; + return 1; + } + (*out)->data = size; + } else if (mandatory) + return 1; return 0; } +static int build_EC_PKEY_from_buf(struct buffer *jwk, EVP_PKEY **pkey); /* - * Extract the "alg" and "enc" of the JOSE header as well as some algo-specific - * base64url encoded fields. + * Extract multiple base64url encoded fields from the JOSE header that are + * required for the subsequent decrypt operations. Depending on the used + * algorithms the required fields will change. */ static int parse_jose(struct buffer *decoded_jose, int *alg, int *enc, struct jose_fields *jose_fields) { @@ -174,6 +192,9 @@ static int parse_jose(struct buffer *decoded_jose, int *alg, int *enc, struct jo int retval = 0; int size = 0; + int gcm = 0; + int ec = 0; + /* Look for "alg" field */ trash = get_trash_chunk(); size = mjson_get_string(b_orig(decoded_jose), b_data(decoded_jose), "$.alg", @@ -196,13 +217,56 @@ static int parse_jose(struct buffer *decoded_jose, int *alg, int *enc, struct jo if (*enc == JWE_ENC_UNMANAGED) goto end; - /* Look for "tag" field (used by aes gcm encryption) */ - if (decode_jose_field(decoded_jose, "$.tag", jose_fields->tag)) - goto end; + switch (*alg) { + case JWE_ALG_ECDH_ES: + ec = 1; + break; + case JWE_ALG_A128GCMKW: + case JWE_ALG_A192GCMKW: + case JWE_ALG_A256GCMKW: + gcm = 1; + break; + default: break; + } - /* Look for "iv" field (used by aes gcm encryption) */ - if (decode_jose_field(decoded_jose, "$.iv", jose_fields->iv)) - goto end; + + if (gcm) { + /* Look for "tag" field (used by aes gcm encryption) */ + if (decode_jose_field(decoded_jose, "$.tag", &jose_fields->tag, 1)) + goto end; + + /* Look for "iv" field (used by aes gcm encryption) */ + if (decode_jose_field(decoded_jose, "$.iv", &jose_fields->iv, 1)) + goto end; + } + + if (ec) { + /* Look for mandatory "epk" field (used by ecdh encryption) */ + const char *tokptr = NULL; + int toklen = 0; + int token_type = mjson_find(b_orig(decoded_jose), b_data(decoded_jose), "$.epk", + &tokptr, &toklen); + if (token_type != MJSON_TOK_OBJECT) + goto end; + + jose_fields->epk = alloc_trash_chunk(); + if (!jose_fields->epk) + goto end; + + if (!chunk_memcpy(jose_fields->epk, tokptr, toklen)) + goto end; + + if (build_EC_PKEY_from_buf(jose_fields->epk, &jose_fields->pubkey)) + goto end; + + /* Look for optional "apu" field (used by ecdh encryption) */ + if (decode_jose_field(decoded_jose, "$.apu", &jose_fields->apu, 0)) + goto end; + + /* Look for optional "apv" field (used by ecdh encryption) */ + if (decode_jose_field(decoded_jose, "$.apv", &jose_fields->apv, 0)) + goto end; + } retval = 1; @@ -210,6 +274,19 @@ end: return retval; } +static void clear_jose_fields(struct jose_fields *jose_fields) +{ + if (!jose_fields) + return; + + free_trash_chunk(jose_fields->tag); + free_trash_chunk(jose_fields->iv); + free_trash_chunk(jose_fields->epk); + EVP_PKEY_free(jose_fields->pubkey); + free_trash_chunk(jose_fields->apu); + free_trash_chunk(jose_fields->apv); +} + /* * Decrypt Encrypted Key encrypted with AES GCM Key Wrap algorithm and @@ -519,8 +596,6 @@ static int sample_conv_jwt_decrypt_secret(const struct arg *args, struct sample struct buffer **cek = NULL; struct buffer *decrypted_cek = NULL; struct buffer *out = NULL; - struct buffer *alg_tag = NULL; - struct buffer *alg_iv = NULL; int size = 0; jwe_alg alg = JWE_ALG_UNMANAGED; jwe_enc enc = JWE_ENC_UNMANAGED; @@ -537,16 +612,6 @@ static int sample_conv_jwt_decrypt_secret(const struct arg *args, struct sample if (jwt_tokenize(input, items, item_num)) goto end; - alg_tag = alloc_trash_chunk(); - if (!alg_tag) - goto end; - alg_iv = alloc_trash_chunk(); - if (!alg_iv) - goto end; - - fields.tag = alg_tag; - fields.iv = alg_iv; - /* Base64Url decode the JOSE header */ decoded_items[JWE_ELT_JOSE] = alloc_trash_chunk(); if (!decoded_items[JWE_ELT_JOSE]) @@ -617,7 +682,8 @@ static int sample_conv_jwt_decrypt_secret(const struct arg *args, struct sample (*cek)->data = cek_size; if (gcm) { - if (!decrypt_cek_aesgcmkw(*cek, alg_tag, alg_iv, decrypted_cek, &secret_smp.data.u.str, alg)) + if (!decrypt_cek_aesgcmkw(*cek, fields.tag, fields.iv, decrypted_cek, + &secret_smp.data.u.str, alg)) goto end; } else { if (!decrypt_cek_aeskw(*cek, decrypted_cek, &secret_smp.data.u.str, alg)) @@ -645,11 +711,10 @@ static int sample_conv_jwt_decrypt_secret(const struct arg *args, struct sample retval = 1; end: + clear_jose_fields(&fields); free_trash_chunk(input); free_trash_chunk(decrypted_cek); free_trash_chunk(out); - free_trash_chunk(alg_tag); - free_trash_chunk(alg_iv); clear_decoded_items(decoded_items); return retval; } @@ -868,6 +933,7 @@ end: free_trash_chunk(decrypted_cek); free_trash_chunk(out); clear_decoded_items(decoded_items); + clear_jose_fields(&fields); return retval; } @@ -1365,6 +1431,7 @@ static void clear_jwk(struct jwk *jwk) jwk->secret = NULL; break; case JWK_KTY_RSA: + case JWK_KTY_EC: EVP_PKEY_free(jwk->pkey); jwk->pkey = NULL; break; @@ -1497,9 +1564,6 @@ static int sample_conv_jwt_decrypt_jwk(const struct arg *args, struct sample *sm struct buffer *out = NULL; struct jose_fields fields = {}; - struct buffer *alg_tag = NULL; - struct buffer *alg_iv = NULL; - struct buffer *jwk_buf = NULL; struct jwk jwk = {}; @@ -1524,16 +1588,6 @@ static int sample_conv_jwt_decrypt_jwk(const struct arg *args, struct sample *sm if (jwt_tokenize(input, items, item_num)) goto end; - alg_tag = alloc_trash_chunk(); - if (!alg_tag) - goto end; - alg_iv = alloc_trash_chunk(); - if (!alg_iv) - goto end; - - fields.tag = alg_tag; - fields.iv = alg_iv; - /* Base64Url decode the JOSE header */ decoded_items[JWE_ELT_JOSE] = alloc_trash_chunk(); if (!decoded_items[JWE_ELT_JOSE]) @@ -1620,7 +1674,7 @@ static int sample_conv_jwt_decrypt_jwk(const struct arg *args, struct sample *sm goto end; } else { if (gcm) { - if (!decrypt_cek_aesgcmkw(*cek, alg_tag, alg_iv, decrypted_cek, jwk.secret, alg)) + if (!decrypt_cek_aesgcmkw(*cek, fields.tag, fields.iv, decrypted_cek, jwk.secret, alg)) goto end; } else { if (!decrypt_cek_aeskw(*cek, decrypted_cek, jwk.secret, alg)) @@ -1641,12 +1695,11 @@ static int sample_conv_jwt_decrypt_jwk(const struct arg *args, struct sample *sm end: clear_jwk(&jwk); + clear_jose_fields(&fields); free_trash_chunk(jwk_buf); free_trash_chunk(input); free_trash_chunk(decrypted_cek); free_trash_chunk(out); - free_trash_chunk(alg_tag); - free_trash_chunk(alg_iv); clear_decoded_items(decoded_items); return retval; }