diff --git a/crypto/cipher_extra/aead_test.cc b/crypto/cipher_extra/aead_test.cc index 8b5e44e409..aea0a5d917 100644 --- a/crypto/cipher_extra/aead_test.cc +++ b/crypto/cipher_extra/aead_test.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -45,6 +46,11 @@ constexpr uint32_t kNondeterministic = 1 << 7; // . The two must be synchronized. constexpr uint32_t kConcurrent = 1 << 12; +// kCanCopy indicates that the AEAD supports duplicating an initialized +// |EVP_AEAD_CTX| with |EVP_AEAD_CTX_copy|. AEADs whose state owns external +// resources (e.g. the TLS record-layer AEADs) do not set this. +constexpr uint32_t kCanCopy = 1 << 13; + // RequiresADLength encodes an AD length requirement into flags. constexpr uint32_t RequiresADLength(size_t length) { // If we had a more recent C++ version we could assert that the length is @@ -79,41 +85,41 @@ struct KnownAEAD { static const struct KnownAEAD kAEADs[] = { {"AES_128_GCM", EVP_aead_aes_128_gcm, "aes_128_gcm_tests.txt", - kCanTruncateTags | kVariableNonce | kConcurrent}, + kCanTruncateTags | kVariableNonce | kConcurrent | kCanCopy}, {"AES_128_GCM_NIST", EVP_aead_aes_128_gcm, "nist_cavp/aes_128_gcm.txt", - kCanTruncateTags | kVariableNonce | kConcurrent}, + kCanTruncateTags | kVariableNonce | kConcurrent | kCanCopy}, {"AES_192_GCM", EVP_aead_aes_192_gcm, "aes_192_gcm_tests.txt", - kCanTruncateTags | kVariableNonce | kConcurrent}, + kCanTruncateTags | kVariableNonce | kConcurrent | kCanCopy}, {"AES_256_GCM", EVP_aead_aes_256_gcm, "aes_256_gcm_tests.txt", - kCanTruncateTags | kVariableNonce | kConcurrent}, + kCanTruncateTags | kVariableNonce | kConcurrent | kCanCopy}, {"AES_256_GCM_NIST", EVP_aead_aes_256_gcm, "nist_cavp/aes_256_gcm.txt", - kCanTruncateTags | kVariableNonce | kConcurrent}, + kCanTruncateTags | kVariableNonce | kConcurrent | kCanCopy}, {"AES_128_GCM_SIV", EVP_aead_aes_128_gcm_siv, "aes_128_gcm_siv_tests.txt", - kConcurrent}, + kConcurrent | kCanCopy}, {"AES_256_GCM_SIV", EVP_aead_aes_256_gcm_siv, "aes_256_gcm_siv_tests.txt", - kConcurrent}, + kConcurrent | kCanCopy}, {"AES_128_GCM_RandomNonce", EVP_aead_aes_128_gcm_randnonce, "aes_128_gcm_randnonce_tests.txt", kNondeterministic | kCanTruncateTags | RequiresMinimumTagLength(13) | - kConcurrent}, + kConcurrent | kCanCopy}, {"AES_256_GCM_RandomNonce", EVP_aead_aes_256_gcm_randnonce, "aes_256_gcm_randnonce_tests.txt", kNondeterministic | kCanTruncateTags | RequiresMinimumTagLength(13) | - kConcurrent}, + kConcurrent | kCanCopy}, {"ChaCha20Poly1305", EVP_aead_chacha20_poly1305, - "chacha20_poly1305_tests.txt", kCanTruncateTags | kConcurrent}, + "chacha20_poly1305_tests.txt", kCanTruncateTags | kConcurrent | kCanCopy}, {"XChaCha20Poly1305", EVP_aead_xchacha20_poly1305, - "xchacha20_poly1305_tests.txt", kCanTruncateTags | kConcurrent}, + "xchacha20_poly1305_tests.txt", kCanTruncateTags | kConcurrent | kCanCopy}, {"AES_128_CBC_SHA1_TLS", EVP_aead_aes_128_cbc_sha1_tls, "aes_128_cbc_sha1_tls_tests.txt", @@ -156,19 +162,19 @@ static const struct KnownAEAD kAEADs[] = { kLimitedImplementation | RequiresADLength(11)}, {"AES_128_CTR_HMAC_SHA256", EVP_aead_aes_128_ctr_hmac_sha256, - "aes_128_ctr_hmac_sha256.txt", kCanTruncateTags | kConcurrent}, + "aes_128_ctr_hmac_sha256.txt", kCanTruncateTags | kConcurrent | kCanCopy}, {"AES_256_CTR_HMAC_SHA256", EVP_aead_aes_256_ctr_hmac_sha256, - "aes_256_ctr_hmac_sha256.txt", kCanTruncateTags | kConcurrent}, + "aes_256_ctr_hmac_sha256.txt", kCanTruncateTags | kConcurrent | kCanCopy}, {"AES_128_CCM_BLUETOOTH", EVP_aead_aes_128_ccm_bluetooth, - "aes_128_ccm_bluetooth_tests.txt", kConcurrent}, + "aes_128_ccm_bluetooth_tests.txt", kConcurrent | kCanCopy}, {"AES_128_CCM_BLUETOOTH_8", EVP_aead_aes_128_ccm_bluetooth_8, - "aes_128_ccm_bluetooth_8_tests.txt", kConcurrent}, + "aes_128_ccm_bluetooth_8_tests.txt", kConcurrent | kCanCopy}, {"AES_128_CCM_Matter", EVP_aead_aes_128_ccm_matter, - "aes_128_ccm_matter_tests.txt", kConcurrent}, + "aes_128_ccm_matter_tests.txt", kConcurrent | kCanCopy}, }; class PerAEADTest : public testing::TestWithParam { @@ -286,6 +292,114 @@ TEST_P(PerAEADTest, TestVector) { }); } +TEST_P(PerAEADTest, Copy) { + const uint32_t flags = GetParam().flags; + const bool expect_copyable = (flags & kCanCopy) != 0; + + std::vector key(EVP_AEAD_key_length(aead()), 'k'); + const size_t tag_len = EVP_AEAD_DEFAULT_TAG_LENGTH; + + // Copying an uninitialized context always fails, regardless of the AEAD. + { + bssl::ScopedEVP_AEAD_CTX uninitialized; + bssl::ScopedEVP_AEAD_CTX dst; + EXPECT_FALSE(EVP_AEAD_CTX_copy(dst.get(), uninitialized.get())); + ERR_clear_error(); + } + + // Use |init_with_direction| so this also works for the direction-bound TLS + // AEADs, which do not implement |init|. + bssl::ScopedEVP_AEAD_CTX src; + ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( + src.get(), aead(), key.data(), key.size(), tag_len, evp_aead_seal)); + + // The result of |EVP_AEAD_CTX_copy| must agree with the advertised + // capability. This keeps |kCanCopy| and the AEAD's |copy| hook in sync. + { + bssl::ScopedEVP_AEAD_CTX dst; + const int copied = EVP_AEAD_CTX_copy(dst.get(), src.get()); + ASSERT_EQ(copied == 1, expect_copyable); + } + if (!expect_copyable) { + ERR_clear_error(); + return; + } + + static const uint8_t kPlaintext[] = "EVP_AEAD_CTX_copy test plaintext"; + static const uint8_t kAD[] = {1, 2, 3, 4, 5, 6, 7, 8}; + std::vector nonce(EVP_AEAD_nonce_length(aead()), 'n'); + + auto seal = [&](const EVP_AEAD_CTX *ctx, std::vector *out) -> bool { + out->resize(sizeof(kPlaintext) + EVP_AEAD_max_overhead(aead())); + size_t out_len = 0; + if (!EVP_AEAD_CTX_seal(ctx, out->data(), &out_len, out->size(), + nonce.data(), nonce.size(), kPlaintext, + sizeof(kPlaintext), kAD, sizeof(kAD))) { + return false; + } + out->resize(out_len); + return true; + }; + + auto open_and_check = [&](const EVP_AEAD_CTX *ctx, + const std::vector &ct) -> bool { + std::vector out(ct.size()); + size_t out_len = 0; + if (!EVP_AEAD_CTX_open(ctx, out.data(), &out_len, out.size(), nonce.data(), + nonce.size(), ct.data(), ct.size(), kAD, + sizeof(kAD))) { + return false; + } + return out_len == sizeof(kPlaintext) && + OPENSSL_memcmp(out.data(), kPlaintext, sizeof(kPlaintext)) == 0; + }; + + // A copy must be usable independently of the source, in both directions. + bssl::ScopedEVP_AEAD_CTX copy; + ASSERT_TRUE(EVP_AEAD_CTX_copy(copy.get(), src.get())); + + std::vector ct_src, ct_copy; + ASSERT_TRUE(seal(src.get(), &ct_src)); + ASSERT_TRUE(seal(copy.get(), &ct_copy)); + EXPECT_TRUE(open_and_check(copy.get(), ct_src)); + EXPECT_TRUE(open_and_check(src.get(), ct_copy)); + + // For deterministic AEADs the copy must reproduce the source byte-for-byte. + if (!(flags & kNondeterministic)) { + EXPECT_EQ(Bytes(ct_src), Bytes(ct_copy)); + } + + // Self-copy is a no-op and must leave the context usable. + ASSERT_TRUE(EVP_AEAD_CTX_copy(src.get(), src.get())); + std::vector ct_self; + ASSERT_TRUE(seal(src.get(), &ct_self)); + EXPECT_TRUE(open_and_check(src.get(), ct_self)); + + // Copy between contexts whose |state| fields have different 16-byte + // alignment. |a| is 16-byte aligned; |b| is placed a further 8 bytes past a + // 16-byte boundary so exactly one of the two has an over-aligned |state|. + // This exercises the |state_offset| relocation path that AES-GCM-SIV's + // assembly implementation depends on; a verbatim |state| copy would silently + // break there. The two contexts must not overlap. + alignas(16) uint8_t storage[3 * sizeof(EVP_AEAD_CTX) + 32]; + const size_t b_off = + ((sizeof(EVP_AEAD_CTX) + 15) & ~static_cast(15)) + 8; + EVP_AEAD_CTX *a = new (storage) EVP_AEAD_CTX; + EVP_AEAD_CTX *b = new (storage + b_off) EVP_AEAD_CTX; + EVP_AEAD_CTX_zero(a); + EVP_AEAD_CTX_zero(b); + ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( + a, aead(), key.data(), key.size(), tag_len, evp_aead_seal)); + ASSERT_TRUE(EVP_AEAD_CTX_copy(b, a)); + std::vector ct_a, ct_b; + ASSERT_TRUE(seal(a, &ct_a)); + ASSERT_TRUE(seal(b, &ct_b)); + EXPECT_TRUE(open_and_check(b, ct_a)); + EXPECT_TRUE(open_and_check(a, ct_b)); + EVP_AEAD_CTX_cleanup(a); + EVP_AEAD_CTX_cleanup(b); +} + struct KnownTLSLegacyAEAD { const char name[40]; const EVP_CIPHER *(*func)(void); diff --git a/crypto/cipher_extra/e_aesctrhmac.c b/crypto/cipher_extra/e_aesctrhmac.c index 46e705644c..d361129690 100644 --- a/crypto/cipher_extra/e_aesctrhmac.c +++ b/crypto/cipher_extra/e_aesctrhmac.c @@ -244,6 +244,7 @@ static const EVP_AEAD aead_aes_128_ctr_hmac_sha256 = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_ctx_copy_state_trivial /* copy */, }; static const EVP_AEAD aead_aes_256_ctr_hmac_sha256 = { @@ -264,6 +265,7 @@ static const EVP_AEAD aead_aes_256_ctr_hmac_sha256 = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_ctx_copy_state_trivial /* copy */, }; const EVP_AEAD *EVP_aead_aes_128_ctr_hmac_sha256(void) { diff --git a/crypto/cipher_extra/e_aesgcmsiv.c b/crypto/cipher_extra/e_aesgcmsiv.c index 52b723f958..29243fd01c 100644 --- a/crypto/cipher_extra/e_aesgcmsiv.c +++ b/crypto/cipher_extra/e_aesgcmsiv.c @@ -101,6 +101,24 @@ static int aead_aes_gcm_siv_asm_init(EVP_AEAD_CTX *ctx, const uint8_t *key, static void aead_aes_gcm_siv_asm_cleanup(EVP_AEAD_CTX *ctx) {} +static int aead_aes_gcm_siv_asm_copy(EVP_AEAD_CTX *out, + const EVP_AEAD_CTX *in) { + // The asm context is over-aligned within |state| and is located via + // |state_offset|, which is derived from the runtime address of |state|. + // A verbatim copy of |state| would misplace it whenever |out| and |in| have + // different alignment, so re-derive the offset for |out| and relocate the + // sub-context to the destination's aligned slot. + out->state_offset = (uint8_t)(((uintptr_t)&out->state) & 8); + struct aead_aes_gcm_siv_asm_ctx *out_ctx = asm_ctx_from_ctx(out); + const struct aead_aes_gcm_siv_asm_ctx *in_ctx = asm_ctx_from_ctx(in); + if (in_ctx == NULL) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INITIALIZATION_ERROR); + return 0; + } + OPENSSL_memcpy(out_ctx, in_ctx, sizeof(*out_ctx)); + return 1; +} + // aesgcmsiv_polyval_horner updates the POLYVAL value in |in_out_poly| to // include a number (|in_blocks|) of 16-byte blocks of data from |in|, given // the POLYVAL key in |key|. @@ -516,6 +534,7 @@ static const EVP_AEAD aead_aes_128_gcm_siv_asm = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_aes_gcm_siv_asm_copy /* copy */, }; static const EVP_AEAD aead_aes_256_gcm_siv_asm = { @@ -536,6 +555,7 @@ static const EVP_AEAD aead_aes_256_gcm_siv_asm = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_aes_gcm_siv_asm_copy /* copy */, }; #endif // X86_64 && !NO_ASM && !WINDOWS @@ -805,6 +825,7 @@ static const EVP_AEAD aead_aes_128_gcm_siv = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_ctx_copy_state_trivial /* copy */, }; static const EVP_AEAD aead_aes_256_gcm_siv = { @@ -825,6 +846,7 @@ static const EVP_AEAD aead_aes_256_gcm_siv = { NULL /* tag_len */, NULL /* serialize_state */, NULL /* deserialize_state */, + aead_ctx_copy_state_trivial /* copy */, }; #if defined(AES_GCM_SIV_ASM) diff --git a/crypto/cipher_extra/e_chacha20poly1305.c b/crypto/cipher_extra/e_chacha20poly1305.c index fb32f05b85..ed50029e3a 100644 --- a/crypto/cipher_extra/e_chacha20poly1305.c +++ b/crypto/cipher_extra/e_chacha20poly1305.c @@ -343,6 +343,7 @@ static const EVP_AEAD aead_chacha20_poly1305 = { NULL, // tag_len NULL, // serialize_state NULL, // deserialize_state + aead_ctx_copy_state_trivial, // copy }; static const EVP_AEAD aead_xchacha20_poly1305 = { @@ -363,6 +364,7 @@ static const EVP_AEAD aead_xchacha20_poly1305 = { NULL, // tag_len NULL, // serialize_state NULL, // deserialize_state + aead_ctx_copy_state_trivial, // copy }; const EVP_AEAD *EVP_aead_chacha20_poly1305(void) { diff --git a/crypto/cipher_extra/e_tls.c b/crypto/cipher_extra/e_tls.c index 9be2356bb5..89d733e9e5 100644 --- a/crypto/cipher_extra/e_tls.c +++ b/crypto/cipher_extra/e_tls.c @@ -484,6 +484,7 @@ static const EVP_AEAD aead_aes_128_cbc_sha1_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_128_cbc_sha1_tls_implicit_iv = { @@ -504,6 +505,7 @@ static const EVP_AEAD aead_aes_128_cbc_sha1_tls_implicit_iv = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_256_cbc_sha1_tls = { @@ -524,6 +526,7 @@ static const EVP_AEAD aead_aes_256_cbc_sha1_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_256_cbc_sha1_tls_implicit_iv = { @@ -544,6 +547,7 @@ static const EVP_AEAD aead_aes_256_cbc_sha1_tls_implicit_iv = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_128_cbc_sha256_tls = { @@ -564,6 +568,7 @@ static const EVP_AEAD aead_aes_128_cbc_sha256_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_128_cbc_sha256_tls_implicit_iv = { @@ -584,6 +589,7 @@ static const EVP_AEAD aead_aes_128_cbc_sha256_tls_implicit_iv = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_aes_256_cbc_sha384_tls = { @@ -604,6 +610,7 @@ static const EVP_AEAD aead_aes_256_cbc_sha384_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_des_ede3_cbc_sha1_tls = { @@ -624,6 +631,7 @@ static const EVP_AEAD aead_des_ede3_cbc_sha1_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_des_ede3_cbc_sha1_tls_implicit_iv = { @@ -644,6 +652,7 @@ static const EVP_AEAD aead_des_ede3_cbc_sha1_tls_implicit_iv = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; static const EVP_AEAD aead_null_sha1_tls = { @@ -664,6 +673,7 @@ static const EVP_AEAD aead_null_sha1_tls = { aead_tls_tag_len, NULL /* serialize_state */, NULL /* deserialize_state */, + NULL /* copy */, }; const EVP_AEAD *EVP_aead_aes_128_cbc_sha1_tls(void) { diff --git a/crypto/fipsmodule/cipher/aead.c b/crypto/fipsmodule/cipher/aead.c index 5ab7a11e3c..f5534bff28 100644 --- a/crypto/fipsmodule/cipher/aead.c +++ b/crypto/fipsmodule/cipher/aead.c @@ -101,6 +101,57 @@ void EVP_AEAD_CTX_cleanup(EVP_AEAD_CTX *ctx) { ctx->aead = NULL; } +int aead_ctx_copy_state_trivial(EVP_AEAD_CTX *out, const EVP_AEAD_CTX *in) { + // The state is self-contained within |state| and owns no external + // resources, so a verbatim copy fully duplicates it. |state_offset| is + // preserved for uniformity; AEADs whose live state depends on the runtime + // address of |state| must not use this helper (see internal.h). + OPENSSL_memcpy(&out->state, &in->state, sizeof(out->state)); + out->state_offset = in->state_offset; + return 1; +} + +int EVP_AEAD_CTX_copy(EVP_AEAD_CTX *out, const EVP_AEAD_CTX *in) { + SET_DIT_AUTO_RESET; + GUARD_PTR(in); + GUARD_PTR(out); + + // A self-copy would first release |out| (== |in|) and then read from it, so + // treat it as a no-op. + if (out == in) { + return 1; + } + + if (in->aead == NULL) { + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INPUT_NOT_INITIALIZED); + return 0; + } + + if (in->aead->copy == NULL) { + // This AEAD does not support copying (e.g. its state owns heap resources). + OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_CTRL_NOT_IMPLEMENTED); + return 0; + } + + // Release any prior state in |out| and start from a clean slate. This is + // safe whether |out| was zeroed or previously initialized. + EVP_AEAD_CTX_cleanup(out); + EVP_AEAD_CTX_zero(out); + + out->aead = in->aead; + out->tag_len = in->tag_len; + + // The callback owns duplicating |state| (and |state_offset| where the layout + // depends on alignment). + if (!in->aead->copy(out, in)) { + // Leave |out| in the zero state so it remains safe to clean up. + EVP_AEAD_CTX_zero(out); + return 0; + } + + return 1; +} + // check_alias returns 1 if |out| is compatible with |in| and 0 otherwise. If // |in| and |out| alias, we require that |in| == |out|. static int check_alias(const uint8_t *in, size_t in_len, const uint8_t *out, diff --git a/crypto/fipsmodule/cipher/e_aes.c b/crypto/fipsmodule/cipher/e_aes.c index 2bd0bbb61f..1f44f63935 100644 --- a/crypto/fipsmodule/cipher/e_aes.c +++ b/crypto/fipsmodule/cipher/e_aes.c @@ -1209,6 +1209,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_128_gcm) { out->cleanup = aead_aes_gcm_cleanup; out->seal_scatter = aead_aes_gcm_seal_scatter; out->open_gather = aead_aes_gcm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_192_gcm) { @@ -1225,6 +1226,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_192_gcm) { out->cleanup = aead_aes_gcm_cleanup; out->seal_scatter = aead_aes_gcm_seal_scatter; out->open_gather = aead_aes_gcm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_256_gcm) { @@ -1241,6 +1243,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_256_gcm) { out->cleanup = aead_aes_gcm_cleanup; out->seal_scatter = aead_aes_gcm_seal_scatter; out->open_gather = aead_aes_gcm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } static int aead_aes_gcm_init_randnonce(EVP_AEAD_CTX *ctx, const uint8_t *key, @@ -1345,6 +1348,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_128_gcm_randnonce) { out->cleanup = aead_aes_gcm_cleanup; out->seal_scatter = aead_aes_gcm_seal_scatter_randnonce; out->open_gather = aead_aes_gcm_open_gather_randnonce; + out->copy = aead_ctx_copy_state_trivial; } DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_256_gcm_randnonce) { @@ -1361,6 +1365,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_256_gcm_randnonce) { out->cleanup = aead_aes_gcm_cleanup; out->seal_scatter = aead_aes_gcm_seal_scatter_randnonce; out->open_gather = aead_aes_gcm_open_gather_randnonce; + out->copy = aead_ctx_copy_state_trivial; } struct aead_aes_gcm_tls12_ctx { diff --git a/crypto/fipsmodule/cipher/e_aesccm.c b/crypto/fipsmodule/cipher/e_aesccm.c index e2a2779bd1..73eed8bbdc 100644 --- a/crypto/fipsmodule/cipher/e_aesccm.c +++ b/crypto/fipsmodule/cipher/e_aesccm.c @@ -403,6 +403,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_128_ccm_bluetooth) { out->cleanup = aead_aes_ccm_cleanup; out->seal_scatter = aead_aes_ccm_seal_scatter; out->open_gather = aead_aes_ccm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } static int aead_aes_ccm_bluetooth_8_init(EVP_AEAD_CTX *ctx, const uint8_t *key, @@ -424,6 +425,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_128_ccm_bluetooth_8) { out->cleanup = aead_aes_ccm_cleanup; out->seal_scatter = aead_aes_ccm_seal_scatter; out->open_gather = aead_aes_ccm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } static int aead_aes_ccm_matter_init(EVP_AEAD_CTX *ctx, const uint8_t *key, @@ -444,6 +446,7 @@ DEFINE_METHOD_FUNCTION(EVP_AEAD, EVP_aead_aes_128_ccm_matter) { out->cleanup = aead_aes_ccm_cleanup; out->seal_scatter = aead_aes_ccm_seal_scatter; out->open_gather = aead_aes_ccm_open_gather; + out->copy = aead_ctx_copy_state_trivial; } #if defined(OPENSSL_32_BIT) diff --git a/crypto/fipsmodule/cipher/internal.h b/crypto/fipsmodule/cipher/internal.h index 20cbbf6e8b..16b4a8ff19 100644 --- a/crypto/fipsmodule/cipher/internal.h +++ b/crypto/fipsmodule/cipher/internal.h @@ -98,6 +98,17 @@ struct evp_aead_st { int (*serialize_state)(const EVP_AEAD_CTX *ctx, CBB *cbb); int (*deserialize_state)(const EVP_AEAD_CTX *ctx, CBS *cbs); + + // copy, if non-NULL, duplicates the post-init state of |in| into |out| and + // is invoked by |EVP_AEAD_CTX_copy|. When it is called, the generic layer + // has already set |out->aead| and |out->tag_len| and has zeroed the rest of + // |out|. The callback is responsible for populating |out->state| (and + // |out->state_offset| where the layout depends on alignment). It must fully + // duplicate any state and must not leave |out| aliasing a heap resource + // owned by |in|. It returns one on success and zero on error. A NULL |copy| + // means the AEAD does not support |EVP_AEAD_CTX_copy|; this is the safe + // default for AEADs whose state owns heap resources. + int (*copy)(EVP_AEAD_CTX *out, const EVP_AEAD_CTX *in); }; struct evp_cipher_st { @@ -145,6 +156,15 @@ ctr128_f aes_ctr_set_key(AES_KEY *aes_key, GCM128_KEY *gcm_key, block128_f *out_block, const uint8_t *key, size_t key_bytes); +// aead_ctx_copy_state_trivial is a ready-made |copy| callback (see +// |evp_aead_st|) for AEADs whose per-key state is fully self-contained within +// |ctx->state| and owns no heap resources reachable through |state.ptr|. It +// duplicates |state| verbatim and preserves |state_offset|. It MUST NOT be used +// by AEADs whose live state depends on the runtime address of |ctx->state| +// (i.e. those that consult |state_offset| to locate an over-aligned +// sub-context); such AEADs must supply a bespoke |copy| that relocates the +// state to the destination's alignment. +int aead_ctx_copy_state_trivial(EVP_AEAD_CTX *out, const EVP_AEAD_CTX *in); // EXPERIMENTAL functions for use in the TLS Transfer function. See // |SSL_to_bytes| for more details. diff --git a/crypto/libcrypto.map b/crypto/libcrypto.map index 526c99703b..faa04d04f7 100644 --- a/crypto/libcrypto.map +++ b/crypto/libcrypto.map @@ -984,6 +984,7 @@ AWS_LC_1.0 { ERR_set_mark; EVP_AEAD_CTX_aead; EVP_AEAD_CTX_cleanup; + EVP_AEAD_CTX_copy; EVP_AEAD_CTX_deserialize_state; EVP_AEAD_CTX_free; EVP_AEAD_CTX_get_aead_id; diff --git a/crypto/libcrypto.txt b/crypto/libcrypto.txt index 6450e2c635..bc90099f02 100644 --- a/crypto/libcrypto.txt +++ b/crypto/libcrypto.txt @@ -977,6 +977,7 @@ ERR_set_error_data AWS_LC_1.0 PUBLIC ERR_set_mark AWS_LC_1.0 PUBLIC EVP_AEAD_CTX_aead AWS_LC_1.0 PUBLIC EVP_AEAD_CTX_cleanup AWS_LC_1.0 PUBLIC +EVP_AEAD_CTX_copy AWS_LC_1.0 PUBLIC EVP_AEAD_CTX_deserialize_state AWS_LC_1.0 PRIVATE EVP_AEAD_CTX_free AWS_LC_1.0 PUBLIC EVP_AEAD_CTX_get_aead_id AWS_LC_1.0 PRIVATE diff --git a/include/openssl/aead.h b/include/openssl/aead.h index 4ea8d2477a..9f5e808fad 100644 --- a/include/openssl/aead.h +++ b/include/openssl/aead.h @@ -267,6 +267,19 @@ OPENSSL_EXPORT int EVP_AEAD_CTX_init(EVP_AEAD_CTX *ctx, const EVP_AEAD *aead, // all zeros. OPENSSL_EXPORT void EVP_AEAD_CTX_cleanup(EVP_AEAD_CTX *ctx); +// EVP_AEAD_CTX_copy sets |out| to be a duplicate of the current state of |in|. +// The |out| argument must either have been initialised with +// |EVP_AEAD_CTX_init| or set to the zero state with |EVP_AEAD_CTX_zero|; any +// existing state in |out| is released before the copy. It returns one on +// success and zero on error, including when the AEAD does not support being +// copied. On error, |out| is either left unmodified or set to the zero state +// (as if |EVP_AEAD_CTX_zero| had been called on it); in both cases it remains +// safe to pass to |EVP_AEAD_CTX_cleanup|. +// +// Not all AEADs support copying. In particular, the TLS record-layer AEADs +// (see |EVP_aead_aes_128_cbc_sha1_tls| and friends) cannot be copied. +OPENSSL_EXPORT int EVP_AEAD_CTX_copy(EVP_AEAD_CTX *out, const EVP_AEAD_CTX *in); + // EVP_AEAD_CTX_seal encrypts and authenticates |in_len| bytes from |in| and // authenticates |ad_len| bytes from |ad| and writes the result to |out|. It // returns one on success and zero otherwise.