diff --git a/include/cjose/jwe.h b/include/cjose/jwe.h index 07578d4..7ab0d2c 100644 --- a/include/cjose/jwe.h +++ b/include/cjose/jwe.h @@ -64,6 +64,36 @@ typedef const cjose_jwk_t *(*cjose_key_locator)(cjose_jwe_t *jwe, cjose_header_t cjose_jwe_t * cjose_jwe_encrypt(const cjose_jwk_t *jwk, cjose_header_t *header, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err); +/** + * Creates a new JWE by encrypting the given plaintext within the given header + * and JWK, with a static IV. + * + * If the header provided indicates an algorithm requiring an asymmetric key + * (e.g. RSA-OAEP), the provided JWK must be asymmetric (e.g. RSA or EC). + * + * If the header provided indicates an algorithm requiring a symmetric key + * (e.g. (dir), the provided JWK must be symmetric (e.g. oct). + * + * \param jwk [in] the key to use for encrypting the JWE. + * \param protected_header [in] additional header values to include in the JWE protected header. + * \param iv [in] the initialization vector for encrypting the JWE payload. If NULL, an IV will be automatically generated. + * The IV is copied. + * \param iv_len [in] the length of the initialization vector, or 0 if iv is NULL. + * \param plaintext [in] the plaintext to be encrypted in the JWE payload. + * \param plaintext_len [in] the length of the plaintext. + * \param err [out] An optional error object which can be used to get additional + * information in the event of an error. + * \returns a newly generated JWE with the given plaintext as the payload. + */ +cjose_jwe_t * +cjose_jwe_encrypt_iv(const cjose_jwk_t *jwk, + cjose_header_t *header, + const uint8_t *iv, + size_t iv_len, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err); + /** * Creates a new JWE by encrypting the given plaintext with multiple keys. * \see ::cjose_jwe_encrypt for key requirements. @@ -75,7 +105,7 @@ cjose_jwe_encrypt(const cjose_jwk_t *jwk, cjose_header_t *header, const uint8_t * recipients there is. * \param protected_header [in] additional header values to include in the JWE protected header. The header * is retained by JWE and should be released by the caller if no longer needed. - * \param unprotected_header [in] additional header values to include in the shared JWE unprotected header, + * \param shared_unprotected_header [in] additional header values to include in the shared JWE unprotected header, * can be NULL. The header is retained by JWE and should be released by the caller if no longer needed. * \param plaintext [in] the plaintext to be encrypted in the JWE payload. * \param plaintext_len [in] the length of the plaintext. @@ -91,6 +121,38 @@ cjose_jwe_t *cjose_jwe_encrypt_multi(const cjose_jwe_recipient_t * recipients, size_t plaintext_len, cjose_err *err); +/** + * Creates a new JWE by encrypting the given plaintext with multiple keys and a static IV. + * \see ::cjose_jwe_encrypt for key requirements. + * \see ::cjose_jwe_encrypt_multi to automatically generate an IV. + * \param recipients [in] array of recipient objects. Each element must have the + * key of the recipient, and may have optional (not NULL) unprotected header. + * Unprotected header is retained by this function, and can be safely released by the + * caller if no longer needed. The key is only used within the scope of this function. + * \param recipient_count effective length of the recipients array, specifying how many + * recipients there is. + * \param protected_header [in] additional header values to include in the JWE protected header. The header + * is retained by JWE and should be released by the caller if no longer needed. + * \param shared_unprotected_header [in] additional header values to include in the shared JWE unprotected header, + * can be NULL. The header is retained by JWE and should be released by the caller if no longer needed. + * \param iv [in] the initialization vector for encrypting the JWE payload. If NULL, an IV will be automatically generated. + * \param iv_len [in] the length of the initialization vector, or 0 if iv is NULL. + * \param plaintext [in] the plaintext to be encrypted in the JWE payload. + * \param plaintext_len [in] the length of the plaintext. + * \param err [out] An optional error object which can be used to get additional + * information in the event of an error. + * \returns a newly generated JWE with the given plaintext as the payload. + */ +cjose_jwe_t *cjose_jwe_encrypt_multi_iv(const cjose_jwe_recipient_t * recipients, + size_t recipient_count, + cjose_header_t *protected_header, + cjose_header_t *shared_unprotected_header, + const uint8_t *iv, + size_t iv_len, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err); + /** * Creates a compact serialization of the given JWE object. * diff --git a/src/jwe.c b/src/jwe.c index 29f08ca..e751ddb 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -1442,8 +1442,13 @@ static bool _cjose_jwe_decrypt_dat_aes_cbc(cjose_jwe_t *jwe, cjose_err *err) } //////////////////////////////////////////////////////////////////////////////// -cjose_jwe_t *cjose_jwe_encrypt( - const cjose_jwk_t *jwk, cjose_header_t *protected_header, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err) +cjose_jwe_t *cjose_jwe_encrypt_iv(const cjose_jwk_t *jwk, + cjose_header_t *protected_header, + const uint8_t *iv, + size_t iv_len, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err) { cjose_jwe_recipient_t rec = { @@ -1451,17 +1456,26 @@ cjose_jwe_t *cjose_jwe_encrypt( .unprotected_header = NULL }; - return cjose_jwe_encrypt_multi(&rec, 1, protected_header, NULL, plaintext, plaintext_len, err); + return cjose_jwe_encrypt_multi_iv(&rec, 1, protected_header, NULL, iv, iv_len, plaintext, plaintext_len, err); } //////////////////////////////////////////////////////////////////////////////// -cjose_jwe_t *cjose_jwe_encrypt_multi(const cjose_jwe_recipient_t * recipients, - size_t recipient_count, - cjose_header_t *protected_header, - cjose_header_t *shared_unprotected_header, - const uint8_t *plaintext, - size_t plaintext_len, - cjose_err *err) +cjose_jwe_t *cjose_jwe_encrypt( + const cjose_jwk_t *jwk, cjose_header_t *protected_header, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err) +{ + return cjose_jwe_encrypt_iv(jwk, protected_header, NULL, 0, plaintext, plaintext_len, err); +} + +//////////////////////////////////////////////////////////////////////////////// +cjose_jwe_t *cjose_jwe_encrypt_multi_iv(const cjose_jwe_recipient_t * recipients, + size_t recipient_count, + cjose_header_t *protected_header, + cjose_header_t *shared_unprotected_header, + const uint8_t *iv, + size_t iv_len, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err) { cjose_jwe_t *jwe = NULL; @@ -1539,10 +1553,22 @@ cjose_jwe_t *cjose_jwe_encrypt_multi(const cjose_jwe_recipient_t * recipients, } // build JWE initialization vector - if (!jwe->fns.set_iv(jwe, err)) + if (iv == NULL) { + if (!jwe->fns.set_iv(jwe, err)) + { + cjose_jwe_release(jwe); + return NULL; + } + } + else { - cjose_jwe_release(jwe); - return NULL; + cjose_get_dealloc()(jwe->enc_iv.raw); + jwe->enc_iv.raw_len = iv_len; + if (!_cjose_jwe_malloc(jwe->enc_iv.raw_len, false, &jwe->enc_iv.raw, err)) { + cjose_jwe_release(jwe); + return NULL; + } + memcpy(jwe->enc_iv.raw, iv, iv_len); } // build JWE encrypted data and authentication tag @@ -1557,6 +1583,26 @@ cjose_jwe_t *cjose_jwe_encrypt_multi(const cjose_jwe_recipient_t * recipients, return jwe; } +//////////////////////////////////////////////////////////////////////////////// +cjose_jwe_t *cjose_jwe_encrypt_multi(const cjose_jwe_recipient_t * recipients, + size_t recipient_count, + cjose_header_t *protected_header, + cjose_header_t *shared_unprotected_header, + const uint8_t *plaintext, + size_t plaintext_len, + cjose_err *err) +{ + return cjose_jwe_encrypt_multi_iv(recipients, + recipient_count, + protected_header, + shared_unprotected_header, + NULL, + 0, + plaintext, + plaintext_len, + err); +} + //////////////////////////////////////////////////////////////////////////////// void cjose_jwe_release(cjose_jwe_t *jwe) { diff --git a/test/check_jwe.c b/test/check_jwe.c index 48451e5..25ed4f6 100644 --- a/test/check_jwe.c +++ b/test/check_jwe.c @@ -271,6 +271,142 @@ START_TEST(test_cjose_jwe_self_encrypt_self_decrypt) } END_TEST +static void _self_encrypt_self_decrypt_with_key_iv(const char *alg, const char *enc, const char *key, size_t iv_len, const char *plain1) +{ + cjose_err err; + + cjose_jwk_t *jwk = cjose_jwk_import(key, strlen(key), &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); + + // set header for JWE + cjose_header_t *hdr = cjose_header_new(&err); + ck_assert_msg(cjose_header_set(hdr, CJOSE_HDR_ALG, alg, &err), + "cjose_header_set failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + ck_assert_msg(cjose_header_set(hdr, CJOSE_HDR_ENC, enc, &err), + "cjose_header_set failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // generate a random IV + uint8_t *iv = (uint8_t *)malloc(iv_len); + ck_assert_msg(RAND_bytes(iv, iv_len) == 1, "RAND_bytes failed"); + + // create the JWE + size_t plain1_len = strlen(plain1); + cjose_jwe_t *jwe1 = cjose_jwe_encrypt_iv(jwk, hdr, iv, iv_len, plain1, plain1_len, &err); + ck_assert_msg(NULL != jwe1, "cjose_jwe_encrypt failed: %s, file: %s, function: %s, line: %ld", err.message, err.file, + err.function, err.line); + // ck_assert(hdr == cjose_jwe_get_protected(jwe1)); + + // get the compact serialization of JWE + char *compact = cjose_jwe_export(jwe1, &err); + ck_assert_msg(NULL != compact, "cjose_jwe_export failed: %s, file: %s, function: %s, line: %ld", err.message, err.file, + err.function, err.line); + + // deserialize the compact representation to a new JWE + cjose_jwe_t *jwe2 = cjose_jwe_import(compact, strlen(compact), &err); + ck_assert_msg(NULL != jwe2, + "cjose_jwe_import failed for algo %s, method %s: " + "%s, file: %s, function: %s, line: %ld", + alg, enc, err.message, err.file, err.function, err.line); + + // get the decrypted plaintext + uint8_t *plain2 = NULL; + size_t plain2_len = 0; + plain2 = cjose_jwe_decrypt(jwe2, jwk, &plain2_len, &err); + ck_assert_msg(NULL != plain2, + "cjose_jwe_decrypt failed: " + "%s, file: %s, function: %s, line: %ld", + err.message, err.file, err.function, err.line); + + // confirm plain2 == plain1 + ck_assert(json_equal((json_t *)cjose_jwe_get_protected(jwe1), (json_t *)cjose_jwe_get_protected(jwe2))); + ck_assert_msg(plain2_len == strlen(plain1), + "length of decrypted plaintext does not match length of original, " + "expected: %lu, found: %lu", + strlen(plain1), plain2_len); + ck_assert_msg(strncmp(plain1, plain2, plain2_len) == 0, "decrypted plaintext does not match encrypted plaintext"); + + cjose_get_dealloc()(plain2); + cjose_header_release(hdr); + free(iv); + cjose_jwe_release(jwe1); + cjose_jwe_release(jwe2); + cjose_jwk_release(jwk); + cjose_get_dealloc()(compact); +} + +static void _self_encrypt_self_decrypt_iv(const char *plain1) +{ + // Tests for when #85 (A128GCM and A192GCM support) is merged + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA_OAEP, CJOSE_HDR_ENC_A128GCM, JWK_RSA, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA1_5, CJOSE_HDR_ENC_A128GCM, JWK_RSA, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A128GCM, JWK_OCT_16, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA_OAEP, CJOSE_HDR_ENC_A192GCM, JWK_RSA, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA1_5, CJOSE_HDR_ENC_A192GCM, JWK_RSA, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A192GCM, JWK_OCT_24, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA_OAEP, CJOSE_HDR_ENC_A256GCM, JWK_RSA, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_RSA1_5, CJOSE_HDR_ENC_A256GCM, JWK_RSA, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A256GCM, JWK_OCT_32, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A128CBC_HS256, JWK_OCT_32, 16, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A192CBC_HS384, JWK_OCT_48, 16, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_DIR, CJOSE_HDR_ENC_A256CBC_HS512, JWK_OCT_64, 16, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A128CBC_HS256, JWK_OCT_16, 16, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A192KW, CJOSE_HDR_ENC_A192CBC_HS384, JWK_OCT_24, 16, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A256KW, CJOSE_HDR_ENC_A256CBC_HS512, JWK_OCT_32, 16, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A128GCM, JWK_OCT_16, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_ECDH_ES, CJOSE_HDR_ENC_A128GCM, JWK_EC, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A192GCM, JWK_OCT_16, 12, plain1); + + // _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_ECDH_ES, CJOSE_HDR_ENC_A192GCM, JWK_EC, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_A128KW, CJOSE_HDR_ENC_A256GCM, JWK_OCT_16, 12, plain1); + + _self_encrypt_self_decrypt_with_key_iv(CJOSE_HDR_ALG_ECDH_ES, CJOSE_HDR_ENC_A256GCM, JWK_EC, 12, plain1); +} + +START_TEST(test_cjose_jwe_self_encrypt_self_decrypt_iv) +{ + _self_encrypt_self_decrypt_iv("Sed ut perspiciatis unde omnis iste natus error sit voluptatem " + "doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo " + "veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo " + "ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed " + "consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. " + "porro quisquam est, qui dolorem ipsum quia dolor sit amet, " + "adipisci velit, sed quia non numquam eius modi tempora incidunt ut " + "dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, " + "nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut " + "ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in " + "voluptate velit esse quam nihil molestiae consequatur, vel illum qui " + "eum fugiat quo voluptas nulla pariatur?"); +} +END_TEST + + START_TEST(test_cjose_jwe_self_encrypt_self_decrypt_short) { _self_encrypt_self_decrypt("Setec Astronomy"); } END_TEST @@ -1236,6 +1372,7 @@ Suite *cjose_jwe_suite() tcase_set_timeout(tc_jwe, 120.0); tcase_add_test(tc_jwe, test_cjose_jwe_node_jose_encrypt_self_decrypt); tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt); + tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_iv); tcase_add_test(tc_jwe, test_cjose_jwe_self_encrypt_self_decrypt_short); 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);