mirror of
https://github.com/haproxy/haproxy.git
synced 2026-03-14 06:32:11 -04:00
MINOR: jwt: Parse ec-specific fields in jose header
When the encoding is of the ECDH family, the optional "apu" and "apv" fields of the JOSE header must be parsed, as well as the mandatory "epk" field that contains an EC public key used to derive a key that allows either to decrypt the contents of the token (in case of ECDH-ES) or to decrypt the content encoding key (cek) when using ECDH-ES+AES Key Wrap.
This commit is contained in:
parent
3d9764f4c3
commit
026652a7eb
1 changed files with 102 additions and 49 deletions
151
src/jwe.c
151
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 <field_name> in JSON <decoded_jose> and base64url decode its
|
||||
* content in buffer <out>.
|
||||
* The field might not be found, it won't be raised as an error.
|
||||
* Look for field <field_name> in JSON <decoded_jose> and if it is found,
|
||||
* allocate an <out> buffer and base64url decode the field's content in it.
|
||||
* If <mandatory> 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 <cek> 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue