diff options
-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; } |