diff --git a/src/lib/mac/gmac/gmac.cpp b/src/lib/mac/gmac/gmac.cpp index 20688e32dfe..ed6f4450d42 100644 --- a/src/lib/mac/gmac/gmac.cpp +++ b/src/lib/mac/gmac/gmac.cpp @@ -12,7 +12,6 @@ #include #include #include -#include namespace Botan { @@ -22,7 +21,6 @@ GMAC::GMAC(std::unique_ptr cipher) : void GMAC::clear() { m_cipher->clear(); m_ghash->clear(); - m_aad_buf.clear(); zeroise(m_H); m_initialized = false; } @@ -42,20 +40,7 @@ size_t GMAC::output_length() const { } void GMAC::add_data(std::span input) { - BufferSlicer in(input); - - while(!in.empty()) { - if(const auto one_block = m_aad_buf.handle_unaligned_data(in)) { - m_ghash->update_associated_data(one_block.value()); - } - - if(m_aad_buf.in_alignment()) { - const auto [aligned_data, full_blocks] = m_aad_buf.aligned_data_to_process(in); - if(full_blocks > 0) { - m_ghash->update_associated_data(aligned_data); - } - } - } + m_ghash->update_associated_data(input); } bool GMAC::has_keying_material() const { @@ -77,8 +62,7 @@ void GMAC::start_msg(std::span nonce) { copy_mem(y0.data(), nonce.data(), nonce.size()); y0[GCM_BS - 1] = 1; } else { - m_ghash->ghash_update(y0, nonce); - m_ghash->add_final_block(y0, 0, nonce.size()); + m_ghash->nonce_hash(y0, nonce); } secure_vector m_enc_y0(GCM_BS); @@ -95,14 +79,8 @@ void GMAC::final_result(std::span mac) { throw Invalid_State("GMAC was not used with a fresh nonce"); } - // Process the rest of the aad buffer. - if(!m_aad_buf.in_alignment()) { - m_ghash->update_associated_data(m_aad_buf.consume_partial()); - } - m_ghash->final(mac.first(output_length())); m_ghash->set_key(m_H); - m_aad_buf.clear(); } std::unique_ptr GMAC::new_object() const { diff --git a/src/lib/mac/gmac/gmac.h b/src/lib/mac/gmac/gmac.h index a5ddaa28134..66fac644e92 100644 --- a/src/lib/mac/gmac/gmac.h +++ b/src/lib/mac/gmac/gmac.h @@ -10,7 +10,6 @@ #define BOTAN_GMAC_H_ #include -#include namespace Botan { @@ -55,7 +54,6 @@ class GMAC final : public MessageAuthenticationCode { static const size_t GCM_BS = 16; std::unique_ptr m_cipher; std::unique_ptr m_ghash; - AlignmentBuffer m_aad_buf; secure_vector m_H; bool m_initialized; }; diff --git a/src/lib/modes/aead/gcm/gcm.cpp b/src/lib/modes/aead/gcm/gcm.cpp index 3e9668bc1e6..073dedbd6b0 100644 --- a/src/lib/modes/aead/gcm/gcm.cpp +++ b/src/lib/modes/aead/gcm/gcm.cpp @@ -58,7 +58,7 @@ std::string GCM_Mode::provider() const { } size_t GCM_Mode::update_granularity() const { - return GCM_BS; + return 1; } size_t GCM_Mode::ideal_granularity() const { diff --git a/src/lib/utils/ghash/ghash.cpp b/src/lib/utils/ghash/ghash.cpp index 56c3d935b19..5f995011ba8 100644 --- a/src/lib/utils/ghash/ghash.cpp +++ b/src/lib/utils/ghash/ghash.cpp @@ -2,6 +2,7 @@ * GCM GHASH * (C) 2013,2015,2017 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity +* (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,8 +13,7 @@ #include #include #include - -#include +#include namespace Botan { @@ -33,7 +33,9 @@ std::string GHASH::provider() const { return "base"; } -void GHASH::ghash_multiply(secure_vector& x, std::span input, size_t blocks) { +void GHASH::ghash_multiply(std::span x, std::span input, size_t blocks) { + BOTAN_ASSERT_NOMSG(input.size() % GCM_BS == 0); + #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) if(CPUID::has_carryless_multiply()) { BOTAN_ASSERT_NOMSG(!m_H_pow.empty()); @@ -47,15 +49,17 @@ void GHASH::ghash_multiply(secure_vector& x, std::span i } #endif - CT::poison(x.data(), x.size()); + auto scope = CT::scoped_poison(x); - uint64_t X[2] = {load_be(x.data(), 0), load_be(x.data(), 1)}; + auto X = load_be>(x); + BufferSlicer in(input); for(size_t b = 0; b != blocks; ++b) { - X[0] ^= load_be(input.data(), 2 * b); - X[1] ^= load_be(input.data(), 2 * b + 1); + const auto I = load_be>(in.take()); + X[0] ^= I[0]; + X[1] ^= I[1]; - uint64_t Z[2] = {0, 0}; + std::array Z{}; for(size_t i = 0; i != 64; ++i) { const auto X0MASK = CT::Mask::expand_top_bit(X[0]); @@ -75,45 +79,20 @@ void GHASH::ghash_multiply(secure_vector& x, std::span i X[1] = Z[1]; } - store_be(x.data(), X[0], X[1]); - CT::unpoison(x.data(), x.size()); -} - -void GHASH::ghash_update(secure_vector& ghash, std::span input) { - assert_key_material_set(!m_H.empty()); - - /* - This assumes if less than block size input then we're just on the - final block and should pad with zeros - */ - - const size_t full_blocks = input.size() / GCM_BS; - const size_t final_bytes = input.size() - (full_blocks * GCM_BS); - - if(full_blocks > 0) { - ghash_multiply(ghash, input.first(full_blocks * GCM_BS), full_blocks); - } - - if(final_bytes) { - uint8_t last_block[GCM_BS] = {0}; - copy_mem(last_block, input.subspan(full_blocks * GCM_BS).data(), final_bytes); - ghash_multiply(ghash, last_block, 1); - secure_scrub_memory(last_block, final_bytes); - } + store_be(x, X); } bool GHASH::has_keying_material() const { - return !m_ghash.empty(); + return !m_HM.empty(); } void GHASH::key_schedule(std::span key) { - m_H.assign(key.begin(), key.end()); // TODO: C++23 - std::vector<>::assign_range() - m_H_ad.resize(GCM_BS); + m_H_ad = {0}; m_ad_len = 0; m_text_len = 0; - uint64_t H0 = load_be(m_H.data(), 0); - uint64_t H1 = load_be(m_H.data(), 1); + BOTAN_ASSERT_NOMSG(key.size() == GCM_BS); + auto H = load_be>(key.first()); const uint64_t R = 0xE100000000000000; @@ -126,98 +105,124 @@ void GHASH::key_schedule(std::span key) { we interleave H^1, H^65, H^2, H^66, H3, H67, H4, H68 to make indexing nicer in the multiplication code */ - m_HM[4 * j + 2 * i] = H0; - m_HM[4 * j + 2 * i + 1] = H1; + m_HM[4 * j + 2 * i] = H[0]; + m_HM[4 * j + 2 * i + 1] = H[1]; // GCM's bit ops are reversed so we carry out of the bottom - const uint64_t carry = CT::Mask::expand(H1 & 1).if_set_return(R); - H1 = (H1 >> 1) | (H0 << 63); - H0 = (H0 >> 1) ^ carry; + const uint64_t carry = CT::Mask::expand(H[1] & 1).if_set_return(R); + H[1] = (H[1] >> 1) | (H[0] << 63); + H[0] = (H[0] >> 1) ^ carry; } } #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) if(CPUID::has_carryless_multiply()) { m_H_pow.resize(8); - ghash_precompute_cpu(m_H.data(), m_H_pow.data()); + ghash_precompute_cpu(key.data(), m_H_pow.data()); } #endif } void GHASH::start(std::span nonce) { BOTAN_ARG_CHECK(nonce.size() == 16, "GHASH requires a 128-bit nonce"); - m_nonce.assign(nonce.begin(), nonce.end()); // TODO: C++23: assign_range - m_ghash = m_H_ad; + auto& n = m_nonce.emplace(); + copy_mem(n, nonce); + copy_mem(m_ghash, m_H_ad); } void GHASH::set_associated_data(std::span input) { - if(m_ghash.empty() == false) { - throw Invalid_State("Too late to set AD in GHASH"); - } - - zeroise(m_H_ad); + BOTAN_STATE_CHECK(!m_nonce); + assert_key_material_set(); + m_H_ad = {0}; ghash_update(m_H_ad, input); + ghash_zeropad(m_H_ad); m_ad_len = input.size(); } void GHASH::update_associated_data(std::span ad) { assert_key_material_set(); - m_ad_len += ad.size(); ghash_update(m_ghash, ad); + m_ad_len += ad.size(); } void GHASH::update(std::span input) { assert_key_material_set(); - m_text_len += input.size(); + BOTAN_STATE_CHECK(m_nonce); ghash_update(m_ghash, input); -} - -void GHASH::add_final_block(secure_vector& hash, size_t ad_len, size_t text_len) { - /* - * stack buffer is fine here since the text len is public - * and the length of the AD is probably not sensitive either. - */ - std::array final_block; - - const uint64_t ad_bits = 8 * ad_len; - const uint64_t text_bits = 8 * text_len; - store_be(final_block, ad_bits, text_bits); - ghash_update(hash, final_block); + m_text_len += input.size(); } void GHASH::final(std::span mac) { - BOTAN_ARG_CHECK(!mac.empty() && mac.size() <= 16, "GHASH output length"); - + BOTAN_ARG_CHECK(!mac.empty() && mac.size() <= GCM_BS, "GHASH output length"); + BOTAN_STATE_CHECK(m_nonce); assert_key_material_set(); - add_final_block(m_ghash, m_ad_len, m_text_len); - for(size_t i = 0; i != mac.size(); ++i) { - mac[i] = m_ghash[i] ^ m_nonce[i]; - } + ghash_zeropad(m_ghash); + ghash_final_block(m_ghash, m_ad_len, m_text_len); - m_ghash.clear(); + xor_buf(mac, std::span{m_ghash}.first(mac.size()), std::span{*m_nonce}.first(mac.size())); + + secure_scrub_memory(m_ghash); m_text_len = 0; + m_nonce.reset(); } void GHASH::nonce_hash(secure_vector& y0, std::span nonce) { - BOTAN_ASSERT(m_ghash.empty(), "nonce_hash called during wrong time"); + assert_key_material_set(); + BOTAN_STATE_CHECK(!m_nonce); + BOTAN_ARG_CHECK(y0.size() == GCM_BS, "ghash state must be 16 bytes"); - ghash_update(y0, nonce); - add_final_block(y0, 0, nonce.size()); + auto sy0 = std::span{y0}; + ghash_update(sy0, nonce); + ghash_zeropad(sy0); + ghash_final_block(sy0, 0, nonce.size()); } void GHASH::clear() { - zap(m_H); zap(m_HM); reset(); } void GHASH::reset() { - zeroise(m_H_ad); - m_ghash.clear(); - m_nonce.clear(); + m_H_ad = {0}; + secure_scrub_memory(m_ghash); + if(m_nonce) { + secure_scrub_memory(m_nonce.value()); + m_nonce.reset(); + } + m_buffer.clear(); m_text_len = m_ad_len = 0; } +void GHASH::ghash_update(std::span x, std::span input) { + BufferSlicer in(input); + while(!in.empty()) { + if(const auto one_block = m_buffer.handle_unaligned_data(in)) { + ghash_multiply(x, one_block.value(), 1); + } + + if(m_buffer.in_alignment()) { + const auto [aligned_data, full_blocks] = m_buffer.aligned_data_to_process(in); + if(full_blocks > 0) { + ghash_multiply(x, aligned_data, full_blocks); + } + } + } + BOTAN_ASSERT_NOMSG(in.empty()); +} + +void GHASH::ghash_zeropad(std::span x) { + if(!m_buffer.in_alignment()) { + m_buffer.fill_up_with_zeros(); + ghash_multiply(x, m_buffer.consume(), 1); + } +} + +void GHASH::ghash_final_block(std::span x, uint64_t ad_len, uint64_t text_len) { + BOTAN_STATE_CHECK(m_buffer.in_alignment()); + const auto final_block = store_be(8 * ad_len, 8 * text_len); + ghash_multiply(x, final_block, 1); +} + } // namespace Botan diff --git a/src/lib/utils/ghash/ghash.h b/src/lib/utils/ghash/ghash.h index f75b37e47ed..22ece29a09c 100644 --- a/src/lib/utils/ghash/ghash.h +++ b/src/lib/utils/ghash/ghash.h @@ -9,6 +9,7 @@ #define BOTAN_GCM_GHASH_H_ #include +#include namespace Botan { @@ -16,21 +17,21 @@ namespace Botan { * GCM's GHASH */ class GHASH final : public SymmetricAlgorithm { - public: - void set_associated_data(std::span ad); + private: + static constexpr size_t GCM_BS = 16; + public: + /// Hashing of non-default length nonce values for both GCM and GMAC use-cases void nonce_hash(secure_vector& y0, std::span nonce); void start(std::span nonce); - /* - * Assumes input len is multiple of 16 - */ void update(std::span in); - /* - * Incremental update of associated data - */ + /// Monolithic setting of associated data usid in the GCM use-case + void set_associated_data(std::span ad); + + /// Incremental update of associated data used in the GMAC use-case void update_associated_data(std::span ad); void final(std::span out); @@ -47,11 +48,11 @@ class GHASH final : public SymmetricAlgorithm { std::string provider() const; - void ghash_update(secure_vector& x, std::span input); - - void add_final_block(secure_vector& x, size_t ad_len, size_t pt_len); - private: + void ghash_update(std::span x, std::span input); + void ghash_zeropad(std::span x); + void ghash_final_block(std::span x, uint64_t ad_len, uint64_t pt_len); + #if defined(BOTAN_HAS_GHASH_CLMUL_CPU) static void ghash_precompute_cpu(const uint8_t H[16], uint64_t H_pow[4 * 2]); @@ -64,16 +65,17 @@ class GHASH final : public SymmetricAlgorithm { void key_schedule(std::span key) override; - void ghash_multiply(secure_vector& x, std::span input, size_t blocks); + void ghash_multiply(std::span x, std::span input, size_t blocks); - static const size_t GCM_BS = 16; + private: + AlignmentBuffer m_buffer; - secure_vector m_H; - secure_vector m_H_ad; - secure_vector m_ghash; - secure_vector m_nonce; + std::array m_H_ad; /// cache of hash state after consuming the AD, reused for multiple messages + std::array m_ghash; /// hash state used for update() or update_associated_data() secure_vector m_HM; secure_vector m_H_pow; + + std::optional> m_nonce; size_t m_ad_len = 0; size_t m_text_len = 0; }; diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index ddca625566e..7e8468a5f6d 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -283,7 +283,7 @@ def test_cipher(self): elif mode == 'Serpent/GCM': self.assertEqual(enc.algo_name(), 'Serpent/GCM(16)') self.assertTrue(enc.is_authenticated()) - self.assertEqual(enc.update_granularity(), 16) + self.assertEqual(enc.update_granularity(), 1) self.assertGreater(enc.ideal_update_granularity(), 16) elif mode == 'ChaCha20Poly1305': self.assertEqual(enc.algo_name(), 'ChaCha20Poly1305')