diff options
-rw-r--r-- | checks/aead.cpp | 78 | ||||
-rw-r--r-- | checks/aead.vec | 12 | ||||
-rw-r--r-- | checks/hkdf.cpp | 44 | ||||
-rw-r--r-- | checks/tests.cpp | 43 | ||||
-rw-r--r-- | checks/validate.cpp | 1 | ||||
-rw-r--r-- | checks/validate.h | 7 | ||||
-rw-r--r-- | doc/relnotes/1_11_6.rst | 4 | ||||
-rw-r--r-- | src/modes/aead/aead.cpp | 17 | ||||
-rw-r--r-- | src/modes/aead/siv/info.txt | 3 | ||||
-rw-r--r-- | src/modes/aead/siv/siv.cpp | 183 | ||||
-rw-r--r-- | src/modes/aead/siv/siv.h | 114 |
11 files changed, 452 insertions, 54 deletions
diff --git a/checks/aead.cpp b/checks/aead.cpp new file mode 100644 index 000000000..3cb0fb986 --- /dev/null +++ b/checks/aead.cpp @@ -0,0 +1,78 @@ +#include "validate.h" + +#include <botan/hex.h> +#include <botan/siv.h> +#include <botan/aead.h> +#include <iostream> +#include <fstream> + +using namespace Botan; + +namespace { + +secure_vector<byte> aead(const std::string& algo, + Cipher_Dir dir, + const secure_vector<byte>& pt, + const secure_vector<byte>& nonce, + const secure_vector<byte>& ad, + const secure_vector<byte>& key) + { + std::unique_ptr<AEAD_Mode> aead(get_aead(algo, dir)); + + aead->set_key(&key[0], key.size()); + aead->start_vec(nonce); + aead->set_associated_data_vec(ad); + + secure_vector<byte> ct = pt; + aead->finish(ct); + + return ct; + } + +bool aead_test(const std::string& algo, + const std::string& pt, + const std::string& ct, + const std::string& nonce_hex, + const std::string& ad_hex, + const std::string& key_hex) + { + auto nonce = hex_decode_locked(nonce_hex); + auto ad = hex_decode_locked(ad_hex); + auto key = hex_decode_locked(key_hex); + + const std::string ct2 = hex_encode(aead(algo, + ENCRYPTION, + hex_decode_locked(pt), + nonce, + ad, + key)); + + if(ct != ct2) + std::cout << algo << " got ct " << ct2 << " expected " << ct << "\n"; + + const std::string pt2 = hex_encode(aead(algo, + DECRYPTION, + hex_decode_locked(ct), + nonce, + ad, + key)); + + if(pt != pt2) + std::cout << algo << " got pt " << pt2 << " expected " << pt << "\n"; + + return (ct == ct2) && (pt == pt2); + } + +} + +void test_aead() + { + std::ifstream vec("checks/aead.vec"); + + run_tests_bb(vec, "AEAD", "Ciphertext", true, + [](std::map<std::string, std::string> m) + { + return aead_test(m["AEAD"], m["Plaintext"], m["Ciphertext"], + m["Nonce"], m["AD"], m["Key"]); + }); + } diff --git a/checks/aead.vec b/checks/aead.vec new file mode 100644 index 000000000..4d9f39556 --- /dev/null +++ b/checks/aead.vec @@ -0,0 +1,12 @@ +AEAD = AES-128/SIV +Plaintext = 112233445566778899AABBCCDDEE +Key = FFFEFDFCFBFAF9F8F7F6F5F4F3F2F1F0F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF +AD = 101112131415161718191A1B1C1D1E1F2021222324252627 +Ciphertext = 85632D07C6E8F37F950ACD320A2ECC9340C02B9690C4DC04DAEF7F6AFE5C + +AEAD = AES-128/SIV +Plaintext = 7468697320697320736F6D6520706C61696E7465787420746F20656E6372797074207573696E67205349562D414553 +Key = 7F7E7D7C7B7A79787776757473727170404142434445464748494A4B4C4D4E4F +AD = 00112233445566778899AABBCCDDEEFFDEADDADADEADDADAFFEEDDCCBBAA99887766554433221100 +Nonce = 09F911029D74E35BD84156C5635688C1 +Ciphertext = E21A9D0FE3BD3ED189C71F29B24C39E1E40B9BAB82D428D0A9B392F13EA14C9B4433F393595A8E031F032350F50D2B21825B3EE64958103BD8445C3F48E5CF diff --git a/checks/hkdf.cpp b/checks/hkdf.cpp index ce93458d7..e63ff55fa 100644 --- a/checks/hkdf.cpp +++ b/checks/hkdf.cpp @@ -33,7 +33,7 @@ secure_vector<byte> hkdf(const std::string& algo, return key; } -void hkdf_test(const std::string& algo, +bool hkdf_test(const std::string& algo, const std::string& ikm, const std::string& salt, const std::string& info, @@ -50,40 +50,8 @@ void hkdf_test(const std::string& algo, if(got != okm) std::cout << "HKDF got " << got << " expected " << okm << std::endl; - } - -void run_tests(std::istream& src, - bool clear_between_cb, - const std::string& trigger_key, - std::function<void (std::map<std::string, std::string>)> cb) - { - std::map<std::string, std::string> vars; - - while(src.good()) - { - std::string line; - std::getline(src, line); - - if(line == "") - continue; - - // FIXME: strip # comments - - // FIXME: Do this right - - const std::string key = line.substr(0, line.find_first_of(' ')); - const std::string val = line.substr(line.find_last_of(' ') + 1, std::string::npos); - - vars[key] = val; - - if(key == trigger_key) - { - cb(vars); - if(clear_between_cb) - vars.clear(); - } - } + return (got == okm); } } @@ -93,10 +61,10 @@ void test_hkdf() // From RFC 5869 std::ifstream vec("checks/hkdf.vec"); - run_tests(vec, true, "OKM", - [](std::map<std::string, std::string> m) + run_tests_bb(vec, "HKDF", "OKM", true, + [](std::map<std::string, std::string> m) -> bool { - hkdf_test(m["Hash"], m["IKM"], m["salt"], m["info"], - m["OKM"], to_u32bit(m["L"])); + return hkdf_test(m["Hash"], m["IKM"], m["salt"], m["info"], + m["OKM"], to_u32bit(m["L"])); }); } diff --git a/checks/tests.cpp b/checks/tests.cpp index 2c6321415..fdaccd805 100644 --- a/checks/tests.cpp +++ b/checks/tests.cpp @@ -1,11 +1,11 @@ #include "validate.h" #include <iostream> -void run_tests(std::istream& src, - const std::string& name_key, - const std::string& output_key, - bool clear_between_cb, - std::function<std::string (std::map<std::string, std::string>)> cb) +void run_tests_bb(std::istream& src, + const std::string& name_key, + const std::string& output_key, + bool clear_between_cb, + std::function<bool (std::map<std::string, std::string>)> cb) { std::map<std::string, std::string> vars; size_t test_cnt = 0; @@ -20,9 +20,8 @@ void run_tests(std::istream& src, if(line == "") continue; - // FIXME: strip # comments - - // FIXME: Do this right + if(line[0] == '#') + continue; const std::string key = line.substr(0, line.find_first_of(' ')); const std::string val = line.substr(line.find_last_of(' ') + 1, std::string::npos); @@ -32,14 +31,10 @@ void run_tests(std::istream& src, if(key == output_key) { ++test_cnt; - const std::string got = cb(vars); + bool passed = cb(vars); - if(got != val) - { + if(!passed) ++test_fail; - std::cout << name_key << " #" << test_cnt - << " got " << got << " expected " << val << std::endl; - } if(clear_between_cb) vars.clear(); @@ -51,3 +46,23 @@ void run_tests(std::istream& src, << test_fail << " failed\n"; } +void run_tests(std::istream& src, + const std::string& name_key, + const std::string& output_key, + bool clear_between_cb, + std::function<std::string (std::map<std::string, std::string>)> cb) + { + run_tests_bb(src, name_key, output_key, clear_between_cb, + [name_key,output_key,cb](std::map<std::string, std::string> vars) + { + const std::string got = cb(vars); + if(got != vars[output_key]) + { + std::cout << name_key << " got " << got + << " expected " << vars[output_key] << std::endl; + return false; + } + return true; + }); + } + diff --git a/checks/validate.cpp b/checks/validate.cpp index 606f3a2c3..48932f0e4 100644 --- a/checks/validate.cpp +++ b/checks/validate.cpp @@ -422,6 +422,7 @@ u32bit do_validation_tests(const std::string& filename, test_hkdf(); test_pbkdf(); test_kdf(); + test_aead(); } return errors; diff --git a/checks/validate.h b/checks/validate.h index e1a8acfd5..48830619b 100644 --- a/checks/validate.h +++ b/checks/validate.h @@ -38,6 +38,13 @@ void test_ocb(); void test_hkdf(); void test_pbkdf(); void test_kdf(); +void test_aead(); + +void run_tests_bb(std::istream& src, + const std::string& name_key, + const std::string& output_key, + bool clear_between_cb, + std::function<bool (std::map<std::string, std::string>)> cb); void run_tests(std::istream& src, const std::string& name_key, diff --git a/doc/relnotes/1_11_6.rst b/doc/relnotes/1_11_6.rst index 7c0c64cde..b51339791 100644 --- a/doc/relnotes/1_11_6.rst +++ b/doc/relnotes/1_11_6.rst @@ -7,5 +7,7 @@ Version 1.11.6, Not Yet Released * Add HKDF from :rfc:`5869` + * Add SIV from :rfc:`5297` + * TLS::Session_Manager_In_Memory now requires a rng to be passed to its - constructor. Previously it used the global RNG, however + constructor. diff --git a/src/modes/aead/aead.cpp b/src/modes/aead/aead.cpp index d913c7c3a..26a7091fd 100644 --- a/src/modes/aead/aead.cpp +++ b/src/modes/aead/aead.cpp @@ -20,6 +20,10 @@ #include <botan/gcm.h> #endif +#if defined(BOTAN_HAS_AEAD_SIV) + #include <botan/siv.h> +#endif + #if defined(BOTAN_HAS_AEAD_OCB) #include <botan/ocb.h> #endif @@ -59,7 +63,7 @@ AEAD_Mode* get_aead(const std::string& algo_spec, Cipher_Dir direction) return new CCM_Decryption(cipher->clone(), 8, 3); } - if(mode_name == "CCM") + if(mode_name == "CCM" || mode_name == "CCM-8") { const size_t L = (mode_info.size() > 2) ? to_u32bit(mode_info[2]) : 3; @@ -80,6 +84,17 @@ AEAD_Mode* get_aead(const std::string& algo_spec, Cipher_Dir direction) } #endif +#if defined(BOTAN_HAS_AEAD_SIV) + if(mode_name == "SIV") + { + BOTAN_ASSERT(tag_size == 16, "Valid tag size for SIV"); + if(direction == ENCRYPTION) + return new SIV_Encryption(cipher->clone()); + else + return new SIV_Decryption(cipher->clone()); + } +#endif + #if defined(BOTAN_HAS_AEAD_GCM) if(mode_name == "GCM") { diff --git a/src/modes/aead/siv/info.txt b/src/modes/aead/siv/info.txt new file mode 100644 index 000000000..b1e38568e --- /dev/null +++ b/src/modes/aead/siv/info.txt @@ -0,0 +1,3 @@ +define AEAD_SIV 20131202 + +load_on auto diff --git a/src/modes/aead/siv/siv.cpp b/src/modes/aead/siv/siv.cpp new file mode 100644 index 000000000..15fb16aa0 --- /dev/null +++ b/src/modes/aead/siv/siv.cpp @@ -0,0 +1,183 @@ +/* +* SIV Mode Encryption +* (C) 2013 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include <botan/siv.h> +#include <botan/cmac.h> +#include <botan/ctr.h> +#include <botan/parsing.h> +#include <botan/internal/xor_buf.h> +#include <algorithm> + +#include <iostream> +#include <botan/hex.h> + +namespace Botan { + +SIV_Mode::SIV_Mode(BlockCipher* cipher) : + m_name(cipher->name() + "/SIV"), + m_ctr(new CTR_BE(cipher->clone())), + m_cmac(new CMAC(cipher)) + { + } + +void SIV_Mode::clear() + { + m_ctr.reset(); + m_nonce.clear(); + m_msg_buf.clear(); + m_ad_macs.clear(); + } + +std::string SIV_Mode::name() const + { + return m_name; + } + +bool SIV_Mode::valid_nonce_length(size_t) const + { + return true; + } + +size_t SIV_Mode::update_granularity() const + { + /* + This value does not particularly matter as regardless SIV_Mode::update + buffers all input, so in theory this could be 1. However as for instance + Transformation_Filter creates update_granularity() byte buffers, use a + somewhat large size to avoid bouncing on a tiny buffer. + */ + return 128; + } + +Key_Length_Specification SIV_Mode::key_spec() const + { + return m_cmac->key_spec().multiple(2); + } + +void SIV_Mode::key_schedule(const byte key[], size_t length) + { + const size_t keylen = length / 2; + m_cmac->set_key(key, keylen); + m_ctr->set_key(key + keylen, keylen); + m_ad_macs.clear(); + } + +void SIV_Mode::set_associated_data_n(size_t n, const byte ad[], size_t length) + { + if(n >= m_ad_macs.size()) + m_ad_macs.resize(n+1); + + m_ad_macs[n] = m_cmac->process(ad, length); + } + +secure_vector<byte> SIV_Mode::start(const byte nonce[], size_t nonce_len) + { + if(!valid_nonce_length(nonce_len)) + throw Invalid_IV_Length(name(), nonce_len); + + if(nonce_len) + m_nonce = m_cmac->process(nonce, nonce_len); + else + m_nonce.clear(); + + m_msg_buf.clear(); + + return secure_vector<byte>(); + } + +void SIV_Mode::update(secure_vector<byte>& buffer, size_t offset) + { + BOTAN_ASSERT(buffer.size() >= offset, "Offset is sane"); + const size_t sz = buffer.size() - offset; + byte* buf = &buffer[offset]; + + m_msg_buf.insert(m_msg_buf.end(), buf, buf + sz); + buffer.resize(offset); // truncate msg + } + +secure_vector<byte> SIV_Mode::S2V(const byte* text, size_t text_len) + { + const byte zero[16] = { 0 }; + + secure_vector<byte> V = cmac().process(zero, 16); + + for(size_t i = 0; i != m_ad_macs.size(); ++i) + { + V = CMAC::poly_double(V, 0x87); + V ^= m_ad_macs[i]; + } + + if(m_nonce.size()) + { + V = CMAC::poly_double(V, 0x87); + V ^= m_nonce; + } + + if(text_len < 16) + { + V = CMAC::poly_double(V, 0x87); + xor_buf(&V[0], text, text_len); + V[text_len] ^= 0x80; + return cmac().process(V); + } + + cmac().update(text, text_len - 16); + xor_buf(&V[0], &text[text_len - 16], 16); + cmac().update(V); + + return cmac().final(); + } + +void SIV_Mode::set_ctr_iv(secure_vector<byte> V) + { + V[8] &= 0x7F; + V[12] &= 0x7F; + + ctr().set_iv(&V[0], V.size()); + } + +void SIV_Encryption::finish(secure_vector<byte>& buffer, size_t offset) + { + BOTAN_ASSERT(buffer.size() >= offset, "Offset is sane"); + + buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end()); + + secure_vector<byte> V = S2V(&buffer[offset], buffer.size() - offset); + + buffer.insert(buffer.begin() + offset, V.begin(), V.end()); + + set_ctr_iv(V); + ctr().cipher1(&buffer[offset + V.size()], buffer.size() - offset - V.size()); + } + +void SIV_Decryption::finish(secure_vector<byte>& buffer, size_t offset) + { + BOTAN_ASSERT(buffer.size() >= offset, "Offset is sane"); + + buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end()); + + const size_t sz = buffer.size() - offset; + + BOTAN_ASSERT(sz >= tag_size(), "We have the tag"); + + secure_vector<byte> V(&buffer[offset], &buffer[offset + 16]); + + set_ctr_iv(V); + + ctr().cipher(&buffer[offset + V.size()], + &buffer[offset], + buffer.size() - offset - V.size()); + + secure_vector<byte> T = S2V(&buffer[offset], buffer.size() - offset - V.size()); + + if(T != V) + throw Integrity_Failure("SIV tag check failed"); + + buffer.resize(buffer.size() - tag_size()); + } + +} diff --git a/src/modes/aead/siv/siv.h b/src/modes/aead/siv/siv.h new file mode 100644 index 000000000..612d29bb7 --- /dev/null +++ b/src/modes/aead/siv/siv.h @@ -0,0 +1,114 @@ +/* +* SIV Mode +* (C) 2013 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#ifndef BOTAN_AEAD_SIV_H__ +#define BOTAN_AEAD_SIV_H__ + +#include <botan/aead.h> +#include <botan/block_cipher.h> +#include <botan/stream_cipher.h> +#include <botan/mac.h> +#include <memory> + +namespace Botan { + +/** +* Base class for SIV encryption and decryption (@see RFC 5297) +*/ +class BOTAN_DLL SIV_Mode : public AEAD_Mode + { + public: + secure_vector<byte> start(const byte nonce[], size_t nonce_len) override; + + void update(secure_vector<byte>& blocks, size_t offset) override; + + void set_associated_data_n(size_t n, const byte ad[], size_t ad_len); + + void set_associated_data(const byte ad[], size_t ad_len) override + { + set_associated_data_n(0, ad, ad_len); + } + + std::string name() const override; + + size_t update_granularity() const; + + Key_Length_Specification key_spec() const override; + + bool valid_nonce_length(size_t) const override; + + void clear(); + + size_t tag_size() const { return 16; } + + protected: + SIV_Mode(BlockCipher* cipher); + + StreamCipher& ctr() { return *m_ctr; } + + void set_ctr_iv(secure_vector<byte> V); + + secure_vector<byte>& msg_buf() { return m_msg_buf; } + + secure_vector<byte> S2V(const byte text[], size_t text_len); + private: + MessageAuthenticationCode& cmac() { return *m_cmac; } + + void key_schedule(const byte key[], size_t length) override; + + const std::string m_name; + + std::unique_ptr<StreamCipher> m_ctr; + std::unique_ptr<MessageAuthenticationCode> m_cmac; + secure_vector<byte> m_nonce, m_msg_buf; + std::vector<secure_vector<byte>> m_ad_macs; + }; + +/** +* SIV Encryption +*/ +class BOTAN_DLL SIV_Encryption : public SIV_Mode + { + public: + /** + * @param cipher a block cipher + */ + SIV_Encryption(BlockCipher* cipher) : SIV_Mode(cipher) {} + + void finish(secure_vector<byte>& final_block, size_t offset) override; + + size_t output_length(size_t input_length) const override + { return input_length + tag_size(); } + + size_t minimum_final_size() const override { return 0; } + }; + +/** +* SIV Decryption +*/ +class BOTAN_DLL SIV_Decryption : public SIV_Mode + { + public: + /** + * @param cipher a 128-bit block cipher + */ + SIV_Decryption(BlockCipher* cipher) : SIV_Mode(cipher) {} + + void finish(secure_vector<byte>& final_block, size_t offset) override; + + size_t output_length(size_t input_length) const override + { + BOTAN_ASSERT(input_length > tag_size(), "Sufficient input"); + return input_length - tag_size(); + } + + size_t minimum_final_size() const override { return tag_size(); } + }; + +} + +#endif |