diff --git a/crypto/cipher_extra/cipher_extra.c b/crypto/cipher_extra/cipher_extra.c index f67cb233fe9..364bdee7590 100644 --- a/crypto/cipher_extra/cipher_extra.c +++ b/crypto/cipher_extra/cipher_extra.c @@ -89,6 +89,7 @@ static const struct { {NID_aes_256_ctr, "aes-256-ctr", EVP_aes_256_ctr}, {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_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}, @@ -114,7 +115,8 @@ static const struct { {"aes128", "aes-128-cbc"}, {"id-aes128-gcm", "aes-128-gcm"}, {"id-aes192-gcm", "aes-192-gcm"}, - {"id-aes256-gcm", "aes-256-gcm"} + {"id-aes256-gcm", "aes-256-gcm"}, + {"id-xaes256-gcm", "xaes-256-gcm"} }; 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 5290a69f23e..e682c40a7d5 100644 --- a/crypto/cipher_extra/cipher_test.cc +++ b/crypto/cipher_extra/cipher_test.cc @@ -68,6 +68,7 @@ #include #include #include +#include #include "../internal.h" #include "../test/file_test.h" @@ -127,6 +128,8 @@ static const EVP_CIPHER *GetCipher(const std::string &name) { return EVP_aes_192_ccm(); } else if (name == "AES-256-CCM") { return EVP_aes_256_ccm(); + } else if (name == "XAES-256-GCM") { + return EVP_xaes_256_gcm(); } return nullptr; } @@ -1080,6 +1083,7 @@ TEST(CipherTest, GetCipher) { test_get_cipher(NID_aes_256_ctr, "aes-256-ctr"); 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_aes_256_ofb128, "aes-256-ofb"); test_get_cipher(NID_aes_256_xts, "aes-256-xts"); test_get_cipher(NID_chacha20_poly1305, "chacha20-poly1305"); @@ -1102,6 +1106,7 @@ TEST(CipherTest, GetCipher) { test_get_cipher(NID_aes_128_gcm, "id-aes128-gcm"); test_get_cipher(NID_aes_192_gcm, "id-aes192-gcm"); test_get_cipher(NID_aes_256_gcm, "id-aes256-gcm"); + test_get_cipher(NID_xaes_256_gcm, "id-xaes256-gcm"); // error case EXPECT_FALSE(EVP_get_cipherbyname(nullptr)); @@ -1454,3 +1459,286 @@ TEST(CipherTest, Empty_EVP_CIPHER_CTX_V1187459157) { CHECK_ERROR(EVP_DecryptUpdate(ctx.get(), out_vec.data(), &out_len, in_vec.data(), in_len), ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); CHECK_ERROR(EVP_DecryptFinal(ctx.get(), out_vec.data(), &out_len), ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); } + +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)); +} + +TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_DERIVING_SUBKEYS_DIFFERENT_NONCES) { + /* + * Test deriving many subkeys from a main key but with different nonces + * Source of test vectors: + * https://github.com/C2SP/C2SP/blob/main/XAES-256-GCM.md + */ + 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)); + size_t iv_len = 24; + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_AEAD_SET_IVLEN, iv_len, nullptr)); + + // 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)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_IVLEN, iv_len, 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, const uint8_t *plaintext, size_t plaintext_len, + std::vector &expected_ciphertext, std::vector &expected_tag) { + // Encrypt + // Initiaze IV and derive a subkey + ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), nullptr, nullptr, nullptr, iv.data(), -1)); + + std::vector ciphertext, tag; + ciphertext.resize(plaintext_len); + int ciphertext_len = 0, len = 0; + + // ASSERT_TRUE(EVP_CipherUpdate(ectx.get(), ciphertext.data(), &ciphertext_len, plaintext, plaintext_len)); + + for(size_t i = 0; i < plaintext_len; ++i) { + // Test streaming input + ASSERT_TRUE(EVP_CipherUpdate(ectx.get(), ciphertext.data() + ciphertext_len, &len, + plaintext + i, /* one byte */ 1)); + ciphertext_len += len; + } + + ASSERT_TRUE(EVP_CipherFinal_ex(ectx.get(), ciphertext.data() + ciphertext_len, &len)); + ciphertext_len += len; + ASSERT_EQ(Bytes(ciphertext), Bytes(expected_ciphertext)); + + size_t tag_size = 16; + tag.resize(tag_size); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_AEAD_GET_TAG, tag.size(), (void*)tag.data())); + ASSERT_EQ(Bytes(tag), Bytes(expected_tag)); + + // Decrypt + // Initiaze IV and derive a subkey + ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), nullptr, nullptr, nullptr, iv.data())); + + 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)); + for(size_t i = 0; i < plaintext_len; ++i) { + // Test streaming input + ASSERT_TRUE(EVP_DecryptUpdate(dctx.get(), decrypted.data() + decrypted_len, &len, + ciphertext.data() + i, /* one byte */ 1)); + decrypted_len += len; + } + + ASSERT_TRUE(EVP_DecryptFinal(dctx.get(), decrypted.data() + decrypted_len, &len)); + decrypted_len += len; + + ASSERT_EQ((size_t)decrypted_len, plaintext_len); + ASSERT_EQ(Bytes(decrypted), Bytes(plaintext, plaintext_len)); + }; + + // Test with an IV + std::vector iv; + DecodeHex(&iv, "424242424242424242424242424242424242424242424242"); + + // Test encryption and decryption with a plaintext + const uint8_t *plaintext = (const uint8_t *)"Hello, XAES-256-GCM!"; + std::vector ciphertext, tag; + DecodeHex(&ciphertext, "01e5f78bc99de880bd2eeff2870d361f0eab5b2f"); + DecodeHex(&tag, "c55268f34b14045878fe3668db980319"); + test(iv, plaintext, strlen((const char *)plaintext), ciphertext, tag); + + // Test with another IV + DecodeHex(&iv, "4142434445464748494a4b4c4d4e4f505152535455565758"); + + // Test encryption and decryption again with another plaintext + plaintext = (const uint8_t *)"XAES-256-GCM"; + DecodeHex(&ciphertext, "ce546ef63c9cc60765923609"); + DecodeHex(&tag, "b33a9a1974e96e52daf2fcf7075e2271"); + test(iv, plaintext, strlen((const char *)plaintext), ciphertext, tag); +} + +TEST(CipherTest, XAES_256_GCM_EVP_CIPHER_MULTI_LOOP_TEST) { + // Source of multi-loop tests: + // https://github.com/C2SP/C2SP/blob/main/XAES-256-GCM/go/XAES-256-GCM_test.go + const auto test = [](int n, const char *output) { + bssl::ScopedEVP_MD_CTX s; + ASSERT_TRUE(EVP_DigestInit(s.get(), EVP_shake128())); + bssl::ScopedEVP_MD_CTX d; + ASSERT_TRUE(EVP_DigestInit(d.get(), EVP_shake128())); + + 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)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 24, nullptr)); + + bssl::UniquePtr dctx(EVP_CIPHER_CTX_new()); + ASSERT_TRUE(dctx); + ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), EVP_xaes_256_gcm(), nullptr, nullptr, nullptr)); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(dctx.get(), EVP_CTRL_AEAD_SET_IVLEN, 24, nullptr)); + + std::vector key(32), nonce(24), plaintext(256); + std::vector aad(256), ciphertext(256), tag(16); + uint8_t plaintext_len = 0, aad_len = 0; + int tag_size = 16; + + for(int i = 0; i < n; ++i) { + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), key.data(), 32)); + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), nonce.data(), 24)); + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), &plaintext_len, 1)); + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), plaintext.data(), plaintext_len)); + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), &aad_len, 1)); + ASSERT_TRUE(EVP_DigestSqueeze(s.get(), aad.data(), aad_len)); + + // XAES-256-GCM Encryption + ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), nonce.data(), -1)); + ASSERT_EQ(aad_len, EVP_Cipher(ctx.get(), nullptr, aad.data(), aad_len)); + int 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)); + ciphertext_len += len; + ASSERT_TRUE(EVP_DigestUpdate(d.get(), ciphertext.data(), ciphertext_len)); + + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, tag_size, tag.data())); + ASSERT_TRUE(EVP_DigestUpdate(d.get(), tag.data(), tag_size)); + + // 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_DecryptFinal(dctx.get(), decrypted.data() + len, &len)); + + ASSERT_EQ(Bytes(decrypted), Bytes(plaintext.data(), plaintext_len)); + } + std::vector expected; + ASSERT_TRUE(DecodeHex(&expected, output)); + uint8_t got[32] = {0}; + ASSERT_TRUE(EVP_DigestFinalXOF(d.get(), got, 32)); + ASSERT_EQ(Bytes(got, 32), Bytes(expected)); + }; + + test(10000, "e6b9edf2df6cec60c8cbd864e2211b597fb69a529160cd040d56c0c210081939"); + test(1000000, "2163ae1445985a30b60585ee67daa55674df06901b890593e824b8a7c885ab15"); +} + +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) { + // 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)); + + // Encrypt + // Initiaze IV and derive a subkey + ASSERT_TRUE(EVP_CipherInit_ex(ectx.get(), nullptr, nullptr, nullptr, iv.data(), -1)); + + std::vector ciphertext, tag; + ciphertext.resize(plaintext_len); + int ciphertext_len = 0; + + ASSERT_TRUE(EVP_CipherUpdate(ectx.get(), ciphertext.data(), &ciphertext_len, + plaintext, plaintext_len)); + int len = 0; + ASSERT_TRUE(EVP_CipherFinal_ex(ectx.get(), ciphertext.data() + ciphertext_len, &len)); + ciphertext_len += len; + + size_t tag_size = 16; + tag.resize(tag_size); + ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_AEAD_GET_TAG, tag.size(), (void*)tag.data())); + + // Decrypt + // Initiaze IV and derive a subkey + ASSERT_TRUE(EVP_DecryptInit_ex(dctx.get(), nullptr, nullptr, nullptr, iv.data())); + + 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_DecryptFinal(dctx.get(), decrypted.data() + decrypted_len, &len)); + decrypted_len += len; + + ASSERT_EQ((size_t)decrypted_len, plaintext_len); + ASSERT_EQ(Bytes(decrypted), Bytes(plaintext, plaintext_len)); + }; + + // Test with a 20-byte IV + std::vector iv; + 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 with a 23-byte IV + DecodeHex(&iv, "4142434445464748494a4b4c4d4e4f5051525354555657"); + plaintext = (const uint8_t *)"XAES-256-GCM"; + test(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 1622a88dd14..52b6e8faa7b 100644 --- a/crypto/cipher_extra/test/cipher_tests.txt +++ b/crypto/cipher_extra/test/cipher_tests.txt @@ -506,6 +506,32 @@ AAD = feedfacedeadbeeffeedfacedeadbeefabaddad2 Tag = a44a8266ee1c8eb0c8b5d4cf5ae9f19b Operation = InvalidDecrypt +# Source of test vectors: +# https://github.com/C2SP/C2SP/blob/main/XAES-256-GCM.md +Cipher = XAES-256-GCM +Key = 0101010101010101010101010101010101010101010101010101010101010101 +IV = 424242424242424242424242424242424242424242424242 +Plaintext = 48656c6c6f2c20584145532d3235362d47434d21 +Ciphertext = 01e5f78bc99de880bd2eeff2870d361f0eab5b2f +AAD = +Tag = c55268f34b14045878fe3668db980319 + +Cipher = XAES-256-GCM +Key = 0101010101010101010101010101010101010101010101010101010101010101 +IV = 4142434445464748494a4b4c4d4e4f505152535455565758 +Plaintext = 584145532d3235362d47434d +Ciphertext = ce546ef63c9cc60765923609 +AAD = +Tag = b33a9a1974e96e52daf2fcf7075e2271 + +Cipher = XAES-256-GCM +Key = 0303030303030303030303030303030303030303030303030303030303030303 +IV = 4142434445464748494a4b4c4d4e4f505152535455565758 +Plaintext = 584145532d3235362d47434d +Ciphertext = 986ec1832593df5443a17943 +AAD = 633273702e6f72672f584145532d3235362d47434d +Tag = 7fd083bf3fdb41abd740a21f71eb769d + # 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 60921abaa07..4b629ac3e85 100644 --- a/crypto/fipsmodule/cipher/e_aes.c +++ b/crypto/fipsmodule/cipher/e_aes.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include "../../internal.h" @@ -346,6 +347,12 @@ ctr128_f aes_ctr_set_key(AES_KEY *aes_key, GCM128_KEY *gcm_key, #define EVP_AES_GCM_CTX_PADDING 8 #endif +typedef struct { + EVP_AES_GCM_CTX aes_gcm_ctx; + AES_KEY xaes_key; + uint8_t k1[AES_BLOCK_SIZE]; +} XAES_256_GCM_CTX; + static EVP_AES_GCM_CTX *aes_gcm_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { OPENSSL_STATIC_ASSERT( alignof(EVP_AES_GCM_CTX) <= 16, @@ -353,17 +360,38 @@ static EVP_AES_GCM_CTX *aes_gcm_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { // |malloc| guarantees up to 4-byte alignment on 32-bit and 8-byte alignment // on 64-bit systems, so we need to adjust to reach 16-byte alignment. - assert(ctx->cipher->ctx_size == - sizeof(EVP_AES_GCM_CTX) + EVP_AES_GCM_CTX_PADDING); - - char *ptr = ctx->cipher_data; + char *ptr = NULL; + + switch(ctx->cipher->nid) { + // AES-GCM + case NID_aes_128_gcm: + case NID_aes_192_gcm: + case NID_aes_256_gcm: + assert(ctx->cipher->ctx_size == + sizeof(EVP_AES_GCM_CTX) + EVP_AES_GCM_CTX_PADDING); + ptr = ctx->cipher_data; #if defined(OPENSSL_32_BIT) - assert((uintptr_t)ptr % 4 == 0); - ptr += (uintptr_t)ptr & 4; + assert((uintptr_t)ptr % 4 == 0); + ptr += (uintptr_t)ptr & 4; #endif - assert((uintptr_t)ptr % 8 == 0); - ptr += (uintptr_t)ptr & 8; - return (EVP_AES_GCM_CTX *)ptr; + assert((uintptr_t)ptr % 8 == 0); + ptr += (uintptr_t)ptr & 8; + return (EVP_AES_GCM_CTX *)ptr; + // XAES-256-GCM + case NID_xaes_256_gcm: + assert(ctx->cipher->ctx_size == sizeof(XAES_256_GCM_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_CTX *)ptr)->aes_gcm_ctx; + default: + break; + } + return NULL; } static int aes_gcm_init_key(EVP_CIPHER_CTX *ctx, const uint8_t *key, @@ -1745,3 +1773,153 @@ int EVP_has_aes_hardware(void) { } OPENSSL_MSVC_PRAGMA(warning(pop)) + +/* ---------------------------- XAES-256-GCM ---------------------------- +Specification: +https://github.com/C2SP/C2SP/blob/main/XAES-256-GCM.md +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) + +/* +The following function performs the step #2 of CMAC specified in: +https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38b.pdf#page=14 +Only K1 is needed as the CMAC input is always a complete block. +Also note that K1 only depends on the input main key. +Pseudocode: + If MSB(L) = 0: K1 = L << 1; + Else: K1 = (L << 1) ^ (0x00, ..., 0x00, 0x87) +*/ +#define BINARY_FIELD_MUL_X_128(out, in) \ +do { \ + unsigned i; \ + /* Shift |in| to left, including carry. */ \ + for (i = 0; i < 15; i++) { \ + out[i] = (in[i] << 1) | (in[i+1] >> 7); \ + } \ + /* If MSB set fixup with R. */ \ + const uint8_t carry = in[0] >> 7; \ + out[i] = (in[i] << 1) ^ ((0 - carry) & 0x87); \ +} while(0); + +static int xaes_256_gcm_CMAC_derive_key(XAES_256_GCM_CTX *xaes_ctx, + const uint8_t* nonce, uint8_t *derived_key) { + uint8_t M1[AES_BLOCK_SIZE] = {0}; + uint8_t M2[AES_BLOCK_SIZE] = {0}; + + M1[1] = 0x01; + M1[2] = 0x58; + OPENSSL_memcpy(M1 + 4, nonce, 12); + OPENSSL_memcpy(M2, M1, AES_BLOCK_SIZE); + + M2[1] = 0x02; + + for (size_t i = 0; i < AES_BLOCK_SIZE; i++) { + M1[i] ^= xaes_ctx->k1[i]; + M2[i] ^= xaes_ctx->k1[i]; + } + + AES_encrypt(M1, derived_key, &xaes_ctx->xaes_key); + AES_encrypt(M2, derived_key + AES_BLOCK_SIZE, &xaes_ctx->xaes_key); + + return 1; +} + +static XAES_256_GCM_CTX *xaes_256_gcm_from_cipher_ctx(EVP_CIPHER_CTX *ctx) { + // Handle alignment according to the way it is implemented for the AES-GCM context + 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_CTX *)ptr; +} + +static int xaes_256_gcm_set_gcm_key(EVP_CIPHER_CTX *ctx, const uint8_t *nonce, int enc) { + XAES_256_GCM_CTX *xaes_ctx = xaes_256_gcm_from_cipher_ctx(ctx); + EVP_AES_GCM_CTX *gctx = &xaes_ctx->aes_gcm_ctx; + + // Nonce size: 20 bytes <= |N| <= 24 bytes + if(gctx->ivlen < XAES_256_GCM_MIN_NONCE_SIZE || + gctx->ivlen > XAES_256_GCM_MAX_NONCE_SIZE) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INVALID_NONCE_SIZE); + return 0; + } + + uint8_t derived_key[XAES_256_GCM_KEY_LENGTH]; + + xaes_256_gcm_CMAC_derive_key(xaes_ctx, nonce, derived_key); + + int ivlen = gctx->ivlen; + + // AES-GCM uses a different size nonce than XAES-GCM, + // so to be able to call aes_gcm_init_key with ctx we temporarily + // set the nonce (iv) length to AES_GCM_NONCE_LENGTH. + gctx->ivlen = AES_GCM_NONCE_LENGTH; + + // For nonce size < 24 bytes + // Reference: https://eprint.iacr.org/2025/758.pdf#page=24 + aes_gcm_init_key(ctx, derived_key, nonce + ivlen - AES_GCM_NONCE_LENGTH, enc); + + // Re-assign the original nonce size of XAES-256-GCM (20 <= |N| <= 24) + gctx->ivlen = ivlen; + + return 1; +} + +static int xaes_256_gcm_ctx_init(XAES_256_GCM_CTX *xaes_ctx, const uint8_t *key) { + static const uint8_t kZeroIn[AES_BLOCK_SIZE] = {0}; + uint8_t L[AES_BLOCK_SIZE]; + AES_set_encrypt_key(key, XAES_256_GCM_KEY_LENGTH << 3, &xaes_ctx->xaes_key); + AES_encrypt(kZeroIn, L, &xaes_ctx->xaes_key); + BINARY_FIELD_MUL_X_128(xaes_ctx->k1, L); + return 1; +} + +// ------------------------------------------------------------------------------ +// --------------- EVP_CIPHER XAES-256-GCM Without Key Commitment --------------- +// ------------------------------------------------------------------------------ +static int xaes_256_gcm_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_CTX *xaes_ctx = xaes_256_gcm_from_cipher_ctx(ctx); + + // When main key is provided, initialize the context and derive a subkey + if(key != NULL) { + xaes_256_gcm_ctx_init(xaes_ctx, key); + } + + // If iv is provided, even if main key is not, derive a subkey + if(iv != NULL) { + return xaes_256_gcm_set_gcm_key(ctx, iv, enc); + } + + return 1; +} + +DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_xaes_256_gcm) { + OPENSSL_memset(out, 0, sizeof(EVP_CIPHER)); + out->nid = NID_xaes_256_gcm; + out->block_size = AES_BLOCK_SIZE; + out->key_len = XAES_256_GCM_KEY_LENGTH; + out->iv_len = XAES_256_GCM_MAX_NONCE_SIZE; + out->ctx_size = sizeof(XAES_256_GCM_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; + out->init = xaes_256_gcm_init; + out->cipher = aes_gcm_cipher; + out->cleanup = aes_gcm_cleanup; + out->ctrl = aes_gcm_ctrl; +} diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h index 6834ec79562..b343549defa 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 999 +#define NUM_NID 1001 static const uint8_t kObjectData[] = { /* NID_rsadsi */ @@ -7330,6 +7330,16 @@ static const uint8_t kObjectData[] = { 0xc9, 0x7b, 0x06, + /* NID_xaes_256_gcm */ + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x01, + 0x31, }; static const ASN1_OBJECT kObjects[NUM_NID] = { @@ -9017,6 +9027,9 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { {"ED25519ph", "ED25519ph", NID_ED25519ph, 0, NULL, 0}, {"SecP384r1MLKEM1024", "SecP384r1MLKEM1024", NID_SecP384r1MLKEM1024, 9, &kObjectData[6360], 0}, + {"id-xaes256-GCM", "xaes-256-gcm", NID_xaes_256_gcm, 9, &kObjectData[6369], + 0}, + {NULL, NULL, NID_undef, 0, NULL, 0}, }; static const uint16_t kNIDsInShortNameOrder[] = { @@ -9657,6 +9670,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { 194 /* id-smime-spq */, 250 /* id-smime-spq-ets-sqt-unotice */, 249 /* id-smime-spq-ets-sqt-uri */, + 999 /* id-xaes256-GCM */, 676 /* identified-organization */, 461 /* info */, 748 /* inhibitAnyPolicy */, @@ -10996,14 +11010,15 @@ static const uint16_t kNIDsInLongNameOrder[] = { 503 /* x500UniqueIdentifier */, 158 /* x509Certificate */, 160 /* x509Crl */, + 999 /* xaes-256-gcm */, 125 /* zlib compression */, }; static const uint16_t kNIDsInOIDOrder[] = { 434 /* 0.9 (OBJ_data) */, 182 /* 1.2 (OBJ_member_body) */, - 379 /* 1.3 (OBJ_org) */, 676 /* 1.3 (OBJ_identified_organization) */, + 379 /* 1.3 (OBJ_org) */, 11 /* 2.5 (OBJ_X500) */, 647 /* 2.23 (OBJ_international_organizations) */, 380 /* 1.3.6 (OBJ_dod) */, @@ -11711,6 +11726,7 @@ static const uint16_t kNIDsInOIDOrder[] = { 901 /* 2.16.840.1.101.3.4.1.46 (OBJ_aes_256_gcm) */, 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) */, 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 d46689d3866..105d4adb84b 100644 --- a/crypto/obj/obj_mac.num +++ b/crypto/obj/obj_mac.num @@ -986,3 +986,4 @@ MLDSA65 995 MLDSA87 996 ED25519ph 997 SecP384r1MLKEM1024 998 +xaes_256_gcm 999 diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index d52dfcb614a..ddc4876596e 100644 --- a/crypto/obj/objects.txt +++ b/crypto/obj/objects.txt @@ -893,7 +893,9 @@ aes 44 : AES-256-CFB : aes-256-cfb 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 +aes 48 : id-aes256-wrap-pad +# We're introducing a new OID for XAES-256-GCM +aes 49 : id-xaes256-GCM : xaes-256-gcm # There are no OIDs for these modes... diff --git a/crypto/test/test_util.cc b/crypto/test/test_util.cc index 363b147ff3a..63039356216 100644 --- a/crypto/test/test_util.cc +++ b/crypto/test/test_util.cc @@ -34,7 +34,6 @@ #include "openssl/pem.h" #include "openssl/rand.h" - void hexdump(FILE *fp, const char *msg, const void *in, size_t len) { const uint8_t *data = reinterpret_cast(in); diff --git a/crypto/test/test_util.h b/crypto/test/test_util.h index 4f13fedc3e7..65c05b982d2 100644 --- a/crypto/test/test_util.h +++ b/crypto/test/test_util.h @@ -33,7 +33,7 @@ // hexdump writes |msg| to |fp| followed by the hex encoding of |len| bytes // from |in|. -void hexdump(FILE *fp, const char *msg, const void *in, size_t len); +void hexdump(FILE *fp, const char *msg, const void *in, size_t len); // Bytes is a wrapper over a byte slice which may be compared for equality. This // allows it to be used in EXPECT_EQ macros. diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h index cf6132061e5..cfc2c7fae12 100644 --- a/include/openssl/cipher.h +++ b/include/openssl/cipher.h @@ -365,7 +365,6 @@ OPENSSL_EXPORT int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md, #define EVP_CIPH_OCB_MODE 0x9 #define EVP_CIPH_WRAP_MODE 0xa - // Cipher flags (for |EVP_CIPHER_flags|). // EVP_CIPH_VARIABLE_LENGTH indicates that the cipher takes a variable length @@ -492,6 +491,7 @@ OPENSSL_EXPORT const EVP_CIPHER *EVP_get_cipherbyname(const char *name); // not act on it until the entire operation is complete. 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_aes_128_ccm(void); OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_192_ccm(void); diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 77f925f52df..2bfb2292f91 100644 --- a/include/openssl/nid.h +++ b/include/openssl/nid.h @@ -4386,6 +4386,11 @@ extern "C" { #define NID_SecP384r1MLKEM1024 998 #define OBJ_SecP384r1MLKEM1024 1L, 3L, 6L, 1L, 4L, 1L, 42235L, 6L +#define SN_xaes_256_gcm "id-xaes256-GCM" +#define LN_xaes_256_gcm "xaes-256-gcm" +#define NID_xaes_256_gcm 999 +#define OBJ_xaes_256_gcm 2L, 16L, 840L, 1L, 101L, 3L, 4L, 1L, 49L + #if defined(__cplusplus) } /* extern C */ #endif diff --git a/tool/speed.cc b/tool/speed.cc index a1160082cec..5c4249579b7 100644 --- a/tool/speed.cc +++ b/tool/speed.cc @@ -2950,6 +2950,7 @@ bool Speed(const std::vector &args) { !SpeedEvpCipherGeneric(EVP_aes_128_gcm(), "EVP-AES-128-GCM", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_192_gcm(), "EVP-AES-192-GCM", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_256_gcm(), "EVP-AES-256-GCM", kTLSADLen, selected) || + !SpeedEvpCipherGeneric(EVP_xaes_256_gcm(), "EVP-XAES-256-GCM", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_128_ctr(), "EVP-AES-128-CTR", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_192_ctr(), "EVP-AES-192-CTR", kTLSADLen, selected) || !SpeedEvpCipherGeneric(EVP_aes_256_ctr(), "EVP-AES-256-CTR", kTLSADLen, selected) ||