Repository: randombit/botan
Pull request #3714 (2023-09-26 14:00:25)

std::span and AlignmentBuffer<> for GHASH
### Pull Request Dependency

* #3713

### Description

This was a side-quest of #3713. I realized that `AlignmentBuffer<>` is useful in `GHASH` and applied it along with the usual `std::span<>` moderizations.


Patch:
diff --git a/src/lib/mac/gmac/gmac.cpp b/src/lib/mac/gmac/gmac.cpp
index 16b070750..20688e32d 100644
--- a/src/lib/mac/gmac/gmac.cpp
+++ b/src/lib/mac/gmac/gmac.cpp
@@ -17,19 +17,13 @@
 namespace Botan {
 
 GMAC::GMAC(std::unique_ptr<BlockCipher> cipher) :
-      m_cipher(std::move(cipher)),
-      m_ghash(std::make_unique<GHASH>()),
-      m_aad_buf(GCM_BS),
-      m_H(GCM_BS),
-      m_aad_buf_pos(0),
-      m_initialized(false) {}
+      m_cipher(std::move(cipher)), m_ghash(std::make_unique<GHASH>()), m_H(GCM_BS), m_initialized(false) {}
 
 void GMAC::clear() {
    m_cipher->clear();
    m_ghash->clear();
-   zeroise(m_aad_buf);
+   m_aad_buf.clear();
    zeroise(m_H);
-   m_aad_buf_pos = 0;
    m_initialized = false;
 }
 
@@ -50,25 +44,17 @@ size_t GMAC::output_length() const {
 void GMAC::add_data(std::span<const uint8_t> input) {
    BufferSlicer in(input);
 
-   if(m_aad_buf_pos > 0) {
-      const auto part = in.take(std::min(GCM_BS - m_aad_buf_pos, in.remaining()));
-      copy_mem(&m_aad_buf[m_aad_buf_pos], part.data(), part.size());
-      m_aad_buf_pos += part.size();
-
-      if(m_aad_buf_pos == GCM_BS) {
-         m_ghash->update_associated_data(m_aad_buf.data(), GCM_BS);
-         m_aad_buf_pos = 0;
+   while(!in.empty()) {
+      if(const auto one_block = m_aad_buf.handle_unaligned_data(in)) {
+         m_ghash->update_associated_data(one_block.value());
       }
-   }
 
-   const size_t left_over = in.remaining() % GCM_BS;
-   const size_t full_blocks = in.remaining() - left_over;
-   m_ghash->update_associated_data(in.take(full_blocks).data(), full_blocks);
-
-   if(!in.empty()) {
-      const auto remaining = in.take(in.remaining());
-      copy_mem(&m_aad_buf[m_aad_buf_pos], remaining.data(), remaining.size());
-      m_aad_buf_pos += remaining.size();
+      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);
+         }
+      }
    }
 }
 
@@ -84,20 +70,20 @@ void GMAC::key_schedule(std::span<const uint8_t> key) {
    m_ghash->set_key(m_H);
 }
 
-void GMAC::start_msg(const uint8_t nonce[], size_t nonce_len) {
+void GMAC::start_msg(std::span<const uint8_t> nonce) {
    secure_vector<uint8_t> y0(GCM_BS);
 
-   if(nonce_len == 12) {
-      copy_mem(y0.data(), nonce, nonce_len);
+   if(nonce.size() == 12) {
+      copy_mem(y0.data(), nonce.data(), nonce.size());
       y0[GCM_BS - 1] = 1;
    } else {
-      m_ghash->ghash_update(y0, nonce, nonce_len);
-      m_ghash->add_final_block(y0, 0, nonce_len);
+      m_ghash->ghash_update(y0, nonce);
+      m_ghash->add_final_block(y0, 0, nonce.size());
    }
 
    secure_vector<uint8_t> m_enc_y0(GCM_BS);
    m_cipher->encrypt(y0.data(), m_enc_y0.data());
-   m_ghash->start(m_enc_y0.data(), m_enc_y0.size());
+   m_ghash->start(m_enc_y0);
    m_initialized = true;
 }
 
@@ -109,15 +95,14 @@ void GMAC::final_result(std::span<uint8_t> mac) {
       throw Invalid_State("GMAC was not used with a fresh nonce");
    }
 
-   // process the rest of the aad buffer. Even if it is a partial block only
-   // ghash_update will process it properly.
-   if(m_aad_buf_pos > 0) {
-      m_ghash->update_associated_data(m_aad_buf.data(), m_aad_buf_pos);
+   // 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.data(), output_length());
+   m_ghash->final(mac.first(output_length()));
    m_ghash->set_key(m_H);
-   m_aad_buf_pos = 0;
+   m_aad_buf.clear();
 }
 
 std::unique_ptr<MessageAuthenticationCode> GMAC::new_object() const {
diff --git a/src/lib/mac/gmac/gmac.h b/src/lib/mac/gmac/gmac.h
index e3ad7c9e1..a5ddaa281 100644
--- a/src/lib/mac/gmac/gmac.h
+++ b/src/lib/mac/gmac/gmac.h
@@ -10,6 +10,7 @@
 #define BOTAN_GMAC_H_
 
 #include <botan/mac.h>
+#include <botan/internal/alignment_buffer.h>
 
 namespace Botan {
 
@@ -48,15 +49,14 @@ class GMAC final : public MessageAuthenticationCode {
    private:
       void add_data(std::span<const uint8_t>) override;
       void final_result(std::span<uint8_t>) override;
-      void start_msg(const uint8_t nonce[], size_t nonce_len) override;
+      void start_msg(std::span<const uint8_t> nonce) override;
       void key_schedule(std::span<const uint8_t> key) override;
 
       static const size_t GCM_BS = 16;
       std::unique_ptr<BlockCipher> m_cipher;
       std::unique_ptr<GHASH> m_ghash;
-      secure_vector<uint8_t> m_aad_buf;
+      AlignmentBuffer<uint8_t, GCM_BS> m_aad_buf;
       secure_vector<uint8_t> m_H;
-      size_t m_aad_buf_pos;
       bool m_initialized;
 };
 
diff --git a/src/lib/mac/kmac/kmac.cpp b/src/lib/mac/kmac/kmac.cpp
index 0c4ad3f8b..437aa2f24 100644
--- a/src/lib/mac/kmac/kmac.cpp
+++ b/src/lib/mac/kmac/kmac.cpp
@@ -51,9 +51,9 @@ std::string KMAC::provider() const {
    return m_cshake->provider();
 }
 
-void KMAC::start_msg(const uint8_t nonce[], size_t nonce_len) {
+void KMAC::start_msg(std::span<const uint8_t> nonce) {
    assert_key_material_set();
-   m_cshake->start({nonce, nonce_len}, {});
+   m_cshake->start(nonce);
    m_cshake->update(m_encoded_key);
    m_message_started = true;
 }
diff --git a/src/lib/mac/kmac/kmac.h b/src/lib/mac/kmac/kmac.h
index 9d799ea1d..8f3a95396 100644
--- a/src/lib/mac/kmac/kmac.h
+++ b/src/lib/mac/kmac/kmac.h
@@ -34,7 +34,7 @@ class KMAC : public MessageAuthenticationCode {
       std::string provider() const final;
 
    private:
-      void start_msg(const uint8_t nonce[], size_t nonce_len) final;
+      void start_msg(std::span<const uint8_t> nonce) final;
       void add_data(std::span<const uint8_t>) final;
       void final_result(std::span<uint8_t>) final;
       void key_schedule(std::span<const uint8_t>) final;
diff --git a/src/lib/mac/mac.cpp b/src/lib/mac/mac.cpp
index 96f6f31ca..c05cdb901 100644
--- a/src/lib/mac/mac.cpp
+++ b/src/lib/mac/mac.cpp
@@ -152,24 +152,24 @@ std::unique_ptr<MessageAuthenticationCode> MessageAuthenticationCode::create_or_
    throw Lookup_Error("MAC", algo, provider);
 }
 
-void MessageAuthenticationCode::start_msg(const uint8_t nonce[], size_t nonce_len) {
+void MessageAuthenticationCode::start_msg(std::span<const uint8_t> nonce) {
    BOTAN_UNUSED(nonce);
-   if(nonce_len > 0) {
-      throw Invalid_IV_Length(name(), nonce_len);
+   if(!nonce.empty()) {
+      throw Invalid_IV_Length(name(), nonce.size());
    }
 }
 
 /*
 * Default (deterministic) MAC verification operation
 */
-bool MessageAuthenticationCode::verify_mac_result(const uint8_t mac[], size_t length) {
+bool MessageAuthenticationCode::verify_mac_result(std::span<const uint8_t> mac) {
    secure_vector<uint8_t> our_mac = final();
 
-   if(our_mac.size() != length) {
+   if(our_mac.size() != mac.size()) {
       return false;
    }
 
-   return constant_time_compare(our_mac.data(), mac, length);
+   return constant_time_compare(our_mac.data(), mac.data(), mac.size());
 }
 
 }  // namespace Botan
diff --git a/src/lib/mac/mac.h b/src/lib/mac/mac.h
index 14c660e23..65dc91bcc 100644
--- a/src/lib/mac/mac.h
+++ b/src/lib/mac/mac.h
@@ -62,19 +62,19 @@ class BOTAN_PUBLIC_API(2, 0) MessageAuthenticationCode : public Buffered_Computa
       *
       * @param nonce the message nonce bytes
       */
-      void start(std::span<const uint8_t> nonce) { start_msg(nonce.data(), nonce.size()); }
+      void start(std::span<const uint8_t> nonce) { start_msg(nonce); }
 
       /**
       * Begin processing a message.
       * @param nonce the per message nonce
       * @param nonce_len length of nonce
       */
-      void start(const uint8_t nonce[], size_t nonce_len) { start_msg(nonce, nonce_len); }
+      void start(const uint8_t nonce[], size_t nonce_len) { start_msg({nonce, nonce_len}); }
 
       /**
       * Begin processing a message.
       */
-      void start() { return start_msg(nullptr, 0); }
+      void start() { return start_msg({}); }
 
       /**
       * Verify a MAC.
@@ -82,14 +82,14 @@ class BOTAN_PUBLIC_API(2, 0) MessageAuthenticationCode : public Buffered_Computa
       * @param length the length of param in
       * @return true if the MAC is valid, false otherwise
       */
-      bool verify_mac(const uint8_t in[], size_t length) { return verify_mac_result(in, length); }
+      bool verify_mac(const uint8_t in[], size_t length) { return verify_mac_result({in, length}); }
 
       /**
       * Verify a MAC.
       * @param in the MAC to verify as a byte array
       * @return true if the MAC is valid, false otherwise
       */
-      bool verify_mac(std::span<const uint8_t> in) { return verify_mac_result(in.data(), in.size()); }
+      bool verify_mac(std::span<const uint8_t> in) { return verify_mac_result(in); }
 
       /**
       * @return new object representing the same algorithm as *this
@@ -122,12 +122,12 @@ class BOTAN_PUBLIC_API(2, 0) MessageAuthenticationCode : public Buffered_Computa
       * If the MAC does not support nonces, it should not override the default
       * implementation.
       */
-      virtual void start_msg(const uint8_t nonce[], size_t nonce_len);
+      virtual void start_msg(std::span<const uint8_t> nonce);
 
       /**
       * Verify the MACs final result
       */
-      virtual bool verify_mac_result(const uint8_t in[], size_t length);
+      virtual bool verify_mac_result(std::span<const uint8_t> in);
 };
 
 typedef MessageAuthenticationCode MAC;
diff --git a/src/lib/modes/aead/gcm/gcm.cpp b/src/lib/modes/aead/gcm/gcm.cpp
index 71135f86d..ec780c5fa 100644
--- a/src/lib/modes/aead/gcm/gcm.cpp
+++ b/src/lib/modes/aead/gcm/gcm.cpp
@@ -13,6 +13,8 @@
 #include <botan/internal/fmt.h>
 #include <botan/internal/ghash.h>
 
+#include <array>
+
 namespace Botan {
 
 /*
@@ -88,7 +90,7 @@ void GCM_Mode::key_schedule(std::span<const uint8_t> key) {
 
 void GCM_Mode::set_associated_data_n(size_t idx, std::span<const uint8_t> ad) {
    BOTAN_ARG_CHECK(idx == 0, "GCM: cannot handle non-zero index in set_associated_data_n");
-   m_ghash->set_associated_data(ad.data(), ad.size());
+   m_ghash->set_associated_data(ad);
 }
 
 void GCM_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
@@ -106,7 +108,7 @@ void GCM_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
       copy_mem(m_y0.data(), nonce, nonce_len);
       m_y0[15] = 1;
    } else {
-      m_ghash->nonce_hash(m_y0, nonce, nonce_len);
+      m_ghash->nonce_hash(m_y0, {nonce, nonce_len});
    }
 
    m_ctr->set_iv(m_y0.data(), m_y0.size());
@@ -114,14 +116,14 @@ void GCM_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
    clear_mem(m_y0.data(), m_y0.size());
    m_ctr->encipher(m_y0);
 
-   m_ghash->start(m_y0.data(), m_y0.size());
+   m_ghash->start(m_y0);
    clear_mem(m_y0.data(), m_y0.size());
 }
 
 size_t GCM_Encryption::process_msg(uint8_t buf[], size_t sz) {
    BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid buffer size");
    m_ctr->cipher(buf, buf, sz);
-   m_ghash->update(buf, sz);
+   m_ghash->update({buf, sz});
    return sz;
 }
 
@@ -131,16 +133,16 @@ void GCM_Encryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
    uint8_t* buf = buffer.data() + offset;
 
    m_ctr->cipher(buf, buf, sz);
-   m_ghash->update(buf, sz);
+   m_ghash->update({buf, sz});
 
-   uint8_t mac[16] = {0};
-   m_ghash->final(mac, tag_size());
-   buffer += std::make_pair(mac, tag_size());
+   std::array<uint8_t, 16> mac = {0};
+   m_ghash->final(std::span(mac).first(tag_size()));
+   buffer += std::make_pair(mac.data(), tag_size());
 }
 
 size_t GCM_Decryption::process_msg(uint8_t buf[], size_t sz) {
    BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid buffer size");
-   m_ghash->update(buf, sz);
+   m_ghash->update({buf, sz});
    m_ctr->cipher(buf, buf, sz);
    return sz;
 }
@@ -156,16 +158,16 @@ void GCM_Decryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
 
    // handle any final input before the tag
    if(remaining) {
-      m_ghash->update(buf, remaining);
+      m_ghash->update({buf, remaining});
       m_ctr->cipher(buf, buf, remaining);
    }
 
-   uint8_t mac[16] = {0};
-   m_ghash->final(mac, tag_size());
+   std::array<uint8_t, 16> mac = {0};
+   m_ghash->final(std::span(mac).first(tag_size()));
 
    const uint8_t* included_tag = &buffer[remaining + offset];
 
-   if(!constant_time_compare(mac, included_tag, tag_size())) {
+   if(!constant_time_compare(mac.data(), included_tag, tag_size())) {
       throw Invalid_Authentication_Tag("GCM tag check failed");
    }
 
diff --git a/src/lib/utils/alignment_buffer.h b/src/lib/utils/alignment_buffer.h
index 8f097883d..87e1a6214 100644
--- a/src/lib/utils/alignment_buffer.h
+++ b/src/lib/utils/alignment_buffer.h
@@ -201,6 +201,17 @@ class AlignmentBuffer {
          return m_buffer;
       }
 
+      /**
+       * Explicitly consumes however many bytes are currently stored in the
+       * buffer. After consumption, the buffer is cleared and ready to collect
+       * new data.
+       */
+      [[nodiscard]] std::span<const T> consume_partial() {
+         const auto elements = elements_in_buffer();
+         m_position = 0;
+         return std::span(m_buffer).first(elements);
+      }
+
       constexpr size_t size() const { return m_buffer.size(); }
 
       size_t elements_in_buffer() const { return m_position; }
diff --git a/src/lib/utils/ghash/ghash.cpp b/src/lib/utils/ghash/ghash.cpp
index 178fd126d..38604afdb 100644
--- a/src/lib/utils/ghash/ghash.cpp
+++ b/src/lib/utils/ghash/ghash.cpp
@@ -31,17 +31,17 @@ std::string GHASH::provider() const {
    return "base";
 }
 
-void GHASH::ghash_multiply(secure_vector<uint8_t>& x, const uint8_t input[], size_t blocks) {
+void GHASH::ghash_multiply(secure_vector<uint8_t>& x, std::span<const uint8_t> input, size_t blocks) {
 #if defined(BOTAN_HAS_GHASH_CLMUL_CPU)
    if(CPUID::has_carryless_multiply()) {
       BOTAN_ASSERT_NOMSG(!m_H_pow.empty());
-      return ghash_multiply_cpu(x.data(), m_H_pow.data(), input, blocks);
+      return ghash_multiply_cpu(x.data(), m_H_pow.data(), input.data(), blocks);
    }
 #endif
 
 #if defined(BOTAN_HAS_GHASH_CLMUL_VPERM)
    if(CPUID::has_vperm()) {
-      return ghash_multiply_vperm(x.data(), m_HM.data(), input, blocks);
+      return ghash_multiply_vperm(x.data(), m_HM.data(), input.data(), blocks);
    }
 #endif
 
@@ -52,8 +52,8 @@ void GHASH::ghash_multiply(secure_vector<uint8_t>& x, const uint8_t input[], siz
    uint64_t X[2] = {load_be<uint64_t>(x.data(), 0), load_be<uint64_t>(x.data(), 1)};
 
    for(size_t b = 0; b != blocks; ++b) {
-      X[0] ^= load_be<uint64_t>(input, 2 * b);
-      X[1] ^= load_be<uint64_t>(input, 2 * b + 1);
+      X[0] ^= load_be<uint64_t>(input.data(), 2 * b);
+      X[1] ^= load_be<uint64_t>(input.data(), 2 * b + 1);
 
       uint64_t Z[2] = {0, 0};
 
@@ -78,7 +78,7 @@ void GHASH::ghash_multiply(secure_vector<uint8_t>& x, const uint8_t input[], siz
    CT::unpoison(x.data(), x.size());
 }
 
-void GHASH::ghash_update(secure_vector<uint8_t>& ghash, const uint8_t input[], size_t length) {
+void GHASH::ghash_update(secure_vector<uint8_t>& ghash, std::span<const uint8_t> input) {
    assert_key_material_set(!m_H.empty());
 
    /*
@@ -86,16 +86,16 @@ void GHASH::ghash_update(secure_vector<uint8_t>& ghash, const uint8_t input[], s
    final block and should pad with zeros
    */
 
-   const size_t full_blocks = length / GCM_BS;
-   const size_t final_bytes = length - (full_blocks * GCM_BS);
+   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, full_blocks);
+      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 + full_blocks * GCM_BS, final_bytes);
+      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);
    }
@@ -143,33 +143,33 @@ void GHASH::key_schedule(std::span<const uint8_t> key) {
 #endif
 }
 
-void GHASH::start(const uint8_t nonce[], size_t len) {
-   BOTAN_ARG_CHECK(len == 16, "GHASH requires a 128-bit nonce");
-   m_nonce.assign(nonce, nonce + len);
+void GHASH::start(std::span<const uint8_t> 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;
 }
 
-void GHASH::set_associated_data(const uint8_t input[], size_t length) {
+void GHASH::set_associated_data(std::span<const uint8_t> input) {
    if(m_ghash.empty() == false) {
       throw Invalid_State("Too late to set AD in GHASH");
    }
 
    zeroise(m_H_ad);
 
-   ghash_update(m_H_ad, input, length);
-   m_ad_len = length;
+   ghash_update(m_H_ad, input);
+   m_ad_len = input.size();
 }
 
-void GHASH::update_associated_data(const uint8_t ad[], size_t length) {
+void GHASH::update_associated_data(std::span<const uint8_t> ad) {
    assert_key_material_set();
-   m_ad_len += length;
-   ghash_update(m_ghash, ad, length);
+   m_ad_len += ad.size();
+   ghash_update(m_ghash, ad);
 }
 
-void GHASH::update(const uint8_t input[], size_t length) {
+void GHASH::update(std::span<const uint8_t> input) {
    assert_key_material_set();
-   m_text_len += length;
-   ghash_update(m_ghash, input, length);
+   m_text_len += input.size();
+   ghash_update(m_ghash, input);
 }
 
 void GHASH::add_final_block(secure_vector<uint8_t>& hash, size_t ad_len, size_t text_len) {
@@ -179,16 +179,16 @@ void GHASH::add_final_block(secure_vector<uint8_t>& hash, size_t ad_len, size_t
    */
    uint8_t final_block[GCM_BS];
    store_be<uint64_t>(final_block, 8 * ad_len, 8 * text_len);
-   ghash_update(hash, final_block, GCM_BS);
+   ghash_update(hash, {final_block, GCM_BS});
 }
 
-void GHASH::final(uint8_t mac[], size_t mac_len) {
-   BOTAN_ARG_CHECK(mac_len > 0 && mac_len <= 16, "GHASH output length");
+void GHASH::final(std::span<uint8_t> mac) {
+   BOTAN_ARG_CHECK(!mac.empty() && mac.size() <= 16, "GHASH output length");
 
    assert_key_material_set();
    add_final_block(m_ghash, m_ad_len, m_text_len);
 
-   for(size_t i = 0; i != mac_len; ++i) {
+   for(size_t i = 0; i != mac.size(); ++i) {
       mac[i] = m_ghash[i] ^ m_nonce[i];
    }
 
@@ -196,11 +196,11 @@ void GHASH::final(uint8_t mac[], size_t mac_len) {
    m_text_len = 0;
 }
 
-void GHASH::nonce_hash(secure_vector<uint8_t>& y0, const uint8_t nonce[], size_t nonce_len) {
+void GHASH::nonce_hash(secure_vector<uint8_t>& y0, std::span<const uint8_t> nonce) {
    BOTAN_ASSERT(m_ghash.empty(), "nonce_hash called during wrong time");
 
-   ghash_update(y0, nonce, nonce_len);
-   add_final_block(y0, 0, nonce_len);
+   ghash_update(y0, nonce);
+   add_final_block(y0, 0, nonce.size());
 }
 
 void GHASH::clear() {
diff --git a/src/lib/utils/ghash/ghash.h b/src/lib/utils/ghash/ghash.h
index 2545d8666..f75b37e47 100644
--- a/src/lib/utils/ghash/ghash.h
+++ b/src/lib/utils/ghash/ghash.h
@@ -17,23 +17,23 @@ namespace Botan {
 */
 class GHASH final : public SymmetricAlgorithm {
    public:
-      void set_associated_data(const uint8_t ad[], size_t ad_len);
+      void set_associated_data(std::span<const uint8_t> ad);
 
-      void nonce_hash(secure_vector<uint8_t>& y0, const uint8_t nonce[], size_t len);
+      void nonce_hash(secure_vector<uint8_t>& y0, std::span<const uint8_t> nonce);
 
-      void start(const uint8_t nonce[], size_t len);
+      void start(std::span<const uint8_t> nonce);
 
       /*
       * Assumes input len is multiple of 16
       */
-      void update(const uint8_t in[], size_t len);
+      void update(std::span<const uint8_t> in);
 
       /*
       * Incremental update of associated data
       */
-      void update_associated_data(const uint8_t ad[], size_t len);
+      void update_associated_data(std::span<const uint8_t> ad);
 
-      void final(uint8_t out[], size_t out_len);
+      void final(std::span<uint8_t> out);
 
       Key_Length_Specification key_spec() const override { return Key_Length_Specification(16); }
 
@@ -47,7 +47,7 @@ class GHASH final : public SymmetricAlgorithm {
 
       std::string provider() const;
 
-      void ghash_update(secure_vector<uint8_t>& x, const uint8_t input[], size_t input_len);
+      void ghash_update(secure_vector<uint8_t>& x, std::span<const uint8_t> input);
 
       void add_final_block(secure_vector<uint8_t>& x, size_t ad_len, size_t pt_len);
 
@@ -64,7 +64,7 @@ class GHASH final : public SymmetricAlgorithm {
 
       void key_schedule(std::span<const uint8_t> key) override;
 
-      void ghash_multiply(secure_vector<uint8_t>& x, const uint8_t input[], size_t blocks);
+      void ghash_multiply(secure_vector<uint8_t>& x, std::span<const uint8_t> input, size_t blocks);
 
       static const size_t GCM_BS = 16;
 


Test patch:
diff --git a/src/tests/test_utils_buffer.cpp b/src/tests/test_utils_buffer.cpp
index 9c824ec9c..131a2f002 100644
--- a/src/tests/test_utils_buffer.cpp
+++ b/src/tests/test_utils_buffer.cpp
@@ -212,6 +212,28 @@ std::vector<Test::Result> test_alignment_buffer() {
                result.test_is_eq("in == out", v(data), v(out));
             }),
 
+      CHECK("Consume partially filled Alignment Buffer",
+            [=](auto& result) {
+               Botan::AlignmentBuffer<uint8_t, 32> b;
+
+               result.require("!ready_to_consume()", !b.ready_to_consume());
+               const auto out_empty = b.consume_partial();
+               result.test_eq("consuming nothing resets buffer", b.elements_in_buffer(), 0);
+               result.confirm("consumed empty buffer", out_empty.empty());
+
+               b.append(first_half_data);
+               result.require("!ready_to_consume()", !b.ready_to_consume());
+               const auto out_half = b.consume_partial();
+               result.test_eq("consumed half-data resets buffer", b.elements_in_buffer(), 0);
+               result.test_is_eq("half-in == half-out", v(out_half), v(first_half_data));
+
+               b.append(data);
+               result.require("ready_to_consume()", b.ready_to_consume());
+               const auto out_full = b.consume_partial();
+               result.test_eq("consumed full-data resets buffer", b.elements_in_buffer(), 0);
+               result.test_is_eq("full-in == full-out", v(out_full), v(data));
+            }),
+
       CHECK("Clear Alignment Buffer",
             [=](auto& result) {
                Botan::AlignmentBuffer<uint8_t, 32> b;