From b04398586744c6aeb6e6de8d100afef53ed63ea2 Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Thu, 21 Jul 2016 16:53:47 +0200 Subject: [PATCH] add support for AES key wrap and AES CBC crypto --- include/cjose/header.h | 10 + include/cjose/util.h | 13 ++ src/header.c | 6 + src/jwe.c | 501 ++++++++++++++++++++++++++++++++++++++++- src/jws.c | 13 +- src/util.c | 13 ++ test/check_jwe.c | 181 +++++++++++++++ 7 files changed, 724 insertions(+), 13 deletions(-) diff --git a/include/cjose/header.h b/include/cjose/header.h index 5bfe950..cf38eb2 100644 --- a/include/cjose/header.h +++ b/include/cjose/header.h @@ -40,6 +40,11 @@ extern const char *CJOSE_HDR_KID; /** The JWE algorithm attribute value for RSA-OAEP. */ extern const char *CJOSE_HDR_ALG_RSA_OAEP; +/** The JWE algorithm attribute value for A128KW, A192KW and A256KW. */ +extern const char *CJOSE_HDR_ALG_A128KW; +extern const char *CJOSE_HDR_ALG_A192KW; +extern const char *CJOSE_HDR_ALG_A256KW; + /** The JWS algorithm attribute value for PS256, PS384 and PS512. */ extern const char *CJOSE_HDR_ALG_PS256; extern const char *CJOSE_HDR_ALG_PS384; @@ -66,6 +71,11 @@ extern const char *CJOSE_HDR_ALG_DIR; /** The JWE content encryption algorithm value for A256GCM. */ extern const char *CJOSE_HDR_ENC_A256GCM; +/** The JWE content encryption algorithm value for A128CBC-HS256, A192CBC-HS384 and A256CBC-HS512. */ +extern const char *CJOSE_HDR_ENC_A128CBC_HS256; +extern const char *CJOSE_HDR_ENC_A192CBC_HS384; +extern const char *CJOSE_HDR_ENC_A256CBC_HS512; + /** * An instance of a header object (used when creating JWE/JWS objects). diff --git a/include/cjose/util.h b/include/cjose/util.h index 4749dd6..98b5aa6 100644 --- a/include/cjose/util.h +++ b/include/cjose/util.h @@ -15,6 +15,7 @@ #define CJOSE_UTIL_H #include +#include #ifdef __cplusplus extern "C" @@ -80,6 +81,18 @@ cjose_realloc_fn_t cjose_get_realloc(); */ cjose_dealloc_fn_t cjose_get_dealloc(); +/** + * Compares the first n bytes of the memory areas s1 and s2 in constant time. + * + * \returns an integer less than, equal to, or + * greater than zero if the first n bytes of s1 is found, respectively, to + * be less than, to match, or be greater than the first n bytes of s2 + */ +int cjose_const_memcmp( + const uint8_t *a, + const uint8_t *b, + const size_t size); + #ifdef __cplusplus } #endif diff --git a/src/header.c b/src/header.c index fbfb044..38d6181 100644 --- a/src/header.c +++ b/src/header.c @@ -14,6 +14,9 @@ const char *CJOSE_HDR_ALG = "alg"; const char *CJOSE_HDR_ALG_RSA_OAEP = "RSA-OAEP"; +const char *CJOSE_HDR_ALG_A128KW = "A128KW"; +const char *CJOSE_HDR_ALG_A192KW = "A192KW"; +const char *CJOSE_HDR_ALG_A256KW = "A256KW"; const char *CJOSE_HDR_ALG_DIR = "dir"; const char *CJOSE_HDR_ALG_PS256 = "PS256"; const char *CJOSE_HDR_ALG_PS384 = "PS384"; @@ -30,6 +33,9 @@ const char *CJOSE_HDR_ALG_ES512 = "ES512"; const char *CJOSE_HDR_ENC = "enc"; const char *CJOSE_HDR_ENC_A256GCM = "A256GCM"; +const char *CJOSE_HDR_ENC_A128CBC_HS256 = "A128CBC-HS256"; +const char *CJOSE_HDR_ENC_A192CBC_HS384 = "A192CBC-HS384"; +const char *CJOSE_HDR_ENC_A256CBC_HS512 = "A256CBC-HS512"; const char *CJOSE_HDR_CTY = "cty"; diff --git a/src/jwe.c b/src/jwe.c index 522483d..0a5d6ce 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include "include/header_int.h" #include "include/jwk_int.h" @@ -28,6 +30,11 @@ static bool _cjose_jwe_set_cek_a256gcm( const cjose_jwk_t *jwk, cjose_err *err); +static bool _cjose_jwe_set_cek_aes_cbc( + cjose_jwe_t *jwe, + const cjose_jwk_t *jwk, + cjose_err *err); + static bool _cjose_jwe_encrypt_ek_dir( cjose_jwe_t *jwe, const cjose_jwk_t *jwk, @@ -38,6 +45,16 @@ static bool _cjose_jwe_decrypt_ek_dir( const cjose_jwk_t *jwk, cjose_err *err); +static bool _cjose_jwe_encrypt_ek_aes_kw( + cjose_jwe_t *jwe, + const cjose_jwk_t *jwk, + cjose_err *err); + +static bool _cjose_jwe_decrypt_ek_aes_kw( + cjose_jwe_t *jwe, + const cjose_jwk_t *jwk, + cjose_err *err); + static bool _cjose_jwe_encrypt_ek_rsa_oaep( cjose_jwe_t *jwe, const cjose_jwk_t *jwk, @@ -52,16 +69,29 @@ static bool _cjose_jwe_set_iv_a256gcm( cjose_jwe_t *jwe, cjose_err *err); +static bool _cjose_jwe_set_iv_aes_cbc( + cjose_jwe_t *jwe, + cjose_err *err); + static bool _cjose_jwe_encrypt_dat_a256gcm( cjose_jwe_t *jwe, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err); +static bool _cjose_jwe_encrypt_dat_aes_cbc( + cjose_jwe_t *jwe, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err); + static bool _cjose_jwe_decrypt_dat_a256gcm( cjose_jwe_t *jwe, cjose_err *err); +static bool _cjose_jwe_decrypt_dat_aes_cbc( + cjose_jwe_t *jwe, + cjose_err *err); //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jwe_malloc( @@ -160,6 +190,11 @@ static bool _cjose_jwe_validate_hdr( jwe->fns.encrypt_ek = _cjose_jwe_encrypt_ek_dir; jwe->fns.decrypt_ek = _cjose_jwe_decrypt_ek_dir; } + if ((strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) || (strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) || (strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)) + { + jwe->fns.encrypt_ek = _cjose_jwe_encrypt_ek_aes_kw; + jwe->fns.decrypt_ek = _cjose_jwe_decrypt_ek_aes_kw; + } if (strcmp(enc, CJOSE_HDR_ENC_A256GCM) == 0) { jwe->fns.set_cek = _cjose_jwe_set_cek_a256gcm; @@ -167,6 +202,13 @@ static bool _cjose_jwe_validate_hdr( jwe->fns.encrypt_dat = _cjose_jwe_encrypt_dat_a256gcm; jwe->fns.decrypt_dat = _cjose_jwe_decrypt_dat_a256gcm; } + if ((strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) || (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) || (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0)) + { + jwe->fns.set_cek = _cjose_jwe_set_cek_aes_cbc; + jwe->fns.set_iv = _cjose_jwe_set_iv_aes_cbc; + jwe->fns.encrypt_dat = _cjose_jwe_encrypt_dat_aes_cbc; + jwe->fns.decrypt_dat = _cjose_jwe_decrypt_dat_aes_cbc; + } // ensure required builders have been assigned if (NULL == jwe->fns.set_cek || @@ -228,6 +270,39 @@ static bool _cjose_jwe_set_cek_a256gcm( } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_set_cek_aes_cbc( + cjose_jwe_t *jwe, + const cjose_jwk_t *dummy_set_to_null_for_random, + cjose_err *err) +{ + // make sure we have an enc header + json_t *enc_obj = json_object_get(jwe->hdr, CJOSE_HDR_ENC); + if (NULL == enc_obj) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + const char *enc = json_string_value(enc_obj); + + // determine the CEK key size based on the encryption algorithm + if (strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) + jwe->cek_len = 32; + if (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) + jwe->cek_len = 48; + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + jwe->cek_len = 64; + + // allocate memory for the CEK and fill with random bytes or 0's + cjose_get_dealloc()(jwe->cek); + if (!_cjose_jwe_malloc(jwe->cek_len, dummy_set_to_null_for_random == NULL, &jwe->cek, err)) + { + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jwe_encrypt_ek_dir( cjose_jwe_t *jwe, @@ -259,6 +334,104 @@ static bool _cjose_jwe_decrypt_ek_dir( return _cjose_jwe_set_cek_a256gcm(jwe, jwk, err); } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_encrypt_ek_aes_kw( + cjose_jwe_t *jwe, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + if (NULL == jwe || NULL == jwk) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // jwk must be OCT + if (jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // generate random CEK + if (!jwe->fns.set_cek(jwe, NULL, err)) + { + return false; + } + + // create the AES encryption key from the shared key + AES_KEY akey; + if (AES_set_encrypt_key(jwk->keydata, jwk->keysize, &akey) < 0) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + + // allocate buffer for encrypted CEK (=cek_len + 8) + if (!_cjose_jwe_malloc(jwe->cek_len + 8, false, &jwe->part[1].raw, err)) + { + return false; + } + + // AES wrap the CEK + int len = AES_wrap_key(&akey, NULL, jwe->part[1].raw, jwe->cek, jwe->cek_len); + if (len <= 0) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + jwe->part[1].raw_len = len; + + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_decrypt_ek_aes_kw( + cjose_jwe_t *jwe, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + if (NULL == jwe || NULL == jwk) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // jwk must be OCT + if (jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // create the AES decryption key from the shared key + AES_KEY akey; + if (AES_set_decrypt_key(jwk->keydata, jwk->keysize, &akey) < 0) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + + // generate empty CEK so the the right amount of memory is allocated (abuse JWK parameter to empty) + if (!jwe->fns.set_cek(jwe, (const cjose_jwk_t *) 1, err)) + { + return false; + } + + // AES unwrap the CEK in to jwe->cek + int len = AES_unwrap_key(&akey, (const unsigned char*) NULL, jwe->cek, (const unsigned char *) jwe->part[1].raw, + jwe->part[1].raw_len); + if (len <= 0) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + jwe->cek_len = len; + + return true; +} + //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jwe_encrypt_ek_rsa_oaep( @@ -369,6 +542,45 @@ static bool _cjose_jwe_set_iv_a256gcm( return true; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_set_iv_aes_cbc( + cjose_jwe_t *jwe, + cjose_err *err) +{ + // make sure we have an enc header + json_t *enc_obj = json_object_get(jwe->hdr, CJOSE_HDR_ENC); + if (NULL == enc_obj) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + const char *enc = json_string_value(enc_obj); + + cjose_get_dealloc()(jwe->part[2].raw); + jwe->part[2].raw_len = 0; + + if (strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) + jwe->part[2].raw_len = 16; + if (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) + jwe->part[2].raw_len = 24; + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + jwe->part[2].raw_len = 32; + + if (jwe->part[2].raw_len == 0) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // generate IV as random iv_size * 8 bit value + if (!_cjose_jwe_malloc(jwe->part[2].raw_len, true, &jwe->part[2].raw, err)) + { + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jwe_encrypt_dat_a256gcm( @@ -481,6 +693,196 @@ static bool _cjose_jwe_encrypt_dat_a256gcm( return false; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_calc_auth_tag(const char *enc, cjose_jwe_t *jwe, uint8_t *md, unsigned int *md_len, cjose_err *err) +{ + bool retval = false; + const EVP_MD *hash = NULL; + + if (strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) + hash = EVP_sha256(); + if (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) + hash = EVP_sha384(); + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + hash = EVP_sha512(); + + if (NULL == hash) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + uint8_t *msg = NULL; + + // calculate the Authentication Tag value over AAD + IV + ciphertext + AAD length + + //0 = header + //1 = cek + //2 = iv + //3 = ciphertext + //4 = authentication tag + + // Additional Authentication Data length (base64encoded header) in # of bits in 64 bit length field + uint64_t al = jwe->part[0].b64u_len * 8; + + // concatenate AAD + IV + ciphertext + AAD length field + int msg_len = jwe->part[0].b64u_len + jwe->part[2].raw_len + jwe->part[3].raw_len + sizeof(uint64_t); + if (!_cjose_jwe_malloc(msg_len, false, &msg, err)) + { + goto _cjose_jwe_calc_auth_tag_end; + } + + // construct AAD + IV + ciphertext + AAD input + uint8_t *p = msg; + memcpy(p, jwe->part[0].b64u, jwe->part[0].b64u_len); + p += jwe->part[0].b64u_len; + memcpy(p, jwe->part[2].raw, jwe->part[2].raw_len); + p += jwe->part[2].raw_len; + memcpy(p, jwe->part[3].raw, jwe->part[3].raw_len); + p += jwe->part[3].raw_len; + + // check if we are on a big endian or little endian machine + int c = 1; + if (*(char *) &c == 1) + { + // little endian machine: reverse AAD length for big endian representation + al = (al & 0x00000000FFFFFFFF) << 32 | (al & 0xFFFFFFFF00000000) >> 32; + al = (al & 0x0000FFFF0000FFFF) << 16 | (al & 0xFFFF0000FFFF0000) >> 16; + al = (al & 0x00FF00FF00FF00FF) << 8 | (al & 0xFF00FF00FF00FF00) >> 8; + } + memcpy(p, &al, sizeof(uint64_t)); + + // HMAC the input + if (!HMAC(hash, jwe->cek, jwe->cek_len / 2, msg, msg_len, md, md_len)) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_calc_auth_tag_end; + } + + // use only the first half of the bits + *md_len = *md_len / 2; + retval = true; + + _cjose_jwe_calc_auth_tag_end: + if (msg) + { + cjose_get_dealloc()(msg); + } + return retval; +} + +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_encrypt_dat_aes_cbc( + cjose_jwe_t *jwe, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err) +{ + // make sure we have an enc header + json_t *enc_obj = json_object_get(jwe->hdr, CJOSE_HDR_ENC); + if (NULL == enc_obj) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + const char *enc = json_string_value(enc_obj); + + // get the AES cipher + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher = NULL; + + if (strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) + cipher = EVP_aes_128_cbc(); + if (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) + cipher = EVP_aes_192_cbc(); + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + cipher = EVP_aes_256_cbc(); + + if (NULL == cipher) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + + // instantiate and initialize a new openssl cipher context + ctx = EVP_CIPHER_CTX_new(); + if (NULL == ctx) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + EVP_CIPHER_CTX_init(ctx); + + // initialize context for decryption using the cipher, the 2nd half of the CEK and the IV + if (EVP_EncryptInit_ex(ctx, cipher, NULL, jwe->cek + jwe->cek_len / 2, jwe->part[2].raw) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + + // we need the header in base64url encoding as input for encryption + if ((NULL == jwe->part[0].b64u) + && (!cjose_base64url_encode((const uint8_t *) jwe->part[0].raw, jwe->part[0].raw_len, &jwe->part[0].b64u, + &jwe->part[0].b64u_len, err))) + { + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + + // allocate buffer for the ciphertext (plaintext + block size) + cjose_get_dealloc()(jwe->part[3].raw); + jwe->part[3].raw_len = plaintext_len + cipher->block_size; + if (!_cjose_jwe_malloc(jwe->part[3].raw_len, false, &jwe->part[3].raw, err)) + { + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + + // encrypt entire plaintext to ciphertext buffer + int bytes_encrypted = 0; + if (EVP_EncryptUpdate(ctx, jwe->part[3].raw, &bytes_encrypted, plaintext, plaintext_len) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + jwe->part[3].raw_len = bytes_encrypted; + + // finalize the encryption and set the ciphertext length to correct value + if (EVP_EncryptFinal_ex(ctx, jwe->part[3].raw + bytes_encrypted, &bytes_encrypted) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + jwe->part[3].raw_len += bytes_encrypted; + + // calculate Authentication Tag + unsigned int tag_len = 0; + uint8_t tag[EVP_MAX_MD_SIZE]; + if (_cjose_jwe_calc_auth_tag(enc, jwe, (unsigned char *) &tag, &tag_len, err) == false) + { + return false; + } + + // allocate buffer for the authentication tag + cjose_get_dealloc()(jwe->part[4].raw); + jwe->part[4].raw_len = tag_len; + if (!_cjose_jwe_malloc(jwe->part[4].raw_len, false, &jwe->part[4].raw, err)) + { + goto _cjose_jwe_encrypt_dat_aes_cbc_fail; + } + + memcpy(jwe->part[4].raw, tag, tag_len); + + EVP_CIPHER_CTX_free(ctx); + + return true; + + _cjose_jwe_encrypt_dat_aes_cbc_fail: + if (NULL != ctx) + { + EVP_CIPHER_CTX_free(ctx); + } + return false; +} + //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jwe_decrypt_dat_a256gcm( @@ -551,7 +953,7 @@ static bool _cjose_jwe_decrypt_dat_a256gcm( } jwe->dat_len = bytes_decrypted; - // finalize the encryption + // finalize the decryption if (EVP_DecryptFinal_ex(ctx, NULL, &bytes_decrypted) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); @@ -569,6 +971,103 @@ static bool _cjose_jwe_decrypt_dat_a256gcm( return false; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jwe_decrypt_dat_aes_cbc( + cjose_jwe_t *jwe, + cjose_err *err) +{ + // make sure we have an enc header + json_t *enc_obj = json_object_get(jwe->hdr, CJOSE_HDR_ENC); + if (NULL == enc_obj) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + const char *enc = json_string_value(enc_obj); + + // calculate Authentication Tag + unsigned int tag_len = 0; + uint8_t tag[EVP_MAX_MD_SIZE]; + if (_cjose_jwe_calc_auth_tag(enc, jwe, (unsigned char *) &tag, &tag_len, err) == false) + { + return false; + } + + // compare the provided Authentication Tag against our calculation + if ((tag_len != jwe->part[4].raw_len) || (cjose_const_memcmp(tag, jwe->part[4].raw, tag_len) != 0)) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + + // get the AES cipher + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher = NULL; + + if (strcmp(enc, CJOSE_HDR_ENC_A128CBC_HS256) == 0) + cipher = EVP_aes_128_cbc(); + if (strcmp(enc, CJOSE_HDR_ENC_A192CBC_HS384) == 0) + cipher = EVP_aes_192_cbc(); + if (strcmp(enc, CJOSE_HDR_ENC_A256CBC_HS512) == 0) + cipher = EVP_aes_256_cbc(); + + if (NULL == cipher) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + + // instantiate and initialize a new openssl cipher context + ctx = EVP_CIPHER_CTX_new(); + if (NULL == ctx) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + EVP_CIPHER_CTX_init(ctx); + + // initialize context for decryption using the cipher, the 2nd half of the CEK and the IV + if (EVP_DecryptInit_ex(ctx, cipher, NULL, jwe->cek + jwe->cek_len / 2, jwe->part[2].raw) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + + // allocate buffer for the plaintext + one block padding + int p_len = jwe->part[3].raw_len, f_len = 0; + cjose_get_dealloc()(jwe->dat); + jwe->dat_len = p_len + AES_BLOCK_SIZE; + if (!_cjose_jwe_malloc(jwe->dat_len, false, &jwe->dat, err)) + { + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + + // decrypt ciphertext to plaintext buffer + if (EVP_DecryptUpdate(ctx, jwe->dat, &p_len, jwe->part[3].raw, jwe->part[3].raw_len) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + + // finalize the decryption + if (EVP_DecryptFinal_ex(ctx, jwe->dat + p_len, &f_len) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jwe_decrypt_dat_aes_cbc_fail; + } + jwe->dat_len = p_len + f_len; + + EVP_CIPHER_CTX_free(ctx); + + return true; + + _cjose_jwe_decrypt_dat_aes_cbc_fail: + if (NULL != ctx) + { + EVP_CIPHER_CTX_free(ctx); + } + return false; +} //////////////////////////////////////////////////////////////////////////////// cjose_jwe_t *cjose_jwe_encrypt( diff --git a/src/jws.c b/src/jws.c index 24bbbbb..805efd0 100644 --- a/src/jws.c +++ b/src/jws.c @@ -952,17 +952,6 @@ static bool _cjose_jws_verify_sig_ps( } -//////////////////////////////////////////////////////////////////////////////// -int _const_memcmp(const uint8_t *a, const uint8_t *b, const size_t size) -{ - unsigned char result = 0; - for (size_t i = 0; i < size; i++) { - result |= a[i] ^ b[i]; - } - return result; -} - - //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_verify_sig_rs( cjose_jws_t *jws, @@ -1031,7 +1020,7 @@ static bool _cjose_jws_verify_sig_hmac_sha( } // verify decrypted digest matches computed digest - if ((_const_memcmp(jws->dig, jws->sig, jws->dig_len) != 0) || + if ((cjose_const_memcmp(jws->dig, jws->sig, jws->dig_len) != 0) || (jws->sig_len != jws->dig_len)) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); diff --git a/src/util.c b/src/util.c index 19920b1..7e8ec5f 100644 --- a/src/util.c +++ b/src/util.c @@ -48,3 +48,16 @@ cjose_dealloc_fn_t cjose_get_dealloc() free : _dealloc; } + +int cjose_const_memcmp( + const uint8_t *a, + const uint8_t *b, + const size_t size) +{ + unsigned char result = 0; + for (size_t i = 0; i < size; i++) + { + result |= a[i] ^ b[i]; + } + return result; +} diff --git a/test/check_jwe.c b/test/check_jwe.c index ac60776..4039c68 100644 --- a/test/check_jwe.c +++ b/test/check_jwe.c @@ -175,6 +175,24 @@ static void _self_encrypt_self_decrypt(const char *plain1) CJOSE_HDR_ENC_A256GCM, JWK_OCT, plain1); + + _self_encrypt_self_decrypt_with_key( + CJOSE_HDR_ALG_A128KW, + CJOSE_HDR_ENC_A128CBC_HS256, + JWK_OCT, + plain1); + + _self_encrypt_self_decrypt_with_key( + CJOSE_HDR_ALG_A192KW, + CJOSE_HDR_ENC_A192CBC_HS384, + JWK_OCT, + plain1); + + _self_encrypt_self_decrypt_with_key( + CJOSE_HDR_ALG_A256KW, + CJOSE_HDR_ENC_A256CBC_HS512, + JWK_OCT, + plain1); } @@ -556,6 +574,168 @@ START_TEST(test_cjose_jwe_decrypt_bad_params) END_TEST +START_TEST(test_cjose_jwe_decrypt_aes) +{ + // https://tools.ietf.org/html/rfc7516#appendix-A.3 + // JWE Using AES Key Wrap and AES_128_CBC_HMAC_SHA_256 + static const char *JWK_S = "{\"kty\":\"oct\", \"k\":\"GawgguFyGrWKav7AX4VKUg\"}"; + static const char *JWE_S = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "U0m_YmjN04DJvceFICbCVQ"; + static const char *PLAINTEXT_S = "Live long and prosper."; + + cjose_err err; + + // import the JWK + cjose_jwk_t *jwk = cjose_jwk_import(JWK_S, strlen(JWK_S), &err); + ck_assert_msg(NULL != jwk, "cjose_jwk_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // import the JWE + cjose_jwe_t *jwe = cjose_jwe_import(JWE_S, strlen(JWE_S), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain1_len = 0; + uint8_t *plain1 = cjose_jwe_decrypt(jwe, jwk, &plain1_len, &err); + ck_assert_msg( + NULL != plain1, + "cjose_jwe_get_plaintext failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // confirm plain == PLAINTEXT_S + ck_assert_msg( + plain1_len == strlen(PLAINTEXT_S), + "length of decrypted plaintext does not match length of original, " + "expected: %lu, found: %lu", strlen(PLAINTEXT_S), plain1_len); + ck_assert_msg( + strncmp(PLAINTEXT_S, plain1, plain1_len) == 0, + "decrypted plaintext does not match encrypted plaintext"); + + cjose_get_dealloc()(plain1); + cjose_jwe_release(jwe); + + static const char *JWE_TAMPERED_AT = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "U0m_YmjN04DJvceFICbCVq"; + + // import the JWE + jwe = cjose_jwe_import(JWE_TAMPERED_AT, strlen(JWE_TAMPERED_AT), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain2_len = 0; + uint8_t *plain2 = cjose_jwe_decrypt(jwe, jwk, &plain2_len, &err); + ck_assert_msg( + NULL == plain2, + "cjose_jwe_get_plaintext succeeded for tampered authentication tag"); + + cjose_jwe_release(jwe); + + static const char *JWE_TAMPERED_CIPHERTEXT = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGy." + "U0m_YmjN04DJvceFICbCVQ"; + + // import the JWE + jwe = cjose_jwe_import(JWE_TAMPERED_CIPHERTEXT, strlen(JWE_TAMPERED_CIPHERTEXT), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain3_len = 0; + uint8_t *plain3 = cjose_jwe_decrypt(jwe, jwk, &plain3_len, &err); + ck_assert_msg( + NULL == plain3, + "cjose_jwe_get_plaintext succeeded for tampered ciphertext"); + + cjose_jwe_release(jwe); + + static const char *JWE_TAMPERED_IV = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." + "AxY8DCtDaGlsbGljb3RoZq." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "U0m_YmjN04DJvceFICbCVQ"; + + // import the JWE + jwe = cjose_jwe_import(JWE_TAMPERED_IV, strlen(JWE_TAMPERED_IV), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain4_len = 0; + uint8_t *plain4 = cjose_jwe_decrypt(jwe, jwk, &plain4_len, &err); + ck_assert_msg( + NULL == plain4, + "cjose_jwe_get_plaintext succeeded for tampered IV"); + + cjose_jwe_release(jwe); + + static const char *JWE_TAMPERED_CEK = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOq." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "U0m_YmjN04DJvceFICbCVQ"; + + // import the JWE + jwe = cjose_jwe_import(JWE_TAMPERED_CEK, strlen(JWE_TAMPERED_CEK), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain5_len = 0; + uint8_t *plain5 = cjose_jwe_decrypt(jwe, jwk, &plain5_len, &err); + ck_assert_msg( + NULL == plain5, + "cjose_jwe_get_plaintext succeeded for tampered content encryption key"); + + cjose_jwe_release(jwe); + + static const char *JWE_TAMPERED_HDR = + "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiB9." + "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "U0m_YmjN04DJvceFICbCVQ"; + + // import the JWE + jwe = cjose_jwe_import(JWE_TAMPERED_HDR, strlen(JWE_TAMPERED_HDR), &err); + ck_assert_msg(NULL != jwe, "cjose_jwe_import failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // decrypt the imported JWE + size_t plain6_len = 0; + uint8_t *plain6 = cjose_jwe_decrypt(jwe, jwk, &plain6_len, &err); + ck_assert_msg( + NULL == plain6, + "cjose_jwe_get_plaintext succeeded for tampered header"); + + cjose_jwe_release(jwe); + cjose_jwk_release(jwk); +} +END_TEST + + Suite *cjose_jwe_suite() { Suite *suite = suite_create("jwe"); @@ -567,6 +747,7 @@ Suite *cjose_jwe_suite() tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_empty); tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_large); tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_many); + tcase_add_test(tc_jwe, test_cjose_jwe_decrypt_aes); tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_with_bad_header); tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_with_bad_key); tcase_add_test(tc_jwe, test_cjose_jwe_encrypt_with_bad_content);