diff --git a/crypto/cipher_extra/cipher_extra.c b/crypto/cipher_extra/cipher_extra.c index 364bdee7590..f4f8316e8cc 100644 --- a/crypto/cipher_extra/cipher_extra.c +++ b/crypto/cipher_extra/cipher_extra.c @@ -90,6 +90,7 @@ static const struct { {NID_aes_256_ecb, "aes-256-ecb", EVP_aes_256_ecb}, {NID_aes_256_gcm, "aes-256-gcm", EVP_aes_256_gcm}, {NID_xaes_256_gcm, "xaes-256-gcm", EVP_xaes_256_gcm}, + {NID_xaes_256_gcm_kc, "xaes-256-gcm-kc", EVP_xaes_256_gcm_kc}, {NID_aes_256_ofb128, "aes-256-ofb", EVP_aes_256_ofb}, {NID_aes_256_xts, "aes-256-xts", EVP_aes_256_xts}, {NID_chacha20_poly1305, "chacha20-poly1305", EVP_chacha20_poly1305}, @@ -116,7 +117,8 @@ static const struct { {"id-aes128-gcm", "aes-128-gcm"}, {"id-aes192-gcm", "aes-192-gcm"}, {"id-aes256-gcm", "aes-256-gcm"}, - {"id-xaes256-gcm", "xaes-256-gcm"} + {"id-xaes256-gcm", "xaes-256-gcm"}, + {"id-xaes256-gcm-kc", "xaes-256-gcm-kc"} }; const EVP_CIPHER *EVP_get_cipherbynid(int nid) { diff --git a/crypto/cipher_extra/cipher_test.cc b/crypto/cipher_extra/cipher_test.cc index e682c40a7d5..bdb1b4fc5e6 100644 --- a/crypto/cipher_extra/cipher_test.cc +++ b/crypto/cipher_extra/cipher_test.cc @@ -130,6 +130,8 @@ static const EVP_CIPHER *GetCipher(const std::string &name) { return EVP_aes_256_ccm(); } else if (name == "XAES-256-GCM") { return EVP_xaes_256_gcm(); + } else if (name == "XAES-256-GCM-KC") { + return EVP_xaes_256_gcm_kc(); } return nullptr; } @@ -186,7 +188,8 @@ static void TestCipherAPI(const EVP_CIPHER *cipher, Operation op, bool padding, bssl::Span plaintext, bssl::Span ciphertext, bssl::Span aad, - bssl::Span tag) { + bssl::Span tag, + bssl::Span kc = {}) { bool encrypt = op == Operation::kEncrypt; bool is_custom_cipher = EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_CUSTOM_CIPHER; @@ -194,6 +197,7 @@ static void TestCipherAPI(const EVP_CIPHER *cipher, Operation op, bool padding, bssl::Span expected = encrypt ? ciphertext : plaintext; bool is_aead = EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER; bool is_ccm = EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE; + bool is_kc = EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_KC_CIPHER; // Some |EVP_CIPHER|s take a variable-length key, and need to first be // configured with the key length, which requires configuring the cipher. @@ -236,6 +240,12 @@ static void TestCipherAPI(const EVP_CIPHER *cipher, Operation op, bool padding, /*engine=*/nullptr, /*key=*/nullptr, iv.data(), /*enc=*/-1)); + // Verify key commitment + if(is_kc && !encrypt) { + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_VERIFY_KC, + kc.size(), const_cast(kc.data()))); + } + // CCM requires the full length of the plaintext to be known ahead of time. if (is_ccm) { int len; @@ -358,8 +368,17 @@ static void TestCipherAPI(const EVP_CIPHER *cipher, Operation op, bool padding, ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, tag.size(), rtag)); EXPECT_EQ(Bytes(tag), Bytes(rtag, tag.size())); + } + + if(encrypt & is_kc) { + uint8_t rkc[32]; + ASSERT_LE(kc.size(), sizeof(rkc)); + ASSERT_TRUE(MaybeCopyCipherContext(copy, &ctx)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_KC, + kc.size(), rkc)); + EXPECT_EQ(Bytes(kc), Bytes(rkc, kc.size())); } - } + } } static void TestLowLevelAPI( @@ -457,7 +476,8 @@ static void TestCipher(const EVP_CIPHER *cipher, Operation input_op, bssl::Span plaintext, bssl::Span ciphertext, bssl::Span aad, - bssl::Span tag) { + bssl::Span tag, + bssl::Span kc = {}) { size_t block_size = EVP_CIPHER_block_size(cipher); bool is_ccm = EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE; std::vector ops; @@ -487,11 +507,11 @@ static void TestCipher(const EVP_CIPHER *cipher, Operation input_op, SCOPED_TRACE(copy); TestCipherAPI(cipher, op, padding, copy, in_place, /*use_evp_cipher=*/false, chunk_size, key, iv, - plaintext, ciphertext, aad, tag); + plaintext, ciphertext, aad, tag, kc); if (!padding && chunk_size % block_size == 0) { TestCipherAPI(cipher, op, padding, copy, in_place, /*use_evp_cipher=*/true, chunk_size, key, iv, - plaintext, ciphertext, aad, tag); + plaintext, ciphertext, aad, tag, kc); } } if (!padding) { @@ -509,7 +529,7 @@ static void CipherFileTest(FileTest *t) { const EVP_CIPHER *cipher = GetCipher(cipher_str); ASSERT_TRUE(cipher); - std::vector key, iv, plaintext, ciphertext, aad, tag; + std::vector key, iv, plaintext, ciphertext, aad, tag, kc; // Force an allocation of the underlying data-store so that v.data() is // non-NULL even for empty test vectors. plaintext.reserve(1); @@ -525,6 +545,10 @@ static void CipherFileTest(FileTest *t) { ASSERT_TRUE(t->GetBytes(&tag, "Tag")); } + if(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_KC_CIPHER) { + ASSERT_TRUE(t->GetBytes(&kc, "KC")); + } + Operation op = Operation::kBoth; if (t->HasAttribute("Operation")) { const std::string &str = t->GetAttributeOrDie("Operation"); @@ -540,7 +564,7 @@ static void CipherFileTest(FileTest *t) { } TestCipher(cipher, op, /*padding=*/false, key, iv, plaintext, ciphertext, aad, - tag); + tag, kc); } TEST(CipherTest, TestVectors) { @@ -1084,6 +1108,7 @@ TEST(CipherTest, GetCipher) { test_get_cipher(NID_aes_256_ecb, "aes-256-ecb"); test_get_cipher(NID_aes_256_gcm, "aes-256-gcm"); test_get_cipher(NID_xaes_256_gcm, "xaes-256-gcm"); + test_get_cipher(NID_xaes_256_gcm_kc, "id-xaes256-gcm-kc"); test_get_cipher(NID_aes_256_ofb128, "aes-256-ofb"); test_get_cipher(NID_aes_256_xts, "aes-256-xts"); test_get_cipher(NID_chacha20_poly1305, "chacha20-poly1305"); @@ -1461,38 +1486,44 @@ TEST(CipherTest, Empty_EVP_CIPHER_CTX_V1187459157) { } TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_INVALID_NONCE_KEY_LENGTH) { - std::vector key(32), nonce(24); - - // XAES-256-GCM Encryption - bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); - ASSERT_TRUE(ctx); - ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), EVP_xaes_256_gcm(), nullptr, nullptr, nullptr, 1)); - - // Valid nonce size: 20 bytes <= |N| <= 24 bytes - // Test invalid nonce size - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 19, nullptr)); - ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 25, nullptr)); - ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 24, nullptr)); - - // Valid key length: 32 bytes - // Test invalid key length - ctx.get()->key_len = 24; - ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); - - ctx.get()->key_len = 32; - ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); - - // EVP_CipherUpdate is not allowed after EVP_CipherFinal_ex - std::vector plaintext(1), ciphertext(1); - int plaintext_len = 1, ciphertext_len = 0; - ASSERT_TRUE(EVP_CipherUpdate(ctx.get(), ciphertext.data(), &ciphertext_len, - plaintext.data(), plaintext_len)); - int len = 0; - ASSERT_TRUE(EVP_CipherFinal_ex(ctx.get(), ciphertext.data() + ciphertext_len, &len)); - ASSERT_FALSE(EVP_CipherUpdate(ctx.get(), ciphertext.data(), &ciphertext_len, - plaintext.data(), plaintext_len)); + const auto test = [](const EVP_CIPHER *cipher) { + std::vector key(32), nonce(24); + + bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); + ASSERT_TRUE(ctx); + ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, 1)); + + // Valid nonce size: 20 bytes <= |N| <= 24 bytes + // Test invalid nonce size + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 19, nullptr)); + ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 25, nullptr)); + ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 24, nullptr)); + + // Valid key length: 32 bytes + // Test invalid key length + ctx.get()->key_len = 24; + ASSERT_FALSE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); + + ctx.get()->key_len = 32; + ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); + + // EVP_CipherUpdate is not allowed after EVP_CipherFinal_ex + std::vector plaintext(1), ciphertext(1); + int plaintext_len = 1, ciphertext_len = 0; + ASSERT_TRUE(EVP_CipherUpdate(ctx.get(), ciphertext.data(), &ciphertext_len, + plaintext.data(), plaintext_len)); + int len = 0; + ASSERT_TRUE(EVP_CipherFinal_ex(ctx.get(), ciphertext.data() + ciphertext_len, &len)); + ASSERT_FALSE(EVP_CipherUpdate(ctx.get(), ciphertext.data(), &ciphertext_len, + plaintext.data(), plaintext_len)); + }; + + // XAES-256-GCM + test(EVP_xaes_256_gcm()); + // XAES-256-GCM-KC + test(EVP_xaes_256_gcm_kc()); } TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_DERIVING_SUBKEYS_DIFFERENT_NONCES) { @@ -1560,8 +1591,6 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_DERIVING_SUBKEYS_DIFFERENT_NONCES) { decrypted.resize(ciphertext_len); int decrypted_len = 0; - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag.size(), tag.data())); - // ASSERT_TRUE(EVP_DecryptUpdate(dctx.get(), decrypted.data(), &decrypted_len, ciphertext.data(), ciphertext_len)); for(size_t i = 0; i < plaintext_len; ++i) { // Test streaming input @@ -1570,6 +1599,7 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_DERIVING_SUBKEYS_DIFFERENT_NONCES) { decrypted_len += len; } + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag.size(), tag.data())); ASSERT_TRUE(EVP_DecryptFinal(dctx.get(), decrypted.data() + decrypted_len, &len)); decrypted_len += len; @@ -1647,13 +1677,12 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_MULTI_LOOP_TEST) { // XAES-256-GCM Decryption ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), nullptr, nullptr, key.data(), nonce.data())); - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag_size, tag.data())); - std::vector decrypted; decrypted.resize(plaintext_len); len = 0; EVP_DecryptUpdate(dctx.get(), nullptr, &len, aad.data(), aad_len); ASSERT_TRUE(EVP_DecryptUpdate(dctx.get(), decrypted.data(), &len, ciphertext.data(), ciphertext_len)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag_size, tag.data())); ASSERT_TRUE(EVP_DecryptFinal(dctx.get(), decrypted.data() + len, &len)); ASSERT_EQ(Bytes(decrypted), Bytes(plaintext.data(), plaintext_len)); @@ -1670,27 +1699,28 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_MULTI_LOOP_TEST) { } TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_SHORTER_NONCE) { - std::vector key; - - /* ============ INITIALIZE ENCRYPTION CONTEXT ============ */ - bssl::UniquePtr ectx(EVP_CIPHER_CTX_new()); - ASSERT_TRUE(ectx); - ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), EVP_xaes_256_gcm(), nullptr, nullptr, nullptr, 1)); - - // Initialize the main key - DecodeHex(&key, "0101010101010101010101010101010101010101010101010101010101010101"); - ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), nullptr, nullptr, key.data(), nullptr, -1)); - - /* ============ INITIALIZE DECRYPTION CONTEXT ============ */ - bssl::UniquePtr dctx(EVP_CIPHER_CTX_new()); - ASSERT_TRUE(dctx); - ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), EVP_xaes_256_gcm(), nullptr, nullptr, nullptr)); - - // Initialize the main key - ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), nullptr, nullptr, key.data(), nullptr)); - // Test encryption and decryption - const auto test = [&ectx, &dctx](std::vector &iv, int iv_len, const uint8_t *plaintext, size_t plaintext_len) { + const auto test = [](const EVP_CIPHER *cipher, std::vector &iv, int iv_len, + const uint8_t *plaintext, size_t plaintext_len) { + std::vector key; + + /* ============ INITIALIZE ENCRYPTION CONTEXT ============ */ + bssl::UniquePtr ectx(EVP_CIPHER_CTX_new()); + ASSERT_TRUE(ectx); + ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), cipher, nullptr, nullptr, nullptr, 1)); + + // Initialize the main key + DecodeHex(&key, "0101010101010101010101010101010101010101010101010101010101010101"); + ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), nullptr, nullptr, key.data(), nullptr, -1)); + + /* ============ INITIALIZE DECRYPTION CONTEXT ============ */ + bssl::UniquePtr dctx(EVP_CIPHER_CTX_new()); + ASSERT_TRUE(dctx); + ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), cipher, nullptr, nullptr, nullptr)); + + // Initialize the main key + ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), nullptr, nullptr, key.data(), nullptr)); + // Set IV Length ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_AEAD_SET_IVLEN, iv_len, nullptr)); ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_IVLEN, iv_len, nullptr)); @@ -1720,9 +1750,8 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_SHORTER_NONCE) { std::vector decrypted; decrypted.resize(ciphertext_len); int decrypted_len = 0; - - ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag.size(), tag.data())); ASSERT_TRUE(EVP_DecryptUpdate(dctx.get(), decrypted.data(), &decrypted_len, ciphertext.data(), ciphertext_len)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_TAG, tag.size(), tag.data())); ASSERT_TRUE(EVP_DecryptFinal(dctx.get(), decrypted.data() + decrypted_len, &len)); decrypted_len += len; @@ -1735,10 +1764,12 @@ TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_SHORTER_NONCE) { DecodeHex(&iv, "4242424242424242424242424242424242424242"); const uint8_t *plaintext = (const uint8_t *)"Hello, XAES-256-GCM!"; std::vector ciphertext, tag; - test(iv, iv.size(), plaintext, strlen((const char *)plaintext)); + test(EVP_xaes_256_gcm(), iv, iv.size(), plaintext, strlen((const char *)plaintext)); + test(EVP_xaes_256_gcm_kc(), iv, iv.size(), plaintext, strlen((const char *)plaintext)); // Test with a 23-byte IV DecodeHex(&iv, "4142434445464748494a4b4c4d4e4f5051525354555657"); plaintext = (const uint8_t *)"XAES-256-GCM"; - test(iv, iv.size(), plaintext, strlen((const char *)plaintext)); + test(EVP_xaes_256_gcm(), iv, iv.size(), plaintext, strlen((const char *)plaintext)); + test(EVP_xaes_256_gcm_kc(), iv, iv.size(), plaintext, strlen((const char *)plaintext)); } diff --git a/crypto/cipher_extra/test/cipher_tests.txt b/crypto/cipher_extra/test/cipher_tests.txt index 52b6e8faa7b..be0607e6a07 100644 --- a/crypto/cipher_extra/test/cipher_tests.txt +++ b/crypto/cipher_extra/test/cipher_tests.txt @@ -532,6 +532,34 @@ Ciphertext = 986ec1832593df5443a17943 AAD = 633273702e6f72672f584145532d3235362d47434d Tag = 7fd083bf3fdb41abd740a21f71eb769d +# Note: KC are our own test values +Cipher = XAES-256-GCM-KC +Key = 0101010101010101010101010101010101010101010101010101010101010101 +IV = 424242424242424242424242424242424242424242424242 +Plaintext = 48656c6c6f2c20584145532d3235362d47434d21 +Ciphertext = 01e5f78bc99de880bd2eeff2870d361f0eab5b2f +AAD = +Tag = c55268f34b14045878fe3668db980319 +KC = bf37571be1b43aeca64a95d99a2f162e24f8bfd79bbb71fa7d943e6fc060a8ae + +Cipher = XAES-256-GCM-KC +Key = 0101010101010101010101010101010101010101010101010101010101010101 +IV = 4142434445464748494a4b4c4d4e4f505152535455565758 +Plaintext = 584145532d3235362d47434d +Ciphertext = ce546ef63c9cc60765923609 +AAD = +Tag = b33a9a1974e96e52daf2fcf7075e2271 +KC = 04076b6085eebab138855fe57811c04112eff989d44120dfff662d5475a383c3 + +Cipher = XAES-256-GCM-KC +Key = 0303030303030303030303030303030303030303030303030303030303030303 +IV = 4142434445464748494a4b4c4d4e4f505152535455565758 +Plaintext = 584145532d3235362d47434d +Ciphertext = 986ec1832593df5443a17943 +AAD = 633273702e6f72672f584145532d3235362d47434d +Tag = 7fd083bf3fdb41abd740a21f71eb769d +KC = 5553cd21d1592b422e3129632a3187eee8a658cdca5c5b32ce86308dcc18e9d1 + # local add-ons, primarily streaming ghash tests # 128 bytes aad Cipher = AES-128-GCM diff --git a/crypto/fipsmodule/cipher/e_aes.c b/crypto/fipsmodule/cipher/e_aes.c index 23fee89165b..35585402b70 100644 --- a/crypto/fipsmodule/cipher/e_aes.c +++ b/crypto/fipsmodule/cipher/e_aes.c @@ -353,6 +353,15 @@ typedef struct { uint8_t k1[AES_BLOCK_SIZE]; } XAES_256_GCM_CTX; +#define XAES_256_GCM_KEY_COMMIT_SIZE (AES_BLOCK_SIZE * 2) + +typedef struct { + EVP_AES_GCM_CTX aes_gcm_ctx; + AES_KEY xaes_key; + uint8_t k1[AES_BLOCK_SIZE]; + uint8_t kc[XAES_256_GCM_KEY_COMMIT_SIZE]; +} XAES_256_GCM_KC_CTX; + static EVP_AES_GCM_CTX *aes_gcm_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { OPENSSL_STATIC_ASSERT( alignof(EVP_AES_GCM_CTX) <= 16, @@ -388,6 +397,17 @@ static EVP_AES_GCM_CTX *aes_gcm_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { assert((uintptr_t)ptr % 8 == 0); ptr += (uintptr_t)ptr & 8; return &((XAES_256_GCM_CTX *)ptr)->aes_gcm_ctx; + // XAES-256-GCM-KC + case NID_xaes_256_gcm_kc: + assert(ctx->cipher->ctx_size == sizeof(XAES_256_GCM_KC_CTX)); + ptr = ctx->cipher_data; +#if defined(OPENSSL_32_BIT) + assert((uintptr_t)ptr % 4 == 0); + ptr += (uintptr_t)ptr & 4; +#endif + assert((uintptr_t)ptr % 8 == 0); + ptr += (uintptr_t)ptr & 8; + return &((XAES_256_GCM_KC_CTX *)ptr)->aes_gcm_ctx; default: break; } @@ -1781,7 +1801,6 @@ Extension to support nonce size less than 24 bytes: https://eprint.iacr.org/2025/758.pdf#page=24 -----------------------------------------------------------------------*/ #define XAES_256_GCM_KEY_LENGTH (AES_BLOCK_SIZE * 2) -#define XAES_256_GCM_KEY_COMMIT_SIZE (AES_BLOCK_SIZE * 2) #define XAES_256_GCM_MAX_NONCE_SIZE (AES_GCM_NONCE_LENGTH * 2) #define XAES_256_GCM_MIN_NONCE_SIZE (20) /* @@ -2033,3 +2052,132 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_xaes_256_gcm) { out->seal_scatter = aead_xaes_256_gcm_seal_scatter; out->open_gather = aead_xaes_256_gcm_open_gather; } + +// ------------------------------------------------------------------------------ +// ---------------- EVP_CIPHER XAES-256-GCM With Key Commitment ----------------- +// ----------- Reference: https://eprint.iacr.org/2025/758.pdf#page=6 ----------- +// ------------------------------------------------------------------------------ +static XAES_256_GCM_KC_CTX *xaes_256_gcm_kc_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { + // alignment to be consistent with aes_gcm_from_cipher_ctx() + char *ptr = ctx->cipher_data; +#if defined(OPENSSL_32_BIT) + assert((uintptr_t)ptr % 4 == 0); + ptr += (uintptr_t)ptr & 4; +#endif + assert((uintptr_t)ptr % 8 == 0); + ptr += (uintptr_t)ptr & 8; + return (XAES_256_GCM_KC_CTX *)ptr; +} + +static int xaes_256_gcm_extract_key_commitment(AES_KEY *xaes_key, uint8_t *k1, + uint8_t *key_commitment, const uint8_t* nonce, const unsigned nonce_len) { + uint8_t W[AES_BLOCK_SIZE]; + + uint8_t kc_prefix[4] = {0x58, 0x43, 0x4D, 0x54}; + uint8_t X1[AES_BLOCK_SIZE]; + OPENSSL_memcpy(W, kc_prefix, 4); + OPENSSL_memcpy(W + 4, nonce, AES_GCM_NONCE_LENGTH); + + AES_encrypt(W, X1, xaes_key); + // For nonce size < 24 + // Reference: https://eprint.iacr.org/2025/758.pdf#page=24 + OPENSSL_memcpy(W, nonce + nonce_len - AES_GCM_NONCE_LENGTH, AES_GCM_NONCE_LENGTH); + W[AES_BLOCK_SIZE-4] = XAES_256_GCM_MAX_NONCE_SIZE - nonce_len; + W[AES_BLOCK_SIZE-3] = 0x01; + W[AES_BLOCK_SIZE-2] = 0x00; + W[AES_BLOCK_SIZE-1] = 0x01; + + for (size_t i = 0; i < AES_BLOCK_SIZE; i++) { + W[i] ^= X1[i] ^ k1[i]; + } + + AES_encrypt(W, key_commitment, xaes_key); + /* Since W1[i] and W2[i] are the same except at i = 15, where: + * W1[15] = x1[15] ^ k1[15] ^ 0x01, and W2[15] = x1[15] ^ k1[15] ^ 0x02, we have: + * W2[15] = W1[15] ^ 0x03 = (x1[15] ^ k1[15] ^ 0x01) ^ (0x01 ^ 0x02) = W2[15] */ + W[AES_BLOCK_SIZE-1] ^= 0x03; + AES_encrypt(W, key_commitment + AES_BLOCK_SIZE, xaes_key); + + return 1; +} + +static int xaes_256_gcm_kc_init(EVP_CIPHER_CTX *ctx, const uint8_t *key, + const uint8_t *iv, int enc) { + // Key length: 32 bytes + if (ctx->key_len != XAES_256_GCM_KEY_LENGTH) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_KEY_LENGTH); + return 0; + } + + XAES_256_GCM_KC_CTX *xaes_ctx = xaes_256_gcm_kc_from_cipher_ctx(ctx); + + // When main key is provided, initialize the xaes-256-gcm context + if(key != NULL) { + xaes_256_gcm_ctx_init(&xaes_ctx->xaes_key, xaes_ctx->k1, key); + } + + // If iv is provided, even if main key is not, derive a subkey and its key commitment + /* Note: Unlike the MAC tag, key commitment only depends on the main key + * and iv, not on ciphertext and aad, therefore we extract it in init when + * deriving subkey instead of at the end of encrytion like the MAC tag */ + if(iv != NULL) { + // Derive subkey + if(!xaes_256_gcm_set_gcm_key(ctx, iv, enc)) { + return 0; + } + // Extract key commitment + EVP_AES_GCM_CTX *gctx = &xaes_ctx->aes_gcm_ctx; + xaes_256_gcm_extract_key_commitment(&xaes_ctx->xaes_key, xaes_ctx->k1, xaes_ctx->kc, iv, gctx->ivlen); + } + + return 1; +} + +static int xaes_256_gcm_kc_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) { + + XAES_256_GCM_KC_CTX *xaes_ctx = xaes_256_gcm_kc_from_cipher_ctx(ctx); + + switch(type) { + case EVP_CTRL_AEAD_GET_KC: + GUARD_PTR(ptr); + if(arg < XAES_256_GCM_KEY_COMMIT_SIZE) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL); + return 0; + } + OPENSSL_memcpy(ptr, xaes_ctx->kc, arg); + return 1; + /* Since the verification of key commitment (KC) can happen at any time, + * not only at the end of decryption like for MAC tag, we create a control + * command for KC verification, rather than a command to set KC, then + * verifying it at the end of decryption as for checking the MAC tag */ + case EVP_CTRL_AEAD_VERIFY_KC: + GUARD_PTR(ptr); + if(arg < XAES_256_GCM_KEY_COMMIT_SIZE) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL); + return 0; + } + if(OPENSSL_memcmp(xaes_ctx->kc, ptr, XAES_256_GCM_KEY_COMMIT_SIZE)) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_KEY_COMMITMENT_INVALID); + return 0; + } + return 1; + default: + return aes_gcm_ctrl(ctx, type, arg, ptr); + } +} + +DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_xaes_256_gcm_kc) { + OPENSSL_memset(out, 0, sizeof(EVP_CIPHER)); + out->nid = NID_xaes_256_gcm_kc; + out->block_size = 1; + out->key_len = XAES_256_GCM_KEY_LENGTH; + out->iv_len = XAES_256_GCM_MAX_NONCE_SIZE; + out->ctx_size = sizeof(XAES_256_GCM_KC_CTX); + out->flags = EVP_CIPH_GCM_MODE | EVP_CIPH_CUSTOM_IV | EVP_CIPH_CUSTOM_COPY | + EVP_CIPH_FLAG_CUSTOM_CIPHER | EVP_CIPH_ALWAYS_CALL_INIT | + EVP_CIPH_CTRL_INIT | EVP_CIPH_FLAG_AEAD_CIPHER | EVP_CIPH_FLAG_KC_CIPHER; + out->init = xaes_256_gcm_kc_init; + out->cipher = aes_gcm_cipher; + out->cleanup = aes_gcm_cleanup; + out->ctrl = xaes_256_gcm_kc_ctrl; +} diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index 8093ec100ae..248198071d7 100644 --- a/crypto/obj/obj_dat.h +++ b/crypto/obj/obj_dat.h @@ -56,7 +56,7 @@ /* This file is generated by crypto/obj/objects.go. */ -#define NUM_NID 1000 +#define NUM_NID 1001 static const uint8_t kObjectData[] = { /* NID_rsadsi */ @@ -7340,6 +7340,16 @@ static const uint8_t kObjectData[] = { 0x04, 0x01, 0x31, + /* NID_xaes_256_gcm_kc */ + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x01, + 0x32, }; static const ASN1_OBJECT kObjects[NUM_NID] = { @@ -9029,6 +9039,8 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { &kObjectData[6360], 0}, {"id-xaes256-GCM", "xaes-256-gcm", NID_xaes_256_gcm, 9, &kObjectData[6369], 0}, + {"id-xaes256-GCM-KC", "xaes-256-gcm-kc", NID_xaes_256_gcm_kc, 9, + &kObjectData[6378], 0}, }; static const uint16_t kNIDsInShortNameOrder[] = { @@ -9670,6 +9682,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { 250 /* id-smime-spq-ets-sqt-unotice */, 249 /* id-smime-spq-ets-sqt-uri */, 999 /* id-xaes256-GCM */, + 1000 /* id-xaes256-GCM-KC */, 676 /* identified-organization */, 461 /* info */, 748 /* inhibitAnyPolicy */, @@ -11010,6 +11023,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 158 /* x509Certificate */, 160 /* x509Crl */, 999 /* xaes-256-gcm */, + 1000 /* xaes-256-gcm-kc */, 125 /* zlib compression */, }; @@ -11726,6 +11740,7 @@ static const uint16_t kNIDsInOIDOrder[] = { 902 /* 2.16.840.1.101.3.4.1.47 (OBJ_aes_256_ccm) */, 903 /* 2.16.840.1.101.3.4.1.48 (OBJ_id_aes256_wrap_pad) */, 999 /* 2.16.840.1.101.3.4.1.49 (OBJ_xaes_256_gcm) */, + 1000 /* 2.16.840.1.101.3.4.1.50 (OBJ_xaes_256_gcm_kc) */, 672 /* 2.16.840.1.101.3.4.2.1 (OBJ_sha256) */, 673 /* 2.16.840.1.101.3.4.2.2 (OBJ_sha384) */, 674 /* 2.16.840.1.101.3.4.2.3 (OBJ_sha512) */, diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num index 105d4adb84b..597acd8b3c6 100644 --- a/crypto/obj/obj_mac.num +++ b/crypto/obj/obj_mac.num @@ -987,3 +987,4 @@ MLDSA87 996 ED25519ph 997 SecP384r1MLKEM1024 998 xaes_256_gcm 999 +xaes_256_gcm_kc 1000 diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index ddc4876596e..c4b5108f018 100644 --- a/crypto/obj/objects.txt +++ b/crypto/obj/objects.txt @@ -894,9 +894,9 @@ aes 45 : id-aes256-wrap aes 46 : id-aes256-GCM : aes-256-gcm aes 47 : id-aes256-CCM : aes-256-ccm aes 48 : id-aes256-wrap-pad -# We're introducing a new OID for XAES-256-GCM +# We're introducing a new OID for XAES-256-GCM and XAES-256-GCM-KC aes 49 : id-xaes256-GCM : xaes-256-gcm - +aes 50 : id-xaes256-GCM-KC : xaes-256-gcm-kc # There are no OIDs for these modes... : AES-128-CFB1 : aes-128-cfb1 diff --git a/generated-src/crypto_test_data.cc.tar.bz2 b/generated-src/crypto_test_data.cc.tar.bz2 index 123781c5ca3..441eed5dfde 100644 Binary files a/generated-src/crypto_test_data.cc.tar.bz2 and b/generated-src/crypto_test_data.cc.tar.bz2 differ diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h index cfc2c7fae12..2de508ec290 100644 --- a/include/openssl/cipher.h +++ b/include/openssl/cipher.h @@ -393,6 +393,11 @@ OPENSSL_EXPORT int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md, // one. #define EVP_CIPH_FLAG_AEAD_CIPHER 0x800 +// EVP_CIPH_FLAG_KC_CIPHER specifies that the cipher supports key commitment. +// Currently, this flag is only used for test cases, i.e., to check whether +// the cipher supports key commitment or not +#define EVP_CIPH_FLAG_KC_CIPHER 0x4000 + // EVP_CIPH_CUSTOM_COPY indicates that the |ctrl| callback should be called // with |EVP_CTRL_COPY| at the end of normal |EVP_CIPHER_CTX_copy| // processing. @@ -492,6 +497,7 @@ OPENSSL_EXPORT const EVP_CIPHER *EVP_get_cipherbyname(const char *name); OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_128_gcm(void); OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_256_gcm(void); OPENSSL_EXPORT const EVP_CIPHER *EVP_xaes_256_gcm(void); +OPENSSL_EXPORT const EVP_CIPHER *EVP_xaes_256_gcm_kc(void); OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_128_ccm(void); OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_192_ccm(void); @@ -602,6 +608,9 @@ OPENSSL_EXPORT OPENSSL_DEPRECATED int EVP_add_cipher_alias(const char *a, #define EVP_CTRL_GCM_SET_IV_INV 0x18 #define EVP_CTRL_GET_IVLEN 0x19 +#define EVP_CTRL_AEAD_GET_KC 0x20 +#define EVP_CTRL_AEAD_VERIFY_KC 0x21 + // The following constants are unused. #define EVP_GCM_TLS_FIXED_IV_LEN 4 #define EVP_GCM_TLS_EXPLICIT_IV_LEN 8 @@ -735,5 +744,7 @@ BSSL_NAMESPACE_END #define CIPHER_R_ALIGNMENT_CHANGED 142 #define CIPHER_R_SERIALIZATION_INVALID_SERDE_VERSION 143 #define CIPHER_R_SERIALIZATION_INVALID_CIPHER_ID 144 +// We're introducing a new error code for invalid key commitment +#define CIPHER_R_KEY_COMMITMENT_INVALID 145 #endif // OPENSSL_HEADER_CIPHER_H diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 2bfb2292f91..026f5ea890e 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -4391,6 +4391,11 @@ extern "C" { #define NID_xaes_256_gcm 999 #define OBJ_xaes_256_gcm 2L, 16L, 840L, 1L, 101L, 3L, 4L, 1L, 49L +#define SN_xaes_256_gcm_kc "id-xaes256-GCM-KC" +#define LN_xaes_256_gcm_kc "xaes-256-gcm-kc" +#define NID_xaes_256_gcm_kc 1000 +#define OBJ_xaes_256_gcm_kc 2L, 16L, 840L, 1L, 101L, 3L, 4L, 1L, 50L + #if defined(__cplusplus) } /* extern C */ #endif diff --git a/tool/speed.cc b/tool/speed.cc index c8e4335108f..9533ee0b4e2 100644 --- a/tool/speed.cc +++ b/tool/speed.cc @@ -2952,6 +2952,7 @@ bool Speed(const std::vector &args) { !SpeedEvpCipherGeneric(EVP_aes_256_gcm(), "EVP-AES-256-GCM", kTLSADLen, selected) || #if AWSLC_API_VERSION > 34 !SpeedEvpCipherGeneric(EVP_xaes_256_gcm(), "EVP-XAES-256-GCM", kTLSADLen, selected) || + !SpeedEvpCipherGeneric(EVP_xaes_256_gcm_kc(), "EVP-XAES-256-GCM-KC", kTLSADLen, selected) || #endif !SpeedEvpCipherGeneric(EVP_aes_128_ctr(), "EVP-AES-128-CTR", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_192_ctr(), "EVP-AES-192-CTR", kTLSADLen, selected) ||