Skip to content
6 changes: 6 additions & 0 deletions src/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ static inline bool _decode(const char *input, size_t inlen, uint8_t **output, si

// rlen takes a best guess on size;
// might be too large for base64url, but never too small.
if (inlen > SIZE_MAX / 3)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

size_t rlen = ((inlen * 3) >> 2) + 3;
uint8_t *buffer = cjose_get_alloc()(sizeof(uint8_t) * rlen);
if (NULL == buffer)
Expand Down
3 changes: 2 additions & 1 deletion src/concatkdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "include/concatkdf_int.h"
#include "include/util_int.h"

#ifdef _WIN32
#include <Winsock2.h>
Expand Down Expand Up @@ -149,7 +150,7 @@ uint8_t *cjose_concatkdf_derive(const size_t keylen,

concatkdf_derive_finish:
EVP_MD_CTX_destroy(ctx);
cjose_get_dealloc()(buffer);
_cjose_cleanse_dealloc(buffer, keylen);

return derived;
}
54 changes: 54 additions & 0 deletions src/header.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include <stdlib.h>
#include <string.h>
#include <jansson.h>
#include "cjose/header.h"
#include "include/header_int.h"
Expand Down Expand Up @@ -49,6 +50,59 @@ const char *CJOSE_HDR_EPK = "epk";
const char *CJOSE_HDR_APU = "apu";
const char *CJOSE_HDR_APV = "apv";

static const char *CJOSE_HDR_CRIT = "crit";

////////////////////////////////////////////////////////////////////////////////
bool _cjose_header_validate_crit(cjose_header_t *header, const char *const *supported, size_t supported_len, cjose_err *err)
{
if (NULL == header)
{
return true;
}

json_t *crit = json_object_get((json_t *)header, CJOSE_HDR_CRIT);
if (NULL == crit)
{
return true;
}

if (!json_is_array(crit))
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

size_t index = 0;
json_t *entry = NULL;
json_array_foreach(crit, index, entry)
{
if (!json_is_string(entry))
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

const char *name = json_string_value(entry);
bool found = false;
for (size_t i = 0; i < supported_len; i++)
{
if (0 == strcmp(name, supported[i]))
{
found = true;
break;
}
}

if (!found)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}
}

return true;
}

////////////////////////////////////////////////////////////////////////////////
cjose_header_t *cjose_header_new(cjose_err *err)
{
Expand Down
6 changes: 5 additions & 1 deletion src/include/header_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
#ifndef SRC_HEADER_INT_H
#define SRC_HEADER_INT_H

// extern const char *CJOSE_HDR_ATTRS[];
#include <stddef.h>

#include "cjose/header.h"

bool _cjose_header_validate_crit(cjose_header_t *header, const char *const *supported, size_t supported_len, cjose_err *err);

#endif // SRC_HEADER_INT_H
3 changes: 3 additions & 0 deletions src/include/util_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ void *cjose_alloc_wrapped(size_t n);
void *cjose_realloc_wrapped(void *p, size_t n);
void cjose_dealloc_wrapped(void *p);

void _cjose_cleanse(void *ptr, size_t len);
void _cjose_cleanse_dealloc(void *ptr, size_t len);

#endif // SRC_UTIL_INT_H
121 changes: 113 additions & 8 deletions src/jwe.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
Expand Down Expand Up @@ -69,6 +71,12 @@ static bool _cjose_jwe_decrypt_dat_aes_gcm(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_validate_decrypt_key(_jwe_int_recipient_t *recipient,
cjose_header_t *protected_header,
cjose_header_t *shared_header,
const cjose_jwk_t *jwk,
cjose_err *err);

static void _cjose_release_cek(uint8_t **cek, size_t cek_len)
{

Expand All @@ -77,8 +85,7 @@ static void _cjose_release_cek(uint8_t **cek, size_t cek_len)
return;
}

memset(*cek, 0, cek_len);
cjose_get_dealloc()(*cek);
_cjose_cleanse_dealloc(*cek, cek_len);
*cek = 0;
}

Expand Down Expand Up @@ -313,6 +320,24 @@ static bool _cjose_jwe_validate_alg(cjose_header_t *protected_header,
_jwe_int_recipient_t *recipient,
cjose_err *err)
{
static const char *const supported_crit_headers[] = {
"alg",
"enc",
"cty",
"epk",
"apu",
"apv"
};

if (!_cjose_header_validate_crit(protected_header, supported_crit_headers,
sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err)
|| !_cjose_header_validate_crit(unprotected_header, supported_crit_headers,
sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err)
|| !_cjose_header_validate_crit((cjose_header_t *)recipient->unprotected, supported_crit_headers,
sizeof(supported_crit_headers) / sizeof(supported_crit_headers[0]), err))
{
return false;
}

const char *alg = _cjose_jwe_get_from_headers(protected_header, unprotected_header, (cjose_header_t *)recipient->unprotected,
CJOSE_HDR_ALG);
Expand Down Expand Up @@ -825,7 +850,7 @@ static bool _cjose_jwe_encrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose

cjose_jwk_release(epk_jwk);
cjose_get_dealloc()(epk_json);
cjose_get_dealloc()(secret);
_cjose_cleanse_dealloc(secret, secret_len);
cjose_get_dealloc()(otherinfo);

return result;
Expand Down Expand Up @@ -860,6 +885,12 @@ static bool _cjose_jwe_decrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose
goto cjose_decrypt_ek_ecdh_es_finish;
}

if (cjose_jwk_EC_get_curve(jwk, err) != cjose_jwk_EC_get_curve(epk_jwk, err))
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
goto cjose_decrypt_ek_ecdh_es_finish;
}

// perform ECDH (private=jwk, public=epk_jwk)
if (!cjose_jwk_derive_ecdh_bits(jwk, epk_jwk, &secret, &secret_len, err))
{
Expand Down Expand Up @@ -904,7 +935,7 @@ static bool _cjose_jwe_decrypt_ek_ecdh_es(_jwe_int_recipient_t *recipient, cjose

cjose_jwk_release(epk_jwk);
cjose_get_dealloc()(epk_json);
cjose_get_dealloc()(secret);
_cjose_cleanse_dealloc(secret, secret_len);
cjose_get_dealloc()(otherinfo);

return result;
Expand Down Expand Up @@ -944,7 +975,7 @@ static bool _cjose_jwe_set_iv_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
// And in the example in A.2.4 (https://tools.ietf.org/html/rfc7516#appendix-A.2.4)
// they provide an example for AES128-CBC, which results (naturally) in the IV size of 128Bit.
//
// The CISCO implementation chooses for the size of the IV the key size of the
// The CISCO implementation chose for the size of the IV the key size of the
// cipher algorithm, which seems to be wrong.
//
// According to RFC 3602 section 3 (https://tools.ietf.org/html/rfc3602#section-3):
Expand Down Expand Up @@ -1128,7 +1159,16 @@ static bool _cjose_jwe_calc_auth_tag(const char *enc, cjose_jwe_t *jwe, uint8_t
uint64_t al = jwe->enc_header.b64u_len * 8;

// concatenate AAD + IV + ciphertext + AAD length field
int msg_len = jwe->enc_header.b64u_len + jwe->enc_iv.raw_len + jwe->enc_ct.raw_len + sizeof(uint64_t);
size_t msg_len = jwe->enc_header.b64u_len;
if (msg_len > SIZE_MAX - jwe->enc_iv.raw_len || msg_len + jwe->enc_iv.raw_len > SIZE_MAX - jwe->enc_ct.raw_len
|| msg_len + jwe->enc_iv.raw_len + jwe->enc_ct.raw_len > SIZE_MAX - sizeof(uint64_t))
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
goto _cjose_jwe_calc_auth_tag_end;
}
msg_len += jwe->enc_iv.raw_len;
msg_len += jwe->enc_ct.raw_len;
msg_len += sizeof(uint64_t);
if (!_cjose_jwe_malloc(msg_len, false, &msg, err))
{
goto _cjose_jwe_calc_auth_tag_end;
Expand Down Expand Up @@ -1319,6 +1359,12 @@ static bool _cjose_jwe_decrypt_dat_aes_gcm(cjose_jwe_t *jwe, cjose_err *err)
}
EVP_CIPHER_CTX_init(ctx);

if (jwe->enc_iv.raw_len != 12)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
goto _cjose_jwe_decrypt_dat_aes_gcm_fail;
}

// initialize context for decryption using AES GCM cipher and CEK and IV
if (EVP_DecryptInit_ex(ctx, cipher, NULL, jwe->cek, jwe->enc_iv.raw) != 1)
{
Expand Down Expand Up @@ -1394,6 +1440,12 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
}
const char *enc = json_string_value(enc_obj);

if (jwe->enc_iv.raw_len != AES_BLOCK_SIZE)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

// calculate Authentication Tag
unsigned int tag_len = 0;
uint8_t tag[EVP_MAX_MD_SIZE];
Expand All @@ -1403,7 +1455,7 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
}

// compare the provided Authentication Tag against our calculation
if ((tag_len != jwe->enc_auth_tag.raw_len) || (cjose_const_memcmp(tag, jwe->enc_auth_tag.raw, tag_len) != 0))
if ((tag_len != jwe->enc_auth_tag.raw_len) || (CRYPTO_memcmp(tag, jwe->enc_auth_tag.raw, tag_len) != 0))
{
CJOSE_ERROR(err, CJOSE_ERR_CRYPTO);
return false;
Expand Down Expand Up @@ -1449,7 +1501,13 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
}

// allocate buffer for the plaintext + one block padding
int p_len = jwe->enc_ct.raw_len, f_len = 0;
if (jwe->enc_ct.raw_len > INT_MAX || jwe->enc_ct.raw_len > SIZE_MAX - AES_BLOCK_SIZE)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
goto _cjose_jwe_decrypt_dat_aes_cbc_fail;
}

int p_len = (int)jwe->enc_ct.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))
Expand Down Expand Up @@ -1484,6 +1542,43 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err)
return false;
}

////////////////////////////////////////////////////////////////////////////////
static bool _cjose_jwe_validate_decrypt_key(_jwe_int_recipient_t *recipient,
cjose_header_t *protected_header,
cjose_header_t *shared_header,
const cjose_jwk_t *jwk,
cjose_err *err)
{
const char *alg = _cjose_jwe_get_from_headers(protected_header, shared_header, (cjose_header_t *)recipient->unprotected, CJOSE_HDR_ALG);
if (NULL == alg)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

if (((0 == strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP)) || (0 == strcmp(alg, CJOSE_HDR_ALG_RSA1_5))) && jwk->kty != CJOSE_JWK_KTY_RSA)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

if (((0 == strcmp(alg, CJOSE_HDR_ALG_A128KW)) || (0 == strcmp(alg, CJOSE_HDR_ALG_A192KW)) || (0 == strcmp(alg, CJOSE_HDR_ALG_A256KW))
|| (0 == strcmp(alg, CJOSE_HDR_ALG_DIR)))
&& jwk->kty != CJOSE_JWK_KTY_OCT)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

if ((0 == strcmp(alg, CJOSE_HDR_ALG_ECDH_ES)) && jwk->kty != CJOSE_JWK_KTY_EC)
{
CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
return false;
}

return true;
}

////////////////////////////////////////////////////////////////////////////////
cjose_jwe_t *cjose_jwe_encrypt_iv(const cjose_jwk_t *jwk,
cjose_header_t *protected_header,
Expand Down Expand Up @@ -2084,6 +2179,11 @@ uint8_t *cjose_jwe_decrypt_multi(cjose_jwe_t *jwe, cjose_key_locator key_locator
continue;
}

if (!_cjose_jwe_validate_decrypt_key(jwe->to + i, (cjose_header_t *)jwe->hdr, (cjose_header_t *)jwe->shared_hdr, key, err))
{
goto _cjose_jwe_decrypt_multi_fail;
}

// decrypt JWE content-encryption key from encrypted key
if (!jwe->to[i].fns.decrypt_ek(jwe->to + i, jwe, key, err))
{
Expand Down Expand Up @@ -2147,6 +2247,11 @@ uint8_t *cjose_jwe_decrypt(cjose_jwe_t *jwe, const cjose_jwk_t *jwk, size_t *con
return NULL;
}

if (!_cjose_jwe_validate_decrypt_key(jwe->to, (cjose_header_t *)jwe->hdr, (cjose_header_t *)jwe->shared_hdr, jwk, err))
{
return NULL;
}

// decrypt JWE content-encryption key from encrypted key
if (!jwe->to[0].fns.decrypt_ek(jwe->to, jwe, jwk, err))
{
Expand Down
Loading
Loading