diff options
Diffstat (limited to 'src')
30 files changed, 1329 insertions, 307 deletions
diff --git a/src/build-data/buildh.in b/src/build-data/buildh.in index 393bcc4fb..2376b056e 100644 --- a/src/build-data/buildh.in +++ b/src/build-data/buildh.in @@ -51,7 +51,7 @@ * Local/misc configuration options (if any) follow */ %{local_config} -%{misc_config} +%{house_ecc_curve_defines} /* * Things you can edit (but probably shouldn't) diff --git a/src/cli/utils.cpp b/src/cli/utils.cpp index 76501add4..d561f4cdf 100644 --- a/src/cli/utils.cpp +++ b/src/cli/utils.cpp @@ -1,5 +1,6 @@ /* * (C) 2009,2010,2014,2015 Jack Lloyd +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -36,6 +37,10 @@ #include <botan/bcrypt.h> #endif +#if defined(BOTAN_HAS_HMAC) + #include <botan/hmac.h> +#endif + namespace Botan_CLI { class Config_Info final : public Command @@ -226,6 +231,43 @@ BOTAN_REGISTER_COMMAND("http_get", HTTP_Get); #endif // http_util +#if defined(BOTAN_HAS_HEX_CODEC) + +class Hex_Encode final : public Command + { + public: + Hex_Encode() : Command("hex_enc file") {} + + void go() override + { + auto hex_enc_f = [&](const uint8_t b[], size_t l) { output() << Botan::hex_encode(b, l); }; + this->read_file(get_arg("file"), hex_enc_f, 2); + } + }; + +BOTAN_REGISTER_COMMAND("hex_enc", Hex_Encode); + +class Hex_Decode final : public Command + { + public: + Hex_Decode() : Command("hex_dec file") {} + + void go() override + { + auto hex_dec_f = [&](const uint8_t b[], size_t l) + { + std::vector<uint8_t> bin = Botan::hex_decode(reinterpret_cast<const char*>(b), l); + output().write(reinterpret_cast<const char*>(bin.data()), bin.size()); + }; + + this->read_file(get_arg("file"), hex_dec_f, 2); + } + }; + +BOTAN_REGISTER_COMMAND("hex_dec", Hex_Decode); + +#endif + #if defined(BOTAN_HAS_BASE64_CODEC) class Base64_Encode final : public Command @@ -309,4 +351,47 @@ BOTAN_REGISTER_COMMAND("check_bcrypt", Check_Bcrypt); #endif // bcrypt +#if defined(BOTAN_HAS_HMAC) + +class HMAC final : public Command + { + public: + HMAC() : Command("hmac --hash=SHA-256 --buf-size=4096 key *files") {} + + void go() override + { + const std::string hash_algo = get_arg("hash"); + std::unique_ptr<Botan::MessageAuthenticationCode> hmac(Botan::MessageAuthenticationCode::create("HMAC(" + hash_algo + ")")); + + if(!hmac) + throw CLI_Error_Unsupported("HMAC", hash_algo); + + hmac->set_key(slurp_file(get_arg("key"))); + + const size_t buf_size = get_arg_sz("buf-size"); + + std::vector<std::string> files = get_arg_list("files"); + if(files.empty()) + files.push_back("-"); // read stdin if no arguments on command line + + for(const std::string& fsname : files) + { + try + { + auto update_hmac = [&](const uint8_t b[], size_t l) { hmac->update(b, l); }; + read_file(fsname, update_hmac, buf_size); + output() << Botan::hex_encode(hmac->final()) << " " << fsname << "\n"; + } + catch(CLI_IO_Error& e) + { + error_output() << e.what() << "\n"; + } + } + } + }; + +BOTAN_REGISTER_COMMAND("hmac", HMAC); + +#endif // hmac + } diff --git a/src/lib/asn1/ber_dec.cpp b/src/lib/asn1/ber_dec.cpp index 7b2147600..bf728a8e8 100644 --- a/src/lib/asn1/ber_dec.cpp +++ b/src/lib/asn1/ber_dec.cpp @@ -218,20 +218,25 @@ BER_Object BER_Decoder::get_next_object() return next; } - decode_tag(m_source, next.type_tag, next.class_tag); - if(next.type_tag == NO_OBJECT) - return next; + for(;;) + { + decode_tag(m_source, next.type_tag, next.class_tag); + if(next.type_tag == NO_OBJECT) + return next; - const size_t length = decode_length(m_source); - if(!m_source->check_available(length)) - throw BER_Decoding_Error("Value truncated"); + const size_t length = decode_length(m_source); + if(!m_source->check_available(length)) + throw BER_Decoding_Error("Value truncated"); - next.value.resize(length); - if(m_source->read(next.value.data(), length) != length) - throw BER_Decoding_Error("Value truncated"); + next.value.resize(length); + if(m_source->read(next.value.data(), length) != length) + throw BER_Decoding_Error("Value truncated"); - if(next.type_tag == EOC && next.class_tag == UNIVERSAL) - return get_next_object(); + if(next.type_tag == EOC && next.class_tag == UNIVERSAL) + continue; + else + break; + } return next; } diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 7cf69efb3..42b01be62 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -33,6 +33,10 @@ #include <botan/rsa.h> #endif +#if defined(BOTAN_HAS_ELGAMAL) + #include <botan/elgamal.h> +#endif + #if defined(BOTAN_HAS_DSA) #include <botan/dsa.h> #endif @@ -203,6 +207,121 @@ inline int write_str_output(char out[], size_t* out_len, const std::vector<uint8 #define BOTAN_FFI_DO(T, obj, param, block) apply_fn(obj, BOTAN_CURRENT_FUNCTION, [=](T& param) -> int { do { block } while(0); return 0; }) +Botan::BigInt pubkey_get_field(const Botan::Public_Key& key, + const std::string& field) + { + // Maybe this should be `return key.get_integer_field(field_name)`? + +#if defined(BOTAN_HAS_RSA) + if(const Botan::RSA_PublicKey* rsa = dynamic_cast<const Botan::RSA_PublicKey*>(&key)) + { + if(field == "n") + return rsa->get_n(); + else if(field == "e") + return rsa->get_e(); + else + throw Botan::Exception("Field not supported"); + } +#endif + +#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY) + // Handles DSA, ElGamal, etc + if(const Botan::DL_Scheme_PublicKey* dl = dynamic_cast<const Botan::DL_Scheme_PublicKey*>(&key)) + { + if(field == "p") + return dl->group_p(); + else if(field == "q") + return dl->group_q(); + else if(field == "g") + return dl->group_g(); + else if(field == "y") + return dl->get_y(); + else + throw Botan::Exception("Field not supported"); + } +#endif + +#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) + if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key)) + { + if(field == "public_x") + return ecc->public_point().get_affine_x(); + else if(field == "public_y") + return ecc->public_point().get_affine_y(); + else if(field == "base_x") + return ecc->domain().get_base_point().get_affine_x(); + else if(field == "base_y") + return ecc->domain().get_base_point().get_affine_y(); + else if(field == "p") + return ecc->domain().get_curve().get_p(); + else if(field == "a") + return ecc->domain().get_curve().get_a(); + else if(field == "b") + return ecc->domain().get_curve().get_b(); + else if(field == "cofactor") + return ecc->domain().get_cofactor(); + else if(field == "order") + return ecc->domain().get_order(); + else + throw Botan::Exception("Field not supported"); + } +#endif + + // Some other algorithm type not supported by this function + throw Botan::Exception("Unsupported algorithm type for botan_pubkey_get_field"); + } + +Botan::BigInt privkey_get_field(const Botan::Private_Key& key, + const std::string& field) + { + //return key.get_integer_field(field); + +#if defined(BOTAN_HAS_RSA) + + if(const Botan::RSA_PrivateKey* rsa = dynamic_cast<const Botan::RSA_PrivateKey*>(&key)) + { + if(field == "p") + return rsa->get_p(); + else if(field == "q") + return rsa->get_q(); + else if(field == "d") + return rsa->get_d(); + else if(field == "c") + return rsa->get_c(); + else if(field == "d1") + return rsa->get_d1(); + else if(field == "d2") + return rsa->get_d2(); + else + return pubkey_get_field(key, field); + } +#endif + +#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY) + // Handles DSA, ElGamal, etc + if(const Botan::DL_Scheme_PrivateKey* dl = dynamic_cast<const Botan::DL_Scheme_PrivateKey*>(&key)) + { + if(field == "x") + return dl->get_x(); + else + return pubkey_get_field(key, field); + } +#endif + +#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) + if(const Botan::EC_PrivateKey* ecc = dynamic_cast<const Botan::EC_PrivateKey*>(&key)) + { + if(field == "x") + return ecc->private_value(); + else + return pubkey_get_field(key, field); + } +#endif + + // Some other algorithm type not supported by this function + throw Botan::Exception("Unsupported algorithm type for botan_privkey_get_field"); + } + } extern "C" { @@ -434,7 +553,7 @@ int botan_mp_to_str(const botan_mp_t mp, uint8_t digit_base, char* out, size_t* std::vector<uint8_t> hex = Botan::BigInt::encode(bn, base); hex.push_back(0); // null terminator - write_str_output(out, out_len, hex); + return write_str_output(out, out_len, hex); }); } @@ -935,6 +1054,7 @@ int botan_cipher_update(botan_cipher_t cipher_obj, cipher.update(mbuf); input_size -= ud; + copy_mem(output, mbuf.data(), ud); input += ud; taken += ud; @@ -946,6 +1066,7 @@ int botan_cipher_update(botan_cipher_t cipher_obj, *output_written = written; *input_consumed = taken; + return 0; } catch(std::exception& e) { @@ -1326,9 +1447,8 @@ int botan_pubkey_load(botan_pubkey_t* key, int botan_privkey_load_rsa(botan_privkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t d) { - *key = nullptr; - #if defined(BOTAN_HAS_RSA) + *key = nullptr; try { *key = new botan_privkey_struct(new Botan::RSA_PrivateKey(safe_get(p), @@ -1342,6 +1462,7 @@ int botan_privkey_load_rsa(botan_privkey_t* key, } return -1; #else + BOTAN_UNUSED(key, p, q, d); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -1349,9 +1470,8 @@ int botan_privkey_load_rsa(botan_privkey_t* key, int botan_pubkey_load_rsa(botan_pubkey_t* key, botan_mp_t n, botan_mp_t e) { - *key = nullptr; - #if defined(BOTAN_HAS_RSA) + *key = nullptr; try { *key = new botan_pubkey_struct(new Botan::RSA_PublicKey(safe_get(n), safe_get(e))); @@ -1364,6 +1484,7 @@ int botan_pubkey_load_rsa(botan_pubkey_t* key, return -1; #else + BOTAN_UNUSED(key, n, e); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -1371,9 +1492,8 @@ int botan_pubkey_load_rsa(botan_pubkey_t* key, int botan_privkey_load_dsa(botan_privkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t x) { - *key = nullptr; - #if defined(BOTAN_HAS_DSA) + *key = nullptr; try { Botan::Null_RNG null_rng; @@ -1387,10 +1507,7 @@ int botan_privkey_load_dsa(botan_privkey_t* key, } return -1; #else - BOTAN_UNUSED(p); - BOTAN_UNUSED(q); - BOTAN_UNUSED(g); - BOTAN_UNUSED(x); + BOTAN_UNUSED(key, p, q, g, x); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -1398,9 +1515,8 @@ int botan_privkey_load_dsa(botan_privkey_t* key, int botan_pubkey_load_dsa(botan_pubkey_t* key, botan_mp_t p, botan_mp_t q, botan_mp_t g, botan_mp_t y) { - *key = nullptr; - #if defined(BOTAN_HAS_DSA) + *key = nullptr; try { Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g)); @@ -1414,133 +1530,57 @@ int botan_pubkey_load_dsa(botan_pubkey_t* key, return -1; #else - BOTAN_UNUSED(p); - BOTAN_UNUSED(q); - BOTAN_UNUSED(g); - BOTAN_UNUSED(y); + BOTAN_UNUSED(key, p, q, g, y); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -namespace { - -Botan::BigInt botan_pubkey_do_get_field(const Botan::Public_Key& key, - const std::string& field) +int botan_pubkey_load_elgamal(botan_pubkey_t* key, + botan_mp_t p, botan_mp_t g, botan_mp_t y) { - // Maybe this should be `return key.get_integer_field(field_name)`? - -#if defined(BOTAN_HAS_RSA) - if(const Botan::RSA_PublicKey* rsa = dynamic_cast<const Botan::RSA_PublicKey*>(&key)) +#if defined(BOTAN_HAS_ELGAMAL) + *key = nullptr; + try { - if(field == "n") - return rsa->get_n(); - else if(field == "e") - return rsa->get_e(); - else - throw Botan::Exception("Field not supported"); + Botan::DL_Group group(safe_get(p), safe_get(g)); + *key = new botan_pubkey_struct(new Botan::ElGamal_PublicKey(group, safe_get(y))); + return 0; } -#endif - -#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY) - // Handles DSA, ElGamal, etc - if(const Botan::DL_Scheme_PublicKey* dl = dynamic_cast<const Botan::DL_Scheme_PublicKey*>(&key)) + catch(std::exception& exn) { - if(field == "p") - return dl->group_p(); - else if(field == "q") - return dl->group_q(); - else if(field == "g") - return dl->group_g(); - else if(field == "y") - return dl->get_y(); - else - throw Botan::Exception("Field not supported"); + log_exception(BOTAN_CURRENT_FUNCTION, exn.what()); } -#endif -#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) - if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key)) - { - if(field == "public_x") - return ecc->public_point().get_affine_x(); - else if(field == "public_y") - return ecc->public_point().get_affine_y(); - else if(field == "base_x") - return ecc->domain().get_base_point().get_affine_x(); - else if(field == "base_y") - return ecc->domain().get_base_point().get_affine_y(); - else if(field == "p") - return ecc->domain().get_curve().get_p(); - else if(field == "a") - return ecc->domain().get_curve().get_a(); - else if(field == "b") - return ecc->domain().get_curve().get_b(); - else if(field == "cofactor") - return ecc->domain().get_cofactor(); - else if(field == "order") - return ecc->domain().get_order(); - else - throw Botan::Exception("Field not supported"); - } + return -1; +#else + BOTAN_UNUSED(key, p, g, y); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif - - // Some other algorithm type not supported by this function - throw Botan::Exception("Unsupported algorithm type for botan_pubkey_get_field"); } -Botan::BigInt botan_privkey_do_get_field(const Botan::Private_Key& key, - const std::string& field) +int botan_privkey_load_elgamal(botan_privkey_t* key, + botan_mp_t p, botan_mp_t g, botan_mp_t x) { - //return key.get_integer_field(field); - -#if defined(BOTAN_HAS_RSA) - - if(const Botan::RSA_PrivateKey* rsa = dynamic_cast<const Botan::RSA_PrivateKey*>(&key)) - { - if(field == "p") - return rsa->get_p(); - else if(field == "q") - return rsa->get_q(); - else if(field == "d") - return rsa->get_d(); - else if(field == "c") - return rsa->get_c(); - else if(field == "d1") - return rsa->get_d1(); - else if(field == "d2") - return rsa->get_d2(); - else - return botan_pubkey_do_get_field(key, field); - } -#endif - -#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY) - // Handles DSA, ElGamal, etc - if(const Botan::DL_Scheme_PrivateKey* dl = dynamic_cast<const Botan::DL_Scheme_PrivateKey*>(&key)) +#if defined(BOTAN_HAS_ELGAMAL) + *key = nullptr; + try { - if(field == "x") - return dl->get_x(); - else - return botan_pubkey_do_get_field(key, field); + Botan::Null_RNG null_rng; + Botan::DL_Group group(safe_get(p), safe_get(g)); + *key = new botan_privkey_struct(new Botan::ElGamal_PrivateKey(null_rng, group, safe_get(x))); + return 0; } -#endif - -#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) - if(const Botan::EC_PrivateKey* ecc = dynamic_cast<const Botan::EC_PrivateKey*>(&key)) + catch(std::exception& e) { - if(field == "x") - return ecc->private_value(); - else - return botan_pubkey_do_get_field(key, field); + log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } + return -1; +#else + BOTAN_UNUSED(key, p, g, x); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif - - // Some other algorithm type not supported by this function - throw Botan::Exception("Unsupported algorithm type for botan_privkey_get_field"); } -} - int botan_pubkey_get_field(botan_mp_t output, botan_pubkey_t key, const char* field_name_cstr) @@ -1551,7 +1591,7 @@ int botan_pubkey_get_field(botan_mp_t output, const std::string field_name(field_name_cstr); return BOTAN_FFI_DO(Botan::Public_Key, key, k, { - safe_get(output) = botan_pubkey_do_get_field(k, field_name); + safe_get(output) = pubkey_get_field(k, field_name); }); } @@ -1565,7 +1605,7 @@ int botan_privkey_get_field(botan_mp_t output, const std::string field_name(field_name_cstr); return BOTAN_FFI_DO(Botan::Private_Key, key, k, { - safe_get(output) = botan_privkey_do_get_field(k, field_name); + safe_get(output) = privkey_get_field(k, field_name); }); } diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 59e767385..8fb7ca832 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -175,7 +175,7 @@ typedef struct botan_rng_struct* botan_rng_t; * "system": System_RNG, "user": AutoSeeded_RNG * Set rng_type to null or empty string to let the library choose * -* TODO: replace rng_type with simple flags? +* TODO: replace rng_type with simple flags? */ BOTAN_DLL int botan_rng_init(botan_rng_t* rng, const char* rng_type); @@ -785,6 +785,45 @@ BOTAN_DLL int botan_pubkey_dsa_get_g(botan_mp_t d, botan_pubkey_t key); BOTAN_DLL int botan_pubkey_dsa_get_y(botan_mp_t y, botan_pubkey_t key); /* +* Algorithm specific key operations: ElGamal +*/ + +/* +* Loads ElGamal public key +* @param key variable populated with key material +* @param p prime order of a Z_p group +* @param g group generator +* @param y public key +* +* @pre key is NULL on input +* @post function allocates memory and assigns to `key' +* +* @return 0 on success, a negative value on failure +*/ +BOTAN_DLL int botan_pubkey_load_elgamal(botan_pubkey_t* key, + botan_mp_t p, + botan_mp_t g, + botan_mp_t y); + +/* +* Loads ElGamal private key +* +* @param key variable populated with key material +* @param p prime order of a Z_p group +* @param g group generator +* @param x private key +* +* @pre key is NULL on input +* @post function allocates memory and assigns to `key' +* +* @return 0 on success, a negative value on failure +*/ +BOTAN_DLL int botan_privkey_load_elgamal(botan_privkey_t* key, + botan_mp_t p, + botan_mp_t g, + botan_mp_t x); + +/* * Public Key Encryption */ typedef struct botan_pk_op_encrypt_struct* botan_pk_op_encrypt_t; 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/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index b20e60218..cccbb4f7e 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -188,9 +188,7 @@ class BOTAN_DLL Callbacks */ virtual void tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) { - BOTAN_UNUSED(descr); - BOTAN_UNUSED(val); - BOTAN_UNUSED(val_len); + BOTAN_UNUSED(descr, val, val_len); } }; diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index ad95b7eb5..6ddfff749 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -502,6 +502,18 @@ class BOTAN_DLL Text_Policy : public Policy size_t minimum_signature_strength() const override { return get_len("minimum_signature_strength", Policy::minimum_signature_strength()); } + size_t dtls_default_mtu() const override + { return get_len("dtls_default_mtu", Policy::dtls_default_mtu()); } + + size_t dtls_initial_timeout() const override + { return get_len("dtls_initial_timeout", Policy::dtls_initial_timeout()); } + + size_t dtls_maximum_timeout() const override + { return get_len("dtls_maximum_timeout", Policy::dtls_maximum_timeout()); } + + bool require_cert_revocation_info() const override + { return get_bool("require_cert_revocation_info", Policy::require_cert_revocation_info()); } + bool hide_unknown_users() const override { return get_bool("hide_unknown_users", Policy::hide_unknown_users()); } @@ -532,7 +544,7 @@ class BOTAN_DLL Text_Policy : public Policy explicit Text_Policy(std::istream& in) : m_kv(read_cfg(in)) {} - private: + protected: std::vector<std::string> get_list(const std::string& key, const std::vector<std::string>& def) const diff --git a/src/lib/utils/assert.h b/src/lib/utils/assert.h index c49ae62ee..ab459464e 100644 --- a/src/lib/utils/assert.h +++ b/src/lib/utils/assert.h @@ -1,6 +1,7 @@ /* * Runtime assertion checking * (C) 2010 Jack Lloyd +* 2017 Simon Warta (Kullo GmbH) * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -87,9 +88,32 @@ BOTAN_NORETURN void BOTAN_DLL assertion_failure(const char* expr_str, } while(0) /** -* Mark variable as unused +* Mark variable as unused. Takes between 1 and 9 arguments and marks all as unused, +* e.g. BOTAN_UNUSED(a); or BOTAN_UNUSED(x, y, z); */ -#define BOTAN_UNUSED(v) static_cast<void>(v) +#define _BOTAN_UNUSED_IMPL1(a) static_cast<void>(a) +#define _BOTAN_UNUSED_IMPL2(a, b) static_cast<void>(a); _BOTAN_UNUSED_IMPL1(b) +#define _BOTAN_UNUSED_IMPL3(a, b, c) static_cast<void>(a); _BOTAN_UNUSED_IMPL2(b, c) +#define _BOTAN_UNUSED_IMPL4(a, b, c, d) static_cast<void>(a); _BOTAN_UNUSED_IMPL3(b, c, d) +#define _BOTAN_UNUSED_IMPL5(a, b, c, d, e) static_cast<void>(a); _BOTAN_UNUSED_IMPL4(b, c, d, e) +#define _BOTAN_UNUSED_IMPL6(a, b, c, d, e, f) static_cast<void>(a); _BOTAN_UNUSED_IMPL5(b, c, d, e, f) +#define _BOTAN_UNUSED_IMPL7(a, b, c, d, e, f, g) static_cast<void>(a); _BOTAN_UNUSED_IMPL6(b, c, d, e, f, g) +#define _BOTAN_UNUSED_IMPL8(a, b, c, d, e, f, g, h) static_cast<void>(a); _BOTAN_UNUSED_IMPL7(b, c, d, e, f, g, h) +#define _BOTAN_UNUSED_IMPL9(a, b, c, d, e, f, g, h, i) static_cast<void>(a); _BOTAN_UNUSED_IMPL8(b, c, d, e, f, g, h, i) +#define _BOTAN_UNUSED_GET_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, IMPL_NAME, ...) IMPL_NAME + +#define BOTAN_UNUSED(...) _BOTAN_UNUSED_GET_IMPL(__VA_ARGS__, \ + _BOTAN_UNUSED_IMPL9, \ + _BOTAN_UNUSED_IMPL8, \ + _BOTAN_UNUSED_IMPL7, \ + _BOTAN_UNUSED_IMPL6, \ + _BOTAN_UNUSED_IMPL5, \ + _BOTAN_UNUSED_IMPL4, \ + _BOTAN_UNUSED_IMPL3, \ + _BOTAN_UNUSED_IMPL2, \ + _BOTAN_UNUSED_IMPL1, \ + unused dummy rest value \ + ) /* we got an one of _BOTAN_UNUSED_IMPL*, now call it */ (__VA_ARGS__) } diff --git a/src/lib/utils/compiler.h b/src/lib/utils/compiler.h index 898412501..7c2593ed8 100644 --- a/src/lib/utils/compiler.h +++ b/src/lib/utils/compiler.h @@ -117,6 +117,15 @@ #endif /* +* Define BOTAN_ALIGNAS (for MSVC 2013) +*/ +#if defined(BOTAN_BUILD_COMPILER_IS_MSVC_2013) + #define BOTAN_ALIGNAS(n) /**/ +#else + #define BOTAN_ALIGNAS(n) alignas(n) +#endif + +/* * Define BOTAN_PARALLEL_FOR */ #if !defined(BOTAN_PARALLEL_FOR) diff --git a/src/lib/utils/parsing.cpp b/src/lib/utils/parsing.cpp index cd0c2409e..7583767f0 100644 --- a/src/lib/utils/parsing.cpp +++ b/src/lib/utils/parsing.cpp @@ -2,6 +2,7 @@ * Various string utils and parsing functions * (C) 1999-2007,2013,2014,2015 Jack Lloyd * (C) 2015 Simon Warta (Kullo GmbH) +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,6 +13,7 @@ #include <botan/loadstor.h> #include <limits> #include <set> +#include <algorithm> namespace Botan { @@ -337,22 +339,100 @@ std::string replace_char(const std::string& str, char from_char, char to_char) bool host_wildcard_match(const std::string& issued, const std::string& host) { if(issued == host) + { return true; + } - if(issued.size() > 2 && issued[0] == '*' && issued[1] == '.') + if(std::count(issued.begin(), issued.end(), '*') > 1) + { + return false; + } + + // first try to match the base, then the left-most label + // which can contain exactly one wildcard at any position + if(issued.size() > 2) { size_t host_i = host.find('.'); if(host_i == std::string::npos || host_i == host.size() - 1) + { return false; + } + + size_t issued_i = issued.find('.'); + if(issued_i == std::string::npos || issued_i == issued.size() - 1) + { + return false; + } const std::string host_base = host.substr(host_i + 1); - const std::string issued_base = issued.substr(2); + const std::string issued_base = issued.substr(issued_i + 1); + + // if anything but the left-most label doesn't equal, + // we are already out here + if(host_base != issued_base) + { + return false; + } + + // compare the left-most labels + std::string host_prefix = host.substr(0, host_i); + + if(host_prefix.empty()) + { + return false; + } + + const std::string issued_prefix = issued.substr(0, issued_i); + + // if split_on would work on strings with less than 2 items, + // the if/else block would not be necessary + if(issued_prefix == "*") + { + return true; + } + + std::vector<std::string> p; - if(host_base == issued_base) + if(issued_prefix[0] == '*') + { + p = std::vector<std::string>{"", issued_prefix.substr(1, issued_prefix.size())}; + } + else if(issued_prefix[issued_prefix.size()-1] == '*') + { + p = std::vector<std::string>{issued_prefix.substr(0, issued_prefix.size() - 1), ""}; + } + else + { + p = split_on(issued_prefix, '*'); + } + + if(p.size() != 2) + { + return false; + } + + // match anything before and after the wildcard character + const std::string first = p[0]; + const std::string last = p[1]; + + if(host_prefix.substr(0, first.size()) == first) + { + host_prefix.erase(0, first.size()); + } + + // nothing to match anymore + if(last.empty()) + { return true; } + if(host_prefix.size() >= last.size() && + host_prefix.substr(host_prefix.size() - last.size(), last.size()) == last) + { + return true; + } + } + return false; } - } diff --git a/src/scripts/ci/travis/lint.sh b/src/scripts/ci/travis/lint.sh index 91407c3b4..16d7e5fc0 100755 --- a/src/scripts/ci/travis/lint.sh +++ b/src/scripts/ci/travis/lint.sh @@ -2,12 +2,16 @@ set -ev which shellcheck > /dev/null && shellcheck "$0" # Run shellcheck on this if available +# Disabled rules in Python 2 only +# superfluous-parens: needed for print statements +# too-many-locals: variable counting differs from pylint3 + echo "travis_fold:start:pylint_configure" python3 -m pylint configure.py -python2 -m pylint configure.py +python2 -m pylint --disable=superfluous-parens,too-many-locals configure.py echo "travis_fold:end:pylint_configure" echo "travis_fold:start:pylint_botanpy" -python3 -m pylint src/python/botan2.py -python2 -m pylint src/python/botan2.py +python3 -m pylint src/python/botan2.py || true +python2 -m pylint --disable=superfluous-parens,too-many-locals src/python/botan2.py || true echo "travis_fold:end:pylint_botanpy" diff --git a/src/tests/data/hostnames.vec b/src/tests/data/hostnames.vec new file mode 100644 index 000000000..91296d2d8 --- /dev/null +++ b/src/tests/data/hostnames.vec @@ -0,0 +1,66 @@ +# Test vectors derived from from RFC 6125 and OpenSSL test suite +# https://github.com/openssl/openssl/blob/master/test/v3nametest.c + +Issued = example +Hostname = example + +Issued = example.com +Hostname = example.com + +Issued = a.example.com +Hostname = a.example.com + +Issued = test.www.example.com +Hostname = test.www.example.com + +Issued = *.example.com +Hostname = foo.example.com + +Issued = baz*.example.net +Hostname = baz1.example.net + +Issued = baz*.example.net +Hostname = baz.example.net + +Issued = *baz.example.net +Hostname = foobaz.example.net + +Issued = *baz.example.net +Hostname = baz.example.net + +Issued = b*z.example.net +Hostname = buzz.example.net + +Issued = foo*bar.example.net +Hostname = foobar.example.net + +Issued = *.www.example.com +Hostname = test.www.example.com + +Issued = *www.example.com +Hostname = www.example.com + +[Invalid] +Issued = example.com +Hostname = www.example.com + +Issued = www.example.com +Hostname = example.com + +Issued = bar.*.example.net +Hostname = bar.foo.example.net + +Issued = *.example.com +Hostname = bar.foo.example.com + +Issued = *.example.com +Hostname = example.com + +Issued = foo*foo.example.com +Hostname = foo.example.com + +Issued = **.example.com +Hostname = foo.example.com + +Issued = *.*.example.com +Hostname = foo.bar.example.com 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/main.cpp b/src/tests/main.cpp index cb105db18..c90aaec7b 100644 --- a/src/tests/main.cpp +++ b/src/tests/main.cpp @@ -35,7 +35,7 @@ namespace { class Test_Runner : public Botan_CLI::Command { public: - Test_Runner() : Command("test --threads=0 --run-long-tests --run-online-tests --test-runs=1 --drbg-seed= --data-dir= --pkcs11-lib= --log-success *suites") {} + Test_Runner() : Command("test --threads=0 --run-long-tests --run-online-tests --test-runs=1 --drbg-seed= --data-dir= --pkcs11-lib= --provider= --log-success *suites") {} std::string help_text() const override { @@ -79,6 +79,7 @@ class Test_Runner : public Botan_CLI::Command const bool run_long_tests = flag_set("run-long-tests"); const std::string data_dir = get_arg_or("data-dir", "src/tests/data"); const std::string pkcs11_lib = get_arg("pkcs11-lib"); + const std::string provider = get_arg("provider"); const size_t runs = get_arg_sz("test-runs"); std::vector<std::string> req = get_arg_list("suites"); @@ -146,6 +147,13 @@ class Test_Runner : public Botan_CLI::Command output() << " pkcs11 library:" << pkcs11_lib; } + Botan_Tests::Provider_Filter pf; + if(!provider.empty()) + { + output() << " provider:" << provider; + pf.set(provider); + } + std::unique_ptr<Botan::RandomNumberGenerator> rng; #if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_HAS_SHA2_64) @@ -186,7 +194,7 @@ class Test_Runner : public Botan_CLI::Command output() << "\n"; Botan_Tests::Test::setup_tests(log_success, run_online_tests, run_long_tests, - data_dir, pkcs11_lib, rng.get()); + data_dir, pkcs11_lib, pf, rng.get()); for(size_t i = 0; i != runs; ++i) { diff --git a/src/tests/test_asn1.cpp b/src/tests/test_asn1.cpp new file mode 100644 index 000000000..3427d391d --- /dev/null +++ b/src/tests/test_asn1.cpp @@ -0,0 +1,67 @@ +/* +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_ASN1) + #include <botan/der_enc.h> + #include <botan/ber_dec.h> +#endif + +namespace Botan_Tests { + +#if defined(BOTAN_HAS_ASN1) + +namespace { + +Test::Result test_ber_stack_recursion() + { + Test::Result result("BER stack recursion"); + + // OSS-Fuzz #813 GitHub #989 + + try + { + const std::vector<uint8_t> in(10000000, 0); + Botan::DataSource_Memory input(in.data(), in.size()); + Botan::BER_Decoder dec(input); + + while(dec.more_items()) + { + Botan::BER_Object obj; + dec.get_next(obj); + } + } + catch(Botan::Decoding_Error&) + { + } + + result.test_success("No crash"); + + return result; + } + +} + +class ASN1_Tests : public Test + { + public: + std::vector<Test::Result> run() override + { + std::vector<Test::Result> results; + + results.push_back(test_ber_stack_recursion()); + + return results; + } + }; + +BOTAN_REGISTER_TEST("asn1", ASN1_Tests); + +#endif + +} + diff --git a/src/tests/test_block.cpp b/src/tests/test_block.cpp index 20d887794..62f32c21c 100644 --- a/src/tests/test_block.cpp +++ b/src/tests/test_block.cpp @@ -14,6 +14,11 @@ class Block_Cipher_Tests : public Text_Based_Test public: Block_Cipher_Tests() : Text_Based_Test("block", "Key,In,Out") {} + std::vector<std::string> possible_providers(const std::string& algo) override + { + return provider_filter(Botan::BlockCipher::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"); @@ -22,7 +27,7 @@ class Block_Cipher_Tests : public Text_Based_Test Test::Result result(algo); - const std::vector<std::string> providers = Botan::BlockCipher::providers(algo); + const std::vector<std::string> providers = possible_providers(algo); if(providers.empty()) { diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index c84dfd224..d5ddace04 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -234,71 +234,6 @@ class FFI_Unit_Tests : public Test TEST_FFI_FAIL("bad password", botan_bcrypt_is_valid, ("nope", outstr.data())); } - // TODO: Cipher test - /* - botan_cipher_t cipher_encrypt, cipher_decrypt; - - if(TEST_FFI_OK(botan_cipher_init, (&cipher_encrypt, "AES-128/CBC", BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) - { - size_t min_keylen = 0; - size_t max_keylen = 0; - TEST_FFI_OK(botan_cipher_query_keylen, (cipher_encrypt, &min_keylen, &max_keylen)); - result.test_int_eq(min_keylen, 16, "Min key length"); - result.test_int_eq(max_keylen, 16, "Max key length"); - - std::vector<uint8_t> plaintext(256); - std::vector<uint8_t> nonce(16); - TEST_FFI_OK(botan_rng_get, (rng, plaintext.data(), plaintext.size())); - TEST_FFI_OK(botan_rng_get, (rng, nonce.data(), nonce.size())); - - std::vector<uint8_t> ciphertext(plaintext.size()); // TODO: no way to know this size from API - - const std::vector<uint8_t> symkey = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, - 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; - - size_t output_written = 0; - size_t input_consumed = 0; - - // Test that after clear or final the object can be reused - for(size_t r = 0; r != 2; ++r) - { - TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); - TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); - TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, 0, ciphertext.data(), ciphertext.size(), &output_written, - plaintext.data(), plaintext.size(), &input_consumed)); - TEST_FFI_OK(botan_cipher_clear, (cipher_encrypt)); - - TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); - TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); - TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, ciphertext.data(), ciphertext.size(), &output_written, - plaintext.data(), plaintext.size(), &input_consumed)); - - if(TEST_FFI_OK(botan_cipher_init, (&cipher_decrypt, "AES-128/CBC", BOTAN_CIPHER_INIT_FLAG_DECRYPT))) - { - std::vector<uint8_t> decrypted(plaintext.size()); - - TEST_FFI_OK(botan_cipher_start, (cipher_decrypt, nonce.data(), nonce.size())); - TEST_FFI_OK(botan_cipher_set_key, (cipher_decrypt, symkey.data(), symkey.size())); - TEST_FFI_OK(botan_cipher_update, (cipher_decrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, decrypted.data(), decrypted.size(), &output_written, - ciphertext.data(), ciphertext.size(), &input_consumed)); - - result.test_eq("AES plaintext", decrypted, plaintext); - - TEST_FFI_OK(botan_cipher_destroy, (cipher_decrypt)); - } - } - - TEST_FFI_OK(botan_cipher_destroy, (cipher_encrypt)); - } - */ - - // TODO: AEAD test - // TODO ONCE MORE WITH AES-GCM - // botan_cipher_set_associated_data - // botan_cipher_valid_nonce_length - // botan_cipher_get_default_nonce_length - // botan_cipher_get_tag_length - // x509 cert test botan_x509_cert_t cert; if(TEST_FFI_OK(botan_x509_cert_load_file, (&cert, "src/tests/data/ecc/CSCA.CSCA.csca-germany.1.crt"))) @@ -388,6 +323,9 @@ class FFI_Unit_Tests : public Test std::vector<Test::Result> results; results.push_back(ffi_test_mp(rng)); results.push_back(ffi_test_block_ciphers()); + results.push_back(ffi_test_ciphers_cbc()); + results.push_back(ffi_test_ciphers_aead()); + results.push_back(ffi_test_stream_ciphers()); #if defined(BOTAN_HAS_RSA) results.push_back(ffi_test_rsa(rng)); @@ -409,6 +347,10 @@ class FFI_Unit_Tests : public Test results.push_back(ffi_test_mceliece(rng)); #endif +#if defined(BOTAN_HAS_ELGAMAL) + results.push_back(ffi_test_elgamal(rng)); +#endif + TEST_FFI_OK(botan_rng_destroy, (rng)); results.push_back(result); @@ -416,6 +358,208 @@ class FFI_Unit_Tests : public Test } private: + Test::Result ffi_test_ciphers_cbc() + { + Test::Result result("FFI CBC cipher"); + + botan_cipher_t cipher_encrypt, cipher_decrypt; + + if(TEST_FFI_OK(botan_cipher_init, (&cipher_encrypt, "AES-128/CBC/PKCS7", BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) + { + size_t min_keylen = 0; + size_t max_keylen = 0; + TEST_FFI_OK(botan_cipher_query_keylen, (cipher_encrypt, &min_keylen, &max_keylen)); + result.test_int_eq(min_keylen, 16, "Min key length"); + result.test_int_eq(max_keylen, 16, "Max key length"); + + // from https://github.com/geertj/bluepass/blob/master/tests/vectors/aes-cbc-pkcs7.txt + const std::vector<uint8_t> plaintext = Botan::hex_decode("0397f4f6820b1f9386f14403be5ac16e50213bd473b4874b9bcbf5f318ee686b1d"); + const std::vector<uint8_t> symkey = Botan::hex_decode("898be9cc5004ed0fa6e117c9a3099d31"); + const std::vector<uint8_t> nonce = Botan::hex_decode("9dea7621945988f96491083849b068df"); + const std::vector<uint8_t> exp_ciphertext = Botan::hex_decode("e232cd6ef50047801ee681ec30f61d53cfd6b0bca02fd03c1b234baa10ea82ac9dab8b960926433a19ce6dea08677e34"); + + std::vector<uint8_t> ciphertext(16 + plaintext.size()); // TODO: no way to know this size from API + + size_t output_written = 0; + size_t input_consumed = 0; + + // Test that after clear or final the object can be reused + for(size_t r = 0; r != 2; ++r) + { + TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, 0, ciphertext.data(), ciphertext.size(), &output_written, + plaintext.data(), plaintext.size(), &input_consumed)); + TEST_FFI_OK(botan_cipher_clear, (cipher_encrypt)); + + TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, ciphertext.data(), ciphertext.size(), &output_written, + plaintext.data(), plaintext.size(), &input_consumed)); + + ciphertext.resize(output_written); + result.test_eq("AES/CBC ciphertext", ciphertext, exp_ciphertext); + + if(TEST_FFI_OK(botan_cipher_init, (&cipher_decrypt, "AES-128/CBC", BOTAN_CIPHER_INIT_FLAG_DECRYPT))) + { + std::vector<uint8_t> decrypted(plaintext.size()); + + TEST_FFI_OK(botan_cipher_set_key, (cipher_decrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_decrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_decrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, decrypted.data(), decrypted.size(), &output_written, + ciphertext.data(), ciphertext.size(), &input_consumed)); + + result.test_eq("AES/CBC plaintext", decrypted, plaintext); + + TEST_FFI_OK(botan_cipher_destroy, (cipher_decrypt)); + } + } + + TEST_FFI_OK(botan_cipher_destroy, (cipher_encrypt)); + } + + return result; + } + + Test::Result ffi_test_ciphers_aead() + { + Test::Result result("FFI AEAD"); + +#if defined(BOTAN_HAS_AEAD_GCM) + + botan_cipher_t cipher_encrypt, cipher_decrypt; + + if(TEST_FFI_OK(botan_cipher_init, (&cipher_encrypt, "AES-128/GCM", BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) + { + size_t min_keylen = 0; + size_t max_keylen = 0; + size_t nonce_len = 0; + size_t tag_len = 0; + + TEST_FFI_OK(botan_cipher_query_keylen, (cipher_encrypt, &min_keylen, &max_keylen)); + result.test_int_eq(min_keylen, 16, "Min key length"); + result.test_int_eq(max_keylen, 16, "Max key length"); + + TEST_FFI_OK(botan_cipher_get_default_nonce_length, (cipher_encrypt, &nonce_len)); + result.test_int_eq(nonce_len, 12, "Expected default GCM nonce length"); + + TEST_FFI_OK(botan_cipher_get_tag_length, (cipher_encrypt, &tag_len)); + result.test_int_eq(tag_len, 16, "Expected GCM tag length"); + + TEST_FFI_RC(1, botan_cipher_valid_nonce_length, (cipher_encrypt, 12)); + // GCM accepts any nonce size... + TEST_FFI_RC(1, botan_cipher_valid_nonce_length, (cipher_encrypt, 0)); + + // NIST test vector + const std::vector<uint8_t> plaintext = + Botan::hex_decode("D9313225F88406E5A55909C5AFF5269A86A7A9531534F7DA2E4C303D8A318A721C3C0C95956809532FCF0E2449A6B525B16AEDF5AA0DE657BA637B39"); + + const std::vector<uint8_t> symkey = Botan::hex_decode("FEFFE9928665731C6D6A8F9467308308"); + const std::vector<uint8_t> nonce = Botan::hex_decode("CAFEBABEFACEDBADDECAF888"); + const std::vector<uint8_t> exp_ciphertext = Botan::hex_decode( + "42831EC2217774244B7221B784D0D49CE3AA212F2C02A4E035C17E2329ACA12E21D514B25466931C7D8F6A5AAC84AA051BA30B396A0AAC973D58E0915BC94FBC3221A5DB94FAE95AE7121A47"); + const std::vector<uint8_t> aad = Botan::hex_decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2"); + + std::vector<uint8_t> ciphertext(tag_len + plaintext.size()); + + size_t output_written = 0; + size_t input_consumed = 0; + + // Test that after clear or final the object can be reused + for(size_t r = 0; r != 2; ++r) + { + TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, 0, + ciphertext.data(), ciphertext.size(), &output_written, + plaintext.data(), plaintext.size(), &input_consumed)); + TEST_FFI_OK(botan_cipher_clear, (cipher_encrypt)); + + TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_set_associated_data, (cipher_encrypt, aad.data(), aad.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_encrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, + ciphertext.data(), ciphertext.size(), &output_written, + plaintext.data(), plaintext.size(), &input_consumed)); + + ciphertext.resize(output_written); + result.test_eq("AES/GCM ciphertext", ciphertext, exp_ciphertext); + + if(TEST_FFI_OK(botan_cipher_init, (&cipher_decrypt, "AES-128/GCM", BOTAN_CIPHER_INIT_FLAG_DECRYPT))) + { + std::vector<uint8_t> decrypted(plaintext.size()); + + TEST_FFI_OK(botan_cipher_set_key, (cipher_decrypt, symkey.data(), symkey.size())); + TEST_FFI_OK(botan_cipher_set_associated_data, (cipher_decrypt, aad.data(), aad.size())); + TEST_FFI_OK(botan_cipher_start, (cipher_decrypt, nonce.data(), nonce.size())); + TEST_FFI_OK(botan_cipher_update, (cipher_decrypt, BOTAN_CIPHER_UPDATE_FLAG_FINAL, + decrypted.data(), decrypted.size(), &output_written, + ciphertext.data(), ciphertext.size(), &input_consumed)); + + result.test_int_eq(input_consumed, ciphertext.size(), "All input consumed"); + result.test_int_eq(output_written, decrypted.size(), "Expected output size produced"); + result.test_eq("AES/GCM plaintext", decrypted, plaintext); + + TEST_FFI_OK(botan_cipher_destroy, (cipher_decrypt)); + } + } + + TEST_FFI_OK(botan_cipher_destroy, (cipher_encrypt)); + } +#endif + + return result; + } + + Test::Result ffi_test_stream_ciphers() + { + Test::Result result("FFI stream ciphers"); + +#if defined(BOTAN_HAS_CTR_BE) + + const std::vector<uint8_t> key = Botan::hex_decode("2B7E151628AED2A6ABF7158809CF4F3C"); + const std::vector<uint8_t> nonce = Botan::hex_decode("F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF"); + const std::vector<uint8_t> pt = Botan::hex_decode( + "AE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710"); + const std::vector<uint8_t> exp_ct = Botan::hex_decode( + "9806F66B7970FDFF8617187BB9FFFDFF5AE4DF3EDBD5D35E5B4F09020DB03EAB1E031DDA2FBE03D1792170A0F3009CEE"); + + botan_cipher_t ctr; + + std::vector<uint8_t> ct(pt.size()); + + if(TEST_FFI_OK(botan_cipher_init, (&ctr, "AES-128/CTR-BE", BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) + { + size_t input_consumed = 0; + size_t output_written = 0; + + TEST_FFI_OK(botan_cipher_set_key, (ctr, key.data(), key.size())); + TEST_FFI_OK(botan_cipher_start, (ctr, nonce.data(), nonce.size())); + + // Test partial updates... + TEST_FFI_OK(botan_cipher_update, (ctr, 0, + ct.data(), ct.size(), &output_written, + pt.data(), 5, &input_consumed)); + + result.test_int_eq(output_written, 5, "Expected output written"); + result.test_int_eq(input_consumed, 5, "Expected input consumed"); + + TEST_FFI_OK(botan_cipher_update, (ctr, 0, + &ct[5], ct.size() - 5, &output_written, + &pt[5], pt.size() - 5, &input_consumed)); + + result.test_int_eq(output_written, ct.size() - 5, "Expected output written"); + result.test_int_eq(input_consumed, pt.size() - 5, "Expected input consumed"); + result.test_eq("AES-128/CTR ciphertext", ct, exp_ct); + + TEST_FFI_OK(botan_cipher_destroy, (ctr)); + } + +#endif + + return result; + } + Test::Result ffi_test_block_ciphers() { Test::Result result("FFI block ciphers"); @@ -636,6 +780,9 @@ class FFI_Unit_Tests : public Test TEST_FFI_OK(botan_mp_to_str, (r, 10, str_buf, &str_len)); result.test_eq("botan_mp_mod_mul", std::string(str_buf), "123945920473931248854653259523111998693"); + str_len = 0; + TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_mp_to_str, (r, 10, str_buf, &str_len)); + size_t x_bytes; botan_mp_rand_bits(x, rng, 512); TEST_FFI_OK(botan_mp_num_bytes, (x, &x_bytes)); @@ -1151,6 +1298,78 @@ class FFI_Unit_Tests : public Test return result; } + + Test::Result ffi_test_elgamal(botan_rng_t rng) + { + Test::Result result("FFI ELGAMAL"); + + botan_privkey_t priv; + + if(TEST_FFI_OK(botan_privkey_create, (&priv, "ElGamal", nullptr, rng))) + { + TEST_FFI_OK(botan_privkey_check_key, (priv, rng, 0)); + + botan_pubkey_t pub; + TEST_FFI_OK(botan_privkey_export_pubkey, (&pub, priv)); + TEST_FFI_OK(botan_pubkey_check_key, (pub, rng, 0)); + + ffi_test_pubkey_export(result, pub, priv, rng); + botan_mp_t p, g, x, y; + botan_mp_init(&p); + botan_mp_init(&g); + botan_mp_init(&x); + botan_mp_init(&y); + + TEST_FFI_OK(botan_pubkey_get_field, (p, pub, "p")); + TEST_FFI_OK(botan_pubkey_get_field, (g, pub, "g")); + TEST_FFI_OK(botan_pubkey_get_field, (y, pub, "y")); + TEST_FFI_OK(botan_privkey_get_field,(x, priv,"x")); + + size_t p_len = 0; + TEST_FFI_OK(botan_mp_num_bytes, (p, &p_len)); + + botan_privkey_t loaded_privkey; + TEST_FFI_OK(botan_privkey_load_elgamal, (&loaded_privkey, p, g, x)); + TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey, rng, 0)); + + botan_pubkey_t loaded_pubkey; + TEST_FFI_OK(botan_pubkey_load_elgamal, (&loaded_pubkey, p, g, y)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey, rng, 0)); + + botan_mp_destroy(p); + botan_mp_destroy(g); + botan_mp_destroy(y); + botan_mp_destroy(x); + + + std::vector<uint8_t> plaintext(16, 0xFF); + std::vector<uint8_t> ciphertext(p_len*2, 0); + std::vector<uint8_t> decryption(16, 0); + + // Test encryption + botan_pk_op_encrypt_t op_enc; + size_t ct_len = ciphertext.size(); + REQUIRE_FFI_OK(botan_pk_op_encrypt_create, (&op_enc, loaded_pubkey, "Raw", 0)); + TEST_FFI_OK(botan_pk_op_encrypt, (op_enc, rng, ciphertext.data(), &ct_len, plaintext.data(), plaintext.size())); + TEST_FFI_OK(botan_pk_op_encrypt_destroy, (op_enc)); + + // Test decryption + botan_pk_op_decrypt_t op_dec; + size_t pt_len = decryption.size(); + REQUIRE_FFI_OK(botan_pk_op_decrypt_create, (&op_dec, loaded_privkey, "Raw", 0)); + TEST_FFI_OK(botan_pk_op_decrypt, (op_dec, decryption.data(), &pt_len, ciphertext.data(), ct_len)); + TEST_FFI_OK(botan_pk_op_decrypt_destroy, (op_dec)); + + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey)); + TEST_FFI_OK(botan_pubkey_destroy, (pub)); + TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey)); + TEST_FFI_OK(botan_privkey_destroy, (priv)); + } + + return result; + } + + }; BOTAN_REGISTER_TEST("ffi", FFI_Unit_Tests); diff --git a/src/tests/test_hash.cpp b/src/tests/test_hash.cpp index fd255131b..6584a20d1 100644 --- a/src/tests/test_hash.cpp +++ b/src/tests/test_hash.cpp @@ -17,6 +17,11 @@ class Hash_Function_Tests : public Text_Based_Test public: Hash_Function_Tests() : Text_Based_Test("hash", "In,Out") {} + std::vector<std::string> possible_providers(const std::string& algo) override + { + return provider_filter(Botan::HashFunction::providers(algo)); + } + Test::Result run_one_test(const std::string& algo, const VarMap& vars) override { const std::vector<uint8_t> input = get_req_bin(vars, "In"); @@ -24,7 +29,7 @@ class Hash_Function_Tests : public Text_Based_Test Test::Result result(algo); - const std::vector<std::string> providers = Botan::HashFunction::providers(algo); + const std::vector<std::string> providers = possible_providers(algo); if(providers.empty()) { diff --git a/src/tests/test_mac.cpp b/src/tests/test_mac.cpp index 8fb3b38c2..2e12ee6ed 100644 --- a/src/tests/test_mac.cpp +++ b/src/tests/test_mac.cpp @@ -22,6 +22,11 @@ class Message_Auth_Tests : public Text_Based_Test public: Message_Auth_Tests() : Text_Based_Test("mac", "Key,In,Out", "IV") {} + std::vector<std::string> possible_providers(const std::string& algo) override + { + return provider_filter(Botan::MessageAuthenticationCode::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,7 +36,7 @@ class Message_Auth_Tests : public Text_Based_Test Test::Result result(algo); - const std::vector<std::string> providers = Botan::MessageAuthenticationCode::providers(algo); + const std::vector<std::string> providers = possible_providers(algo); if(providers.empty()) { 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; } diff --git a/src/tests/test_pubkey.cpp b/src/tests/test_pubkey.cpp index 9a8c5d2e7..d392437c9 100644 --- a/src/tests/test_pubkey.cpp +++ b/src/tests/test_pubkey.cpp @@ -19,15 +19,6 @@ namespace Botan_Tests { -namespace { - -std::vector<std::string> possible_pk_providers() - { - return { "base", "openssl", "tpm" }; - } - -} - void check_invalid_signatures(Test::Result& result, Botan::PK_Verifier& verifier, const std::vector<uint8_t>& message, @@ -91,6 +82,11 @@ void check_invalid_ciphertexts(Test::Result& result, " invalid ciphertexts, rejected " + std::to_string(ciphertext_rejected)); } +std::vector<std::string> PK_Test::possible_providers(const std::string&) + { + return Test::provider_filter({ "base", "openssl", "tpm" }); + } + Test::Result PK_Signature_Generation_Test::run_one_test(const std::string&, const VarMap& vars) { @@ -115,7 +111,7 @@ PK_Signature_Generation_Test::run_one_test(const std::string&, const VarMap& var std::vector<std::unique_ptr<Botan::PK_Verifier>> verifiers; - for(std::string verify_provider : possible_pk_providers()) + for(std::string verify_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Verifier> verifier; @@ -135,7 +131,7 @@ PK_Signature_Generation_Test::run_one_test(const std::string&, const VarMap& var verifiers.push_back(std::move(verifier)); } - for(auto&& sign_provider : possible_pk_providers()) + for(auto&& sign_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::RandomNumberGenerator> rng; if(vars.count("Nonce")) @@ -189,7 +185,7 @@ PK_Signature_Verification_Test::run_one_test(const std::string&, const VarMap& v Test::Result result(algo_name() + "/" + padding + " signature verification"); - for(auto&& verify_provider : possible_pk_providers()) + for(auto&& verify_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Verifier> verifier; @@ -219,7 +215,7 @@ PK_Signature_NonVerification_Test::run_one_test(const std::string&, const VarMap Test::Result result(algo_name() + "/" + padding + " verify invalid signature"); - for(auto&& verify_provider : possible_pk_providers()) + for(auto&& verify_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Verifier> verifier; @@ -255,7 +251,7 @@ PK_Encryption_Decryption_Test::run_one_test(const std::string&, const VarMap& va std::vector<std::unique_ptr<Botan::PK_Decryptor>> decryptors; - for(auto&& dec_provider : possible_pk_providers()) + for(auto&& dec_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Decryptor> decryptor; @@ -273,7 +269,7 @@ PK_Encryption_Decryption_Test::run_one_test(const std::string&, const VarMap& va } - for(auto&& enc_provider : possible_pk_providers()) + for(auto&& enc_provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Encryptor> encryptor; @@ -388,7 +384,7 @@ Test::Result PK_Key_Agreement_Test::run_one_test(const std::string& header, cons const size_t key_len = get_opt_sz(vars, "OutLen", 0); - for(auto&& provider : possible_pk_providers()) + for(auto&& provider : possible_providers(algo_name())) { std::unique_ptr<Botan::PK_Key_Agreement> kas; diff --git a/src/tests/test_pubkey.h b/src/tests/test_pubkey.h index cbcc38f9d..7709f51c7 100644 --- a/src/tests/test_pubkey.h +++ b/src/tests/test_pubkey.h @@ -30,6 +30,9 @@ class PK_Test : public Text_Based_Test std::string algo_name() const { return m_algo; } + protected: + std::vector<std::string> possible_providers(const std::string&) override; + private: std::string m_algo; }; diff --git a/src/tests/test_stream.cpp b/src/tests/test_stream.cpp index 9250f4e2f..c2c7ce901 100644 --- a/src/tests/test_stream.cpp +++ b/src/tests/test_stream.cpp @@ -32,7 +32,8 @@ class Stream_Cipher_Tests : public Text_Based_Test Test::Result result(algo); - const std::vector<std::string> providers = Botan::StreamCipher::providers(algo); + const std::vector<std::string> providers = + provider_filter(Botan::StreamCipher::providers(algo)); if(providers.empty()) { diff --git a/src/tests/test_utils.cpp b/src/tests/test_utils.cpp index a2601722d..8c1d353b4 100644 --- a/src/tests/test_utils.cpp +++ b/src/tests/test_utils.cpp @@ -1,6 +1,7 @@ /* * (C) 2015 Jack Lloyd * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -12,6 +13,7 @@ #include <botan/calendar.h> #include <botan/internal/rounding.h> #include <botan/charset.h> +#include <botan/parsing.h> #if defined(BOTAN_HAS_BASE64_CODEC) #include <botan/base64.h> @@ -454,6 +456,32 @@ class Charset_Tests : public Text_Based_Test BOTAN_REGISTER_TEST("charset", Charset_Tests); +class Hostname_Tests : public Text_Based_Test + { + public: + Hostname_Tests() : Text_Based_Test("hostnames.vec", "Issued,Hostname") + {} + + Test::Result run_one_test(const std::string& type, const VarMap& vars) override + { + using namespace Botan; + + Test::Result result("Hostname"); + + const std::string issued = get_req_str(vars, "Issued"); + const std::string hostname = get_req_str(vars, "Hostname"); + const bool expected = (type == "Invalid") ? false : true; + + const std::string what = hostname + ((expected == true) ? + " matches " : " does not match ") + issued; + result.test_eq(what, Botan::host_wildcard_match(issued, hostname), expected); + + return result; + } + }; + +BOTAN_REGISTER_TEST("hostname", Hostname_Tests); + } } diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp index e4a755831..a678c64ae 100644 --- a/src/tests/tests.cpp +++ b/src/tests/tests.cpp @@ -417,6 +417,22 @@ std::string Test::Result::result_string(bool verbose) const return report.str(); } +std::vector<std::string> Provider_Filter::filter(const std::vector<std::string> &in) const + { + if(m_provider.empty()) + { + return in; + } + for(auto&& provider : in) + { + if(provider == m_provider) + { + return std::vector<std::string> { provider }; + } + } + return std::vector<std::string> {}; + } + // static Test:: functions //static std::map<std::string, std::unique_ptr<Test>>& Test::global_registry() @@ -488,6 +504,7 @@ bool Test::m_log_success = false; bool Test::m_run_online_tests = false; bool Test::m_run_long_tests = false; std::string Test::m_pkcs11_lib; +Botan_Tests::Provider_Filter Test::m_provider_filter; //static void Test::setup_tests(bool log_success, @@ -495,6 +512,7 @@ void Test::setup_tests(bool log_success, bool run_long, const std::string& data_dir, const std::string& pkcs11_lib, + const Botan_Tests::Provider_Filter& pf, Botan::RandomNumberGenerator* rng) { m_data_dir = data_dir; @@ -503,6 +521,7 @@ void Test::setup_tests(bool log_success, m_run_long_tests = run_long; m_test_rng = rng; m_pkcs11_lib = pkcs11_lib; + m_provider_filter = pf; } //static @@ -542,6 +561,12 @@ std::string Test::pkcs11_lib() } //static +std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in) + { + return m_provider_filter.filter(in); + } + +//static Botan::RandomNumberGenerator& Test::rng() { if(!m_test_rng) @@ -824,6 +849,11 @@ parse_cpuid_bits(const std::vector<std::string>& tok) } +std::vector<std::string> Text_Based_Test::possible_providers(const std::string&) + { + return Test::provider_filter({ "base" }); + } + bool Text_Based_Test::skip_this_test(const std::string& /*header*/, const VarMap& /*vars*/) { @@ -868,6 +898,7 @@ std::vector<Test::Result> Text_Based_Test::run() header = line.substr(1, line.size() - 2); header_or_name = header; test_cnt = 0; + vars.clear(); continue; } @@ -895,7 +926,8 @@ std::vector<Test::Result> Text_Based_Test::run() { try { - if(skip_this_test(header, vars)) + if(possible_providers(header).empty() || + skip_this_test(header, vars)) { continue; } @@ -938,6 +970,11 @@ std::vector<Test::Result> Text_Based_Test::run() } } + if(results.empty()) + { + return results; + } + try { std::vector<Test::Result> final_tests = run_final_tests(); diff --git a/src/tests/tests.h b/src/tests/tests.h index f90e0037b..406cff57a 100644 --- a/src/tests/tests.h +++ b/src/tests/tests.h @@ -46,6 +46,16 @@ class Test_Error : public Botan::Exception explicit Test_Error(const std::string& what) : Exception("Test error", what) {} }; +class Provider_Filter + { + public: + Provider_Filter() {} + void set(const std::string& provider) { m_provider = provider; } + std::vector<std::string> filter(const std::vector<std::string> &) const; + private: + std::string m_provider; + }; + /* * A generic test which returns a set of results when run. * The tests may not all have the same type (for example test @@ -370,12 +380,14 @@ class Test bool run_long_tests, const std::string& data_dir, const std::string& pkcs11_lib, + const Botan_Tests::Provider_Filter& pf, Botan::RandomNumberGenerator* rng); static bool log_success(); static bool run_online_tests(); static bool run_long_tests(); static std::string pkcs11_lib(); + static std::vector<std::string> provider_filter(const std::vector<std::string>&); static const std::string& data_dir(); @@ -388,6 +400,7 @@ class Test static Botan::RandomNumberGenerator* m_test_rng; static bool m_log_success, m_run_online_tests, m_run_long_tests; static std::string m_pkcs11_lib; + static Botan_Tests::Provider_Filter m_provider_filter; }; /* @@ -428,8 +441,8 @@ class Text_Based_Test : public Test virtual Test::Result run_one_test(const std::string& header, const VarMap& vars) = 0; - // Called before run_one_test + virtual std::vector<std::string> possible_providers(const std::string&); virtual bool skip_this_test(const std::string& header, const VarMap& vars); |