diff options
author | Alexander Bluhm <[email protected]> | 2017-04-03 18:00:54 +0200 |
---|---|---|
committer | Alexander Bluhm <[email protected]> | 2017-04-25 15:50:33 +0200 |
commit | a9b91783b734d0cb1b3515f091564d126218275f (patch) | |
tree | 6ea2804824dc3286865cdd368952c3922171a3eb | |
parent | 3a560e25b2ab197e54935eb047090446be6c10f5 (diff) |
Implement cipher modes with OpenSSL.
Use the OpenSSL provider to implement AES CBC mode. Also pass down
the provider to the encryption layer if there is no matching OpenSSL
mode. Add a test with empty nonce.
-rw-r--r-- | src/lib/modes/cipher_mode.cpp | 38 | ||||
-rw-r--r-- | src/lib/modes/cipher_mode.h | 13 | ||||
-rw-r--r-- | src/lib/prov/openssl/openssl.h | 7 | ||||
-rw-r--r-- | src/lib/prov/openssl/openssl_mode.cpp | 200 | ||||
-rw-r--r-- | src/tests/data/modes/cbc.vec | 7 | ||||
-rw-r--r-- | src/tests/test_modes.cpp | 150 |
6 files changed, 344 insertions, 71 deletions
diff --git a/src/lib/modes/cipher_mode.cpp b/src/lib/modes/cipher_mode.cpp index 843e49581..74d565f33 100644 --- a/src/lib/modes/cipher_mode.cpp +++ b/src/lib/modes/cipher_mode.cpp @@ -34,10 +34,26 @@ #include <botan/xts.h> #endif +#if defined(BOTAN_HAS_OPENSSL) + #include <botan/internal/openssl.h> +#endif + namespace Botan { -Cipher_Mode* get_cipher_mode(const std::string& algo, Cipher_Dir direction) +Cipher_Mode* get_cipher_mode(const std::string& algo, Cipher_Dir direction, + const std::string& provider) { +#if defined(BOTAN_HAS_OPENSSL) + if(provider.empty() || provider == "openssl") + { + if(Cipher_Mode* bc = make_openssl_cipher_mode(algo, direction)) + return bc; + + if(!provider.empty()) + return nullptr; + } +#endif + if(auto sc = StreamCipher::create(algo)) { return new Stream_Cipher_Mode(sc.release()); @@ -69,7 +85,7 @@ Cipher_Mode* get_cipher_mode(const std::string& algo, Cipher_Dir direction) alg_args << ')'; const std::string mode_name = mode_info[0] + alg_args.str(); - return get_cipher_mode(mode_name, direction); + return get_cipher_mode(mode_name, direction, provider); } #if defined(BOTAN_HAS_BLOCK_CIPHER) @@ -81,7 +97,7 @@ Cipher_Mode* get_cipher_mode(const std::string& algo, Cipher_Dir direction) return nullptr; } - std::unique_ptr<BlockCipher> bc(BlockCipher::create(spec.arg(0))); + std::unique_ptr<BlockCipher> bc(BlockCipher::create(spec.arg(0), provider)); if(!bc) { @@ -141,4 +157,20 @@ Cipher_Mode* get_cipher_mode(const std::string& algo, Cipher_Dir direction) return nullptr; } +//static +std::vector<std::string> Cipher_Mode::providers(const std::string& algo_spec) + { + const std::vector<std::string>& possible = { "base", "openssl" }; + std::vector<std::string> providers; + for(auto&& prov : possible) + { + std::unique_ptr<Cipher_Mode> mode(get_cipher_mode(algo_spec, ENCRYPTION, prov)); + if(mode) + { + providers.push_back(prov); // available + } + } + return providers; + } + } diff --git a/src/lib/modes/cipher_mode.h b/src/lib/modes/cipher_mode.h index 411a1b3e5..bf1821256 100644 --- a/src/lib/modes/cipher_mode.h +++ b/src/lib/modes/cipher_mode.h @@ -25,6 +25,12 @@ class BOTAN_DLL Cipher_Mode public: virtual ~Cipher_Mode() = default; + /** + * @return list of available providers for this algorithm, empty if not available + * @param algo_spec algorithm name + */ + static std::vector<std::string> providers(const std::string& algo_spec); + /* * Prepare for processing a message under the specified nonce */ @@ -209,14 +215,17 @@ class BOTAN_DLL Cipher_Mode * The two possible directions for cipher filters, determining whether they * actually perform encryption or decryption. */ -enum Cipher_Dir { ENCRYPTION, DECRYPTION }; +enum Cipher_Dir : int { ENCRYPTION, DECRYPTION }; /** * Get a cipher mode by name (eg "AES-128/CBC" or "Serpent/XTS") * @param algo_spec cipher name * @param direction ENCRYPTION or DECRYPTION +* @param provider provider implementation to choose */ -BOTAN_DLL Cipher_Mode* get_cipher_mode(const std::string& algo_spec, Cipher_Dir direction); +BOTAN_DLL Cipher_Mode* get_cipher_mode(const std::string& algo_spec, + Cipher_Dir direction, + const std::string& provider = ""); } diff --git a/src/lib/prov/openssl/openssl.h b/src/lib/prov/openssl/openssl.h index e28fb2931..3cd39113b 100644 --- a/src/lib/prov/openssl/openssl.h +++ b/src/lib/prov/openssl/openssl.h @@ -24,8 +24,10 @@ namespace Botan { class BlockCipher; +class Cipher_Mode; class StreamCipher; class HashFunction; +enum Cipher_Dir : int; class OpenSSL_Error : public Exception { @@ -39,6 +41,11 @@ class OpenSSL_Error : public Exception std::unique_ptr<BlockCipher> make_openssl_block_cipher(const std::string& name); +/* Cipher Modes */ + +Cipher_Mode* +make_openssl_cipher_mode(const std::string& name, Cipher_Dir direction); + /* Hash */ std::unique_ptr<HashFunction> diff --git a/src/lib/prov/openssl/openssl_mode.cpp b/src/lib/prov/openssl/openssl_mode.cpp new file mode 100644 index 000000000..77682bf5c --- /dev/null +++ b/src/lib/prov/openssl/openssl_mode.cpp @@ -0,0 +1,200 @@ +/* +* Cipher Modes via OpenSSL +* (C) 1999-2010,2015 Jack Lloyd +* (C) 2017 Alexander Bluhm (genua GmbH) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/cipher_mode.h> +#include <botan/internal/rounding.h> +#include <botan/internal/openssl.h> +#include <openssl/evp.h> + +namespace Botan { + +namespace { + +class BOTAN_DLL OpenSSL_Cipher_Mode : public Cipher_Mode + { + public: + OpenSSL_Cipher_Mode(const std::string& name, + const EVP_CIPHER* cipher, + Cipher_Dir direction); + ~OpenSSL_Cipher_Mode(); + + std::string provider() const override { return "openssl"; } + std::string name() const override { return m_mode_name; } + + void start_msg(const uint8_t nonce[], size_t nonce_len) override; + size_t process(uint8_t msg[], size_t msg_len) override; + void finish(secure_vector<uint8_t>& final_block, size_t offset0) override; + size_t output_length(size_t input_length) const override; + size_t update_granularity() const override; + size_t minimum_final_size() const override; + size_t default_nonce_length() const override; + bool valid_nonce_length(size_t nonce_len) const override; + void clear() override; + void reset() override; + Key_Length_Specification key_spec() const override; + + private: + void key_schedule(const uint8_t key[], size_t length) override; + + const std::string m_mode_name; + const Cipher_Dir m_direction; + size_t m_block_size; + EVP_CIPHER_CTX m_cipher; + }; + +OpenSSL_Cipher_Mode::OpenSSL_Cipher_Mode(const std::string& name, + const EVP_CIPHER* algo, + Cipher_Dir direction) : + m_mode_name(name), + m_direction(direction) + { + m_block_size = EVP_CIPHER_block_size(algo); + + if(EVP_CIPHER_mode(algo) != EVP_CIPH_CBC_MODE) + throw Invalid_Argument("OpenSSL_BlockCipher: Non-CBC EVP was passed in"); + + EVP_CIPHER_CTX_init(&m_cipher); + if(!EVP_CipherInit_ex(&m_cipher, algo, nullptr, nullptr, nullptr, + m_direction == ENCRYPTION ? 1 : 0)) + throw Internal_Error("EVP_CipherInit_ex failed"); + if(!EVP_CIPHER_CTX_set_padding(&m_cipher, 0)) + throw Internal_Error("EVP_CIPHER_CTX_set_padding failed"); + } + +OpenSSL_Cipher_Mode::~OpenSSL_Cipher_Mode() + { + EVP_CIPHER_CTX_cleanup(&m_cipher); + } + +void OpenSSL_Cipher_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) + { + if(!valid_nonce_length(nonce_len)) + throw Invalid_IV_Length(name(), nonce_len); + if(nonce_len) + { + if(!EVP_CipherInit_ex(&m_cipher, nullptr, nullptr, nullptr, nonce, -1)) + throw Internal_Error("EVP_CipherInit_ex nonce failed"); + } + } + +size_t OpenSSL_Cipher_Mode::process(uint8_t msg[], size_t msg_len) + { + if(msg_len == 0) + return 0; + if(msg_len > INT_MAX) + throw Internal_Error("msg_len overflow"); + int outl = msg_len; + secure_vector<uint8_t> out(outl); + + if(!EVP_CipherUpdate(&m_cipher, out.data(), &outl, msg, msg_len)) + throw Internal_Error("EVP_CipherUpdate failed"); + memcpy(msg, out.data(), outl); + return outl; + } + +void OpenSSL_Cipher_Mode::finish(secure_vector<uint8_t>& buffer, + size_t offset) + { + BOTAN_ASSERT(buffer.size() >= offset, "Offset ok"); + uint8_t* buf = buffer.data() + offset; + const size_t buf_size = buffer.size() - offset; + + size_t written = process(buf, buf_size); + int outl = buf_size - written; + secure_vector<uint8_t> out(outl); + + if(!EVP_CipherFinal_ex(&m_cipher, out.data(), &outl)) + throw Internal_Error("EVP_CipherFinal_ex failed"); + memcpy(buf + written, out.data(), outl); + written += outl; + buffer.resize(offset + written); + } + +size_t OpenSSL_Cipher_Mode::update_granularity() const + { + return m_block_size * BOTAN_BLOCK_CIPHER_PAR_MULT; + } + +size_t OpenSSL_Cipher_Mode::minimum_final_size() const + { + return 0; // no padding + } + +size_t OpenSSL_Cipher_Mode::default_nonce_length() const + { + return m_block_size; + } + +bool OpenSSL_Cipher_Mode::valid_nonce_length(size_t nonce_len) const + { + return (nonce_len == 0 || nonce_len == m_block_size); + } + +size_t OpenSSL_Cipher_Mode::output_length(size_t input_length) const + { + if(input_length == 0) + return m_block_size; + else + return round_up(input_length, m_block_size); + } + +void OpenSSL_Cipher_Mode::clear() + { + const EVP_CIPHER* algo = EVP_CIPHER_CTX_cipher(&m_cipher); + + if(!EVP_CIPHER_CTX_cleanup(&m_cipher)) + throw Internal_Error("EVP_CIPHER_CTX_cleanup failed"); + EVP_CIPHER_CTX_init(&m_cipher); + if(!EVP_CipherInit_ex(&m_cipher, algo, nullptr, nullptr, nullptr, + m_direction == ENCRYPTION ? 1 : 0)) + throw Internal_Error("EVP_CipherInit_ex clear failed"); + if(!EVP_CIPHER_CTX_set_padding(&m_cipher, 0)) + throw Internal_Error("EVP_CIPHER_CTX_set_padding clear failed"); + } + +void OpenSSL_Cipher_Mode::reset() + { + if(!EVP_CipherInit_ex(&m_cipher, nullptr, nullptr, nullptr, nullptr, -1)) + throw Internal_Error("EVP_CipherInit_ex clear failed"); + } + +Key_Length_Specification OpenSSL_Cipher_Mode::key_spec() const + { + return Key_Length_Specification(EVP_CIPHER_CTX_key_length(&m_cipher)); + } + +void OpenSSL_Cipher_Mode::key_schedule(const uint8_t key[], size_t length) + { + if(!EVP_CIPHER_CTX_set_key_length(&m_cipher, length)) + throw Invalid_Argument("EVP_CIPHER_CTX_set_key_length failed"); + if(!EVP_CipherInit_ex(&m_cipher, nullptr, nullptr, key, nullptr, -1)) + throw Internal_Error("EVP_CipherInit_ex key failed"); + } + +} + +Cipher_Mode* +make_openssl_cipher_mode(const std::string& name, Cipher_Dir direction) + { +#define MAKE_OPENSSL_MODE(evp_fn) \ + new OpenSSL_Cipher_Mode(name, (evp_fn)(), direction) + +#if defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_MODE_CBC) && !defined(OPENSSL_NO_AES) + if(name == "AES-128/CBC/NoPadding") + return MAKE_OPENSSL_MODE(EVP_aes_128_cbc); + if(name == "AES-192/CBC/NoPadding") + return MAKE_OPENSSL_MODE(EVP_aes_192_cbc); + if(name == "AES-256/CBC/NoPadding") + return MAKE_OPENSSL_MODE(EVP_aes_256_cbc); +#endif + +#undef MAKE_OPENSSL_MODE + return nullptr; + } + +} diff --git a/src/tests/data/modes/cbc.vec b/src/tests/data/modes/cbc.vec index 4767b7e50..30ca53f3e 100644 --- a/src/tests/data/modes/cbc.vec +++ b/src/tests/data/modes/cbc.vec @@ -1217,6 +1217,13 @@ Nonce = 000102030405060708090A0B0C0D0E0F In = 6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710 Out = F58C4C04D6E5F1BA779EABFB5F7BFBD69CFC4E967EDB808D679F777BC6702C7D39F23369A9D9BACFA530E26304231461B2EB05E2C39BE9FCDA6C19078C6A9D1B +# test empty nonce, must be equivalent to zero +[AES-128/CBC/NoPadding] +Key = 2B7E151628AED2A6ABF7158809CF4F3C +Nonce = +In = 6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710 +Out = 3AD77BB40D7A3660A89ECAF32466EF97B148C17F309EE692287AE57CF12ADD49C93D11BFAF08C5DC4D90B37B4DEE002BA7356E1207BB406639E5E5CEB9A9ED93 + # RFC 3962: Advanced Encryption Standard (AES) Encryption for Kerberos 5 [AES-128/CBC/CTS] Key = 636869636b656e207465726979616b69 diff --git a/src/tests/test_modes.cpp b/src/tests/test_modes.cpp index 480b15527..63edba5f1 100644 --- a/src/tests/test_modes.cpp +++ b/src/tests/test_modes.cpp @@ -22,6 +22,11 @@ class Cipher_Mode_Tests : public Text_Based_Test Text_Based_Test("modes", "Key,Nonce,In,Out") {} + std::vector<std::string> possible_providers(const std::string& algo) override + { + return provider_filter(Botan::Cipher_Mode::providers(algo)); + } + Test::Result run_one_test(const std::string& algo, const VarMap& vars) override { const std::vector<uint8_t> key = get_req_bin(vars, "Key"); @@ -31,102 +36,115 @@ class Cipher_Mode_Tests : public Text_Based_Test Test::Result result(algo); - std::unique_ptr<Botan::Cipher_Mode> enc(Botan::get_cipher_mode(algo, Botan::ENCRYPTION)); - std::unique_ptr<Botan::Cipher_Mode> dec(Botan::get_cipher_mode(algo, Botan::DECRYPTION)); + const std::vector<std::string> providers = possible_providers(algo); - if(!enc || !dec) + if(providers.empty()) { - result.note_missing(algo); + result.note_missing("cipher mode " + algo); return result; } - result.test_is_nonempty("provider", enc->provider()); - result.test_eq("name", enc->name(), algo); - - result.test_eq("mode not authenticated", enc->authenticated(), false); + for(auto&& provider_ask : providers) + { + std::unique_ptr<Botan::Cipher_Mode> enc(Botan::get_cipher_mode( + algo, Botan::ENCRYPTION, provider_ask)); + std::unique_ptr<Botan::Cipher_Mode> dec(Botan::get_cipher_mode( + algo, Botan::DECRYPTION, provider_ask)); - // Test to make sure reset() resets what we need it to - enc->set_key(mutate_vec(key)); - Botan::secure_vector<uint8_t> garbage = Test::rng().random_vec(enc->update_granularity()); - enc->start(mutate_vec(nonce)); - enc->update(garbage); + if(!enc || !dec) + { + result.note_missing(algo); + return result; + } - enc->reset(); + result.test_is_nonempty("provider", enc->provider()); + result.test_eq("name", enc->name(), algo); - enc->set_key(key); - enc->start(nonce); + result.test_eq("mode not authenticated", enc->authenticated(), false); - Botan::secure_vector<uint8_t> buf(input.begin(), input.end()); - // TODO: should first update if possible - enc->finish(buf); - result.test_eq("encrypt", buf, expected); + // Test to make sure reset() resets what we need it to + enc->set_key(mutate_vec(key)); + Botan::secure_vector<uint8_t> garbage = Test::rng().random_vec(enc->update_granularity()); + enc->start(mutate_vec(nonce)); + enc->update(garbage); - // additionally test process() if possible - size_t update_granularity = enc->update_granularity(); - size_t input_length = input.size(); - size_t min_final_bytes = enc->minimum_final_size(); - if(input_length > (update_granularity + min_final_bytes)) - { - // reset state first enc->reset(); + enc->set_key(key); enc->start(nonce); - buf.assign(input.begin(), input.end()); - // we can process at max input_length - const size_t max_blocks_to_process = (input_length - min_final_bytes) / update_granularity; - const size_t bytes_to_process = max_blocks_to_process * update_granularity; + Botan::secure_vector<uint8_t> buf(input.begin(), input.end()); + // TODO: should first update if possible + enc->finish(buf); + result.test_eq("encrypt", buf, expected); - const size_t bytes_written = enc->process(buf.data(), bytes_to_process); + // additionally test process() if possible + size_t update_granularity = enc->update_granularity(); + size_t input_length = input.size(); + size_t min_final_bytes = enc->minimum_final_size(); + if(input_length > (update_granularity + min_final_bytes)) + { + // reset state first + enc->reset(); - result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process); + enc->start(nonce); + buf.assign(input.begin(), input.end()); - enc->finish(buf, bytes_to_process); - result.test_eq("encrypt", buf, expected); - } + // we can process at max input_length + const size_t max_blocks_to_process = (input_length - min_final_bytes) / update_granularity; + const size_t bytes_to_process = max_blocks_to_process * update_granularity; - // decryption - buf.assign(expected.begin(), expected.end()); + const size_t bytes_written = enc->process(buf.data(), bytes_to_process); - // Test to make sure reset() resets what we need it to - dec->set_key(mutate_vec(key)); - garbage = Test::rng().random_vec(dec->update_granularity()); - dec->start(mutate_vec(nonce)); - dec->update(garbage); + result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process); - dec->reset(); + enc->finish(buf, bytes_to_process); + result.test_eq("encrypt", buf, expected); + } - dec->set_key(key); - dec->start(nonce); - dec->finish(buf); - result.test_eq("decrypt", buf, input); + // decryption + buf.assign(expected.begin(), expected.end()); + + // Test to make sure reset() resets what we need it to + dec->set_key(mutate_vec(key)); + garbage = Test::rng().random_vec(dec->update_granularity()); + dec->start(mutate_vec(nonce)); + dec->update(garbage); - // additionally test process() if possible - update_granularity = dec->update_granularity(); - input_length = expected.size(); - min_final_bytes = dec->minimum_final_size(); - if(input_length > (update_granularity + min_final_bytes)) - { - // reset state first dec->reset(); + dec->set_key(key); dec->start(nonce); - buf.assign(expected.begin(), expected.end()); + dec->finish(buf); + result.test_eq("decrypt", buf, input); - // we can process at max input_length - const size_t max_blocks_to_process = (input_length - min_final_bytes) / update_granularity; - const size_t bytes_to_process = max_blocks_to_process * update_granularity; + // additionally test process() if possible + update_granularity = dec->update_granularity(); + input_length = expected.size(); + min_final_bytes = dec->minimum_final_size(); + if(input_length > (update_granularity + min_final_bytes)) + { + // reset state first + dec->reset(); - const size_t bytes_written = dec->process(buf.data(), bytes_to_process); + dec->start(nonce); + buf.assign(expected.begin(), expected.end()); - result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process); + // we can process at max input_length + const size_t max_blocks_to_process = (input_length - min_final_bytes) / update_granularity; + const size_t bytes_to_process = max_blocks_to_process * update_granularity; - dec->finish(buf, bytes_to_process); - result.test_eq("decrypt", buf, input); - } + const size_t bytes_written = dec->process(buf.data(), bytes_to_process); + + result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process); - enc->clear(); - dec->clear(); + dec->finish(buf, bytes_to_process); + result.test_eq("decrypt", buf, input); + } + + enc->clear(); + dec->clear(); + } return result; } |