From 9ad816a5d8d74105558640b2f37baec50d8b920f Mon Sep 17 00:00:00 2001 From: Matthias Gierlings Date: Thu, 27 Oct 2016 19:00:23 +0200 Subject: Implements GMAC with GHASH. Adds support for Galois Message Authentication Code calculation based on GHASH, rather than GCM_Mode. --- src/build-data/policy/bsi.txt | 1 + src/lib/mac/gmac/gmac.cpp | 107 +++++++++++++++++++++++++++++------------ src/lib/mac/gmac/gmac.h | 35 ++++++++------ src/lib/mac/mac.cpp | 11 +++++ src/lib/modes/aead/gcm/gcm.cpp | 4 +- src/lib/modes/aead/gcm/gcm.h | 102 +++++++++++++++++++-------------------- src/tests/test_mac.cpp | 30 ++++++++++++ 7 files changed, 192 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/build-data/policy/bsi.txt b/src/build-data/policy/bsi.txt index 7295f84d4..62be245d0 100644 --- a/src/build-data/policy/bsi.txt +++ b/src/build-data/policy/bsi.txt @@ -18,6 +18,7 @@ sha3 # mac cmac hmac +gmac # kdf kdf1_iso18033 diff --git a/src/lib/mac/gmac/gmac.cpp b/src/lib/mac/gmac/gmac.cpp index 5d82456ae..946e22cf0 100644 --- a/src/lib/mac/gmac/gmac.cpp +++ b/src/lib/mac/gmac/gmac.cpp @@ -9,27 +9,22 @@ namespace Botan { -GMAC* GMAC::make(const Spec& spec) - { - if(spec.arg_count() == 1) - { - if(auto bc = BlockCipher::create(spec.arg(0))) - return new GMAC(bc.release()); - } - return nullptr; - } - GMAC::GMAC(BlockCipher* cipher) - : m_iv(), m_aad(), - m_gcm(GCM_Encryption(cipher)), m_cipher(cipher->clone()) - { - } + : GHASH(), + m_aad_buf(), + m_cipher(cipher), + m_initialized(false) + {} void GMAC::clear() { - m_gcm.clear(); - zeroise(m_iv); - zeroise(m_aad); + GHASH::clear(); + m_H.resize(GCM_BS); + m_H_ad.resize(GCM_BS); + m_ghash.resize(GCM_BS); + m_cipher->clear(); + m_aad_buf.clear(); + m_initialized = false; } std::string GMAC::name() const @@ -39,38 +34,90 @@ std::string GMAC::name() const size_t GMAC::output_length() const { - return m_gcm.tag_size(); + return GCM_BS; + } + +void GMAC::add_data(const byte input[], size_t size) + { + m_ad_len += size; + + // buffer partial blocks till we received a full input block + // or final is called. + m_aad_buf.insert(m_aad_buf.end(), input, input + size); + if(m_aad_buf.size() >= GCM_BS) + { + // process all complete input blocks. + ghash_update(m_ghash, + m_aad_buf.data(), + m_aad_buf.size() - (m_aad_buf.size() % GCM_BS)); + + // remove all processed blocks from buffer. + m_aad_buf.erase(m_aad_buf.begin(), + m_aad_buf.end() - (m_aad_buf.size() % GCM_BS)); + } } -void GMAC::add_data(const byte input[], size_t length) +void GMAC::key_schedule(const byte key[], size_t size) { - m_aad.insert(m_aad.end(), input, input + length); + clear(); + m_cipher->set_key(key, size); + m_cipher->encrypt(m_H_ad.data(), m_H.data()); } void GMAC::start(const std::vector& nonce) { - m_iv.assign(nonce.begin(), nonce.end()); + start(nonce.data(), nonce.size()); } void GMAC::start(const secure_vector& nonce) { - m_iv.assign(nonce.begin(), nonce.end()); + start(nonce.data(), nonce.size()); + } + +void GMAC::start(const byte nonce[], size_t nonce_len) + { + secure_vector y0(GCM_BS); + + if(nonce_len == 12) + { + copy_mem(y0.data(), nonce, nonce_len); + y0[GCM_BS - 1] = 1; + } + else + { + ghash_update(y0, nonce, nonce_len); + add_final_block(y0, 0, nonce_len); + } + + secure_vector m_enc_y0(GCM_BS); + m_cipher->encrypt(y0.data(), m_enc_y0.data()); + GHASH::start(m_enc_y0.data(), m_enc_y0.size()); + m_initialized = true; } void GMAC::final_result(byte mac[]) { - secure_vector result; - m_gcm.set_associated_data(m_aad.data(), m_aad.size()); - m_gcm.start(m_iv); - m_gcm.finish(result); + // This ensures the GMAC computation has been initialized with a fresh + // nonce. The aim of this check is to prevent developers from re-using + // nonces (and potential nonce-reuse attacks). + BOTAN_ASSERT(m_initialized, + "The GMAC computation has not been initialized 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.size() > 0) + { + ghash_update(m_ghash, + m_aad_buf.data(), + m_aad_buf.size()); + } + secure_vector result = GHASH::final(); std::copy(result.begin(), result.end(), mac); - - zeroise(m_aad); - m_aad.clear(); + clear(); } MessageAuthenticationCode* GMAC::clone() const { - return new GMAC(m_cipher->clone()); + return new GMAC(BlockCipher::create(m_cipher->name()).release()); } } diff --git a/src/lib/mac/gmac/gmac.h b/src/lib/mac/gmac/gmac.h index d83236b32..b651c2e11 100644 --- a/src/lib/mac/gmac/gmac.h +++ b/src/lib/mac/gmac/gmac.h @@ -8,9 +8,9 @@ #ifndef BOTAN_GMAC_H__ #define BOTAN_GMAC_H__ -#include -#include #include +#include +#include #include namespace Botan { @@ -18,7 +18,9 @@ namespace Botan { /** * GMAC */ -class BOTAN_DLL GMAC : public MessageAuthenticationCode +class BOTAN_DLL GMAC : public MessageAuthenticationCode, + public GHASH + { public: void clear() override; @@ -26,6 +28,15 @@ class BOTAN_DLL GMAC : public MessageAuthenticationCode size_t output_length() const override; MessageAuthenticationCode* clone() const override; + /** + * Must be called to set the initialization vector prior to GMAC + * calculation. + * + * @param nonce Initialization vector. + * @param nonce_len size of initialization vector. + */ + void start(const byte nonce[], size_t nonce_len); + /** * Must be called to set the initialization vector prior to GMAC * calculation. @@ -44,7 +55,7 @@ class BOTAN_DLL GMAC : public MessageAuthenticationCode Key_Length_Specification key_spec() const { - return m_gcm.key_spec(); + return m_cipher->key_spec(); } /** @@ -54,23 +65,19 @@ class BOTAN_DLL GMAC : public MessageAuthenticationCode */ explicit GMAC(BlockCipher* cipher); - static GMAC* make(const Spec& spec); - GMAC(const GMAC&) = delete; GMAC& operator=(const GMAC&) = delete; + private: void add_data(const byte[], size_t) override; void final_result(byte[]) override; + void start_msg(const byte nonce[], size_t nonce_len); + void key_schedule(const byte key[], size_t size) override; - void key_schedule(const byte key[], size_t size) override - { - m_gcm.set_key(key, size); - } - - secure_vector m_iv; - secure_vector m_aad; - GCM_Encryption m_gcm; + static const size_t GCM_BS = 16; + secure_vector m_aad_buf; std::unique_ptr m_cipher; + bool m_initialized; }; } diff --git a/src/lib/mac/mac.cpp b/src/lib/mac/mac.cpp index f2c5557c7..2fa321a67 100644 --- a/src/lib/mac/mac.cpp +++ b/src/lib/mac/mac.cpp @@ -45,6 +45,17 @@ MessageAuthenticationCode::create(const std::string& algo_spec, { const SCAN_Name req(algo_spec); +#if defined(BOTAN_HAS_GMAC) + if(req.algo_name() == "GMAC" && req.arg_count() == 1) + { + if(provider.empty() || provider == "base") + { + if(auto bc = BlockCipher::create(req.arg(0))) + return std::unique_ptr(new GMAC(bc.release())); + } + } +#endif + #if defined(BOTAN_HAS_HMAC) if(req.algo_name() == "HMAC" && req.arg_count() == 1) { diff --git a/src/lib/modes/aead/gcm/gcm.cpp b/src/lib/modes/aead/gcm/gcm.cpp index 6e1bd82f7..a73e5ee5b 100644 --- a/src/lib/modes/aead/gcm/gcm.cpp +++ b/src/lib/modes/aead/gcm/gcm.cpp @@ -93,9 +93,9 @@ void GHASH::key_schedule(const byte key[], size_t length) m_text_len = 0; } -void GHASH::start(const secure_vector& nonce) +void GHASH::start(const byte nonce[], size_t len) { - m_nonce = nonce; + m_nonce.assign(nonce, nonce + len); m_ghash = m_H_ad; } diff --git a/src/lib/modes/aead/gcm/gcm.h b/src/lib/modes/aead/gcm/gcm.h index 964bd5062..6468cbd9c 100644 --- a/src/lib/modes/aead/gcm/gcm.h +++ b/src/lib/modes/aead/gcm/gcm.h @@ -14,52 +14,7 @@ namespace Botan { -/** -* GCM's GHASH -* Maybe a Transform? -*/ -class BOTAN_DLL GHASH : public SymmetricAlgorithm - { - public: - void set_associated_data(const byte ad[], size_t ad_len); - - secure_vector nonce_hash(const byte nonce[], size_t len); - - void start(const secure_vector& nonce); - - /* - * Assumes input len is multiple of 16 - */ - void update(const byte in[], size_t len); - - secure_vector final(); - - Key_Length_Specification key_spec() const override - { return Key_Length_Specification(16); } - - size_t input_size() const { return m_text_len; } - - void clear() override; - - std::string name() const override { return "GHASH"; } - private: - void key_schedule(const byte key[], size_t key_len) override; - - void gcm_multiply(secure_vector& x) const; - - void ghash_update(secure_vector& x, - const byte input[], size_t input_len); - - void add_final_block(secure_vector& x, - size_t ad_len, size_t pt_len); - - secure_vector m_H; - secure_vector m_H_ad; - secure_vector m_nonce; - secure_vector m_ghash; - size_t m_ad_len = 0, m_text_len = 0; - }; - +class GHASH; /** * GCM Mode @@ -73,8 +28,6 @@ class BOTAN_DLL GCM_Mode : public AEAD_Mode size_t update_granularity() const override; - void update(secure_vector& blocks, size_t offset = 0) override = 0; - Key_Length_Specification key_spec() const override; // GCM supports arbitrary nonce lengths @@ -82,8 +35,6 @@ class BOTAN_DLL GCM_Mode : public AEAD_Mode size_t tag_size() const override { return m_tag_size; } - size_t input_size() const { return m_ghash->input_size(); } - void clear() override; std::string provider() const override; @@ -100,9 +51,7 @@ class BOTAN_DLL GCM_Mode : public AEAD_Mode private: void start_msg(const byte nonce[], size_t nonce_len) override; - private: void key_schedule(const byte key[], size_t length) override; - secure_vector start_raw(const byte nonce[], size_t nonce_len) override; }; /** @@ -153,5 +102,54 @@ class BOTAN_DLL GCM_Decryption final : public GCM_Mode void finish(secure_vector& final_block, size_t offset = 0) override; }; + +/** +* GCM's GHASH +* Maybe a Transform? +*/ +class BOTAN_DLL GHASH : public SymmetricAlgorithm + { + public: + void set_associated_data(const byte ad[], size_t ad_len); + + secure_vector nonce_hash(const byte nonce[], size_t len); + + void start(const byte nonce[], size_t len); + + /* + * Assumes input len is multiple of 16 + */ + void update(const byte in[], size_t len); + + secure_vector final(); + + Key_Length_Specification key_spec() const override + { return Key_Length_Specification(16); } + + void clear() override; + + std::string name() const override { return "GHASH"; } + protected: + void ghash_update(secure_vector& x, + const byte input[], size_t input_len); + + void add_final_block(secure_vector& x, + size_t ad_len, size_t pt_len); + + secure_vector m_H; + secure_vector m_H_ad; + secure_vector m_ghash; + size_t m_ad_len = 0; + + private: + void key_schedule(const byte key[], size_t key_len) override; + + void gcm_multiply(secure_vector& x) const; + + secure_vector m_nonce; + size_t m_text_len = 0; + }; + } + #endif diff --git a/src/tests/test_mac.cpp b/src/tests/test_mac.cpp index 10236dfff..43b19b14e 100644 --- a/src/tests/test_mac.cpp +++ b/src/tests/test_mac.cpp @@ -69,8 +69,24 @@ class Message_Auth_Tests : public Text_Based_Test } #endif + mac->update(input); + + result.test_eq(provider, "correct mac", mac->final(), expected); + + // Test to make sure clear() resets what we need it to + mac->set_key( key ); + mac->update( "some discarded input"); + mac->clear(); + // do the same to test verify_mac() mac->set_key(key); +#if defined(BOTAN_HAS_GMAC) + // GMAC needs to set an IV + if(Botan::GMAC* gmac = dynamic_cast(mac.get())) + { + gmac->start(iv); + } +#endif mac->update(input); result.test_eq(provider + " correct mac", mac->verify_mac(expected.data(), expected.size()), true); @@ -78,6 +94,13 @@ class Message_Auth_Tests : public Text_Based_Test if(input.size() > 2) { mac->set_key(key); // Poly1305 requires the re-key +#if defined(BOTAN_HAS_GMAC) + // GMAC needs to set an IV + if(Botan::GMAC* gmac = dynamic_cast(mac.get())) + { + gmac->start(iv); + } +#endif mac->update(input[0]); mac->update(&input[1], input.size() - 2); mac->update(input[input.size()-1]); @@ -86,6 +109,13 @@ class Message_Auth_Tests : public Text_Based_Test // do the same to test verify_mac() mac->set_key(key); +#if defined(BOTAN_HAS_GMAC) + // GMAC needs to set an IV + if(Botan::GMAC* gmac = dynamic_cast(mac.get())) + { + gmac->start(iv); + } +#endif mac->update(input[ 0 ]); mac->update(&input[ 1 ], input.size() - 2); mac->update(input[ input.size() - 1 ]); -- cgit v1.2.3