aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--checks/aead.cpp78
-rw-r--r--checks/aead.vec12
-rw-r--r--checks/hkdf.cpp44
-rw-r--r--checks/tests.cpp43
-rw-r--r--checks/validate.cpp1
-rw-r--r--checks/validate.h7
-rw-r--r--doc/relnotes/1_11_6.rst4
-rw-r--r--src/modes/aead/aead.cpp17
-rw-r--r--src/modes/aead/siv/info.txt3
-rw-r--r--src/modes/aead/siv/siv.cpp183
-rw-r--r--src/modes/aead/siv/siv.h114
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