diff options
author | Fabian Weissberg <[email protected]> | 2017-12-21 13:33:23 +0100 |
---|---|---|
committer | Fabian Weissberg <[email protected]> | 2017-12-22 09:13:22 +0100 |
commit | 53b8e128121c6f845e2196d79bce3dd39bd83228 (patch) | |
tree | 8e8ad207fec7db3373eec5fd1ab3850ff5b02898 | |
parent | c9940f43eb54c1e02cf07454bedcccf7c0873eed (diff) |
Enable signing X509 structures with rsa-pss
27 files changed, 821 insertions, 64 deletions
diff --git a/doc/manual/cli.rst b/doc/manual/cli.rst index 85b93d5e1..ecebe74e5 100644 --- a/doc/manual/cli.rst +++ b/doc/manual/cli.rst @@ -95,23 +95,34 @@ Public Key Cryptography X.509 ---------------------------------------------- -``gen_pkcs10 key CN --country= --organization= --email= --key-pass= --hash=SHA-256`` +<<<<<<< HEAD +``gen_pkcs10 key CN --country= --organization= --email= --key-pass= --hash=SHA-256 --emsa=`` Generate a PKCS #10 certificate signing request (CSR) using the passed PKCS #8 private key *key*. If the private key is encrypted, the decryption passphrase - *key-pass* has to be passed. + *key-pass* has to be passed.*emsa* specifies the padding scheme to be used + when calculating the signature. + + - For RSA keys EMSA4 (RSA-PSS) is the default scheme. + - For ECDSA, DSA, ECGDSA, ECKCDSA and GOST-34.10 keys *emsa* defaults to EMSA1. -``gen_self_signed key CN --country= --dns= --organization= --email= --key-pass= --ca --hash=SHA-256`` +``gen_self_signed key CN --country= --dns= --organization= --email= --key-pass= --ca --hash=SHA-256 --emsa=`` Generate a self signed X.509 certificate using the PKCS #8 private key *key*. If the private key is encrypted, the decryption passphrase *key-pass* has to be passed. If *ca* is passed, the certificate is marked for certificate - authority (CA) usage. + authority (CA) usage. *emsa* specifies the padding scheme to be used when + calculating the signature. -``sign_cert --ca-key-pass= --hash=SHA-256 --duration=365 ca_cert ca_key pkcs10_req`` + - For RSA keys EMSA4 (RSA-PSS) is the default scheme. + - For ECDSA, DSA, ECGDSA, ECKCDSA and GOST-34.10 keys *emsa* defaults to EMSA1. + +``sign_cert --ca-key-pass= --hash=SHA-256 --duration=365 --emsa= ca_cert ca_key pkcs10_req`` Create a CA signed X.509 certificate from the information contained in the PKCS #10 CSR *pkcs10_req*. The CA certificate is passed as *ca_cert* and the respective PKCS #8 private key as *ca_key*. If the private key is encrypted, the decryption passphrase *ca-key-pass* has to be passed. The created - certificate has a validity period of *duration* days. + certificate has a validity period of *duration* days. *emsa* specifies the + padding scheme to be used when calculating the signature. *emsa* defaults to + the padding scheme used in the CA certificate. ``ocsp_check subject issuer`` Verify an X.509 certificate against the issuers OCSP responder. Pass the diff --git a/doc/manual/x509.rst b/doc/manual/x509.rst index daf154c9a..f9431c362 100644 --- a/doc/manual/x509.rst +++ b/doc/manual/x509.rst @@ -531,6 +531,21 @@ The private ``key`` is the private key corresponding to the public key in the CA's certificate. ``hash_fn`` is the name of the hash function to use for signing, e.g., `SHA-256`. ``rng`` is queried for random during signing. +There is an alternative constructor that lets you set additional options, namely +the padding scheme that will be used by the X509_CA object to sign certificates +and certificate revocation lists. If the padding is not set explicitly, the CA +will use the padding scheme that was used when signing the CA certificate. + +.. cpp:function:: X509_CA::X509_CA(const X509_Certificate& cert, \ + const Private_Key& key, \ + const std::map<std::string,std::string>& opts, \ + const std::string& hash_fn, \ + RandomNumberGenerator& rng) + +The only option valid at this moment is "padding". The supported padding schemes +can be found in src/lib/pubkey/padding.cpp. Some alternative names for the +padding schemes are understood, as well. + Requests for new certificates are supplied to a CA in the form of PKCS #10 certificate requests (called a ``PKCS10_Request`` object in Botan). These are decoded in a similar manner to @@ -668,6 +683,15 @@ to want to use is to create (or request) a CA certificate, which can be done by calling the member function ``CA_key``. This should only be used when needed. +Moreover, you can specify the padding scheme to be used when digital signatures +are computed by calling function ``set_padding_scheme`` with a string +representing the padding scheme. This way, you can control the padding scheme +for self-signed certificates and PKCS #10 requests. The padding scheme used by +a CA when building a certificate or a certificate revocation list can be set in +the ``X509_CA`` constructor. The supported padding schemes can be found in +src/lib/pubkey/padding.cpp. Some alternative names for the padding schemes are +understood, as well. + Other constraints can be set by calling the member functions ``add_constraints`` and ``add_ex_constraints``. The first takes a ``Key_Constraints`` value, and replaces any previously set value. If diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index 6584f7e6d..089523e69 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -1,4 +1,8 @@ -# Regenerate with ./src/scripts/oids.py oids > src/lib/asn1/oids.cpp AND ./src/scripts/oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp +# Regenerate with ./src/scripts/oids.py oids > src/lib/asn1/oids.cpp +# AND ./src/scripts/oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp +# (if you modified something under [dn] +# AND ./src/scripts/oids.py pads > src/lib/pk_pad/padding.cpp +# (if you modified something under [signature] # Public key types [pubkey] diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp index 10e7a1c7f..0feaad003 100644 --- a/src/cli/x509.cpp +++ b/src/cli/x509.cpp @@ -29,7 +29,7 @@ class Sign_Cert final : public Command public: Sign_Cert() : Command("sign_cert --ca-key-pass= --hash=SHA-256 " - "--duration=365 ca_cert ca_key pkcs10_req") {} + "--duration=365 --emsa= ca_cert ca_key pkcs10_req") {} void go() override { @@ -51,7 +51,8 @@ class Sign_Cert final : public Command throw CLI_Error("Failed to load key from " + get_arg("ca_key")); } - Botan::X509_CA ca(ca_cert, *key, get_arg("hash"), rng()); + Botan::X509_CA ca(ca_cert, *key, + {{"padding",get_arg_or("emsa", "EMSA4")}}, get_arg("hash"), rng()); Botan::PKCS10_Request req(get_arg("pkcs10_req")); @@ -186,7 +187,7 @@ class Gen_Self_Signed final : public Command public: Gen_Self_Signed() : Command("gen_self_signed key CN --country= --dns= " - "--organization= --email= --key-pass= --ca --hash=SHA-256") {} + "--organization= --email= --key-pass= --ca --hash=SHA-256 --emsa=") {} void go() override { @@ -204,6 +205,7 @@ class Gen_Self_Signed final : public Command opts.organization = get_arg("organization"); opts.email = get_arg("email"); opts.dns = get_arg("dns"); + opts.set_padding_scheme(get_arg_or("emsa", "EMSA4")); if(flag_set("ca")) { @@ -223,7 +225,7 @@ class Generate_PKCS10 final : public Command public: Generate_PKCS10() : Command("gen_pkcs10 key CN --country= --organization= " - "--email= --key-pass= --hash=SHA-256") {} + "--email= --key-pass= --hash=SHA-256 --emsa=") {} void go() override { @@ -240,6 +242,7 @@ class Generate_PKCS10 final : public Command opts.country = get_arg("country"); opts.organization = get_arg("organization"); opts.email = get_arg("email"); + opts.set_padding_scheme(get_arg_or("emsa", "EMSA4")); Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, get_arg("hash"), rng()); diff --git a/src/lib/pk_pad/emsa.h b/src/lib/pk_pad/emsa.h index d45185f30..db835f1bd 100644 --- a/src/lib/pk_pad/emsa.h +++ b/src/lib/pk_pad/emsa.h @@ -9,6 +9,8 @@ #define BOTAN_PUBKEY_EMSA_H_ #include <botan/secmem.h> +#include <botan/alg_id.h> +#include <botan/pk_keys.h> namespace Botan { @@ -56,12 +58,29 @@ class BOTAN_PUBLIC_API(2,0) EMSA const secure_vector<uint8_t>& raw, size_t key_bits) = 0; + /** + * Prepare sig_algo for use in choose_sig_format for x509 certs, + * return padding info string + * @param sig_algo's oid and parameters will be set properly + * @param key used for checking compatibility with the encoding scheme + * @param cert_hash_name is checked to equal the hash for the encoding + * @return padding string to be consumed by PK_signer + */ + virtual AlgorithmIdentifier config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const + { throw Not_Implemented("Encoding " + name() + " not supported for signing X509 objects"); } + virtual ~EMSA() = default; /** * @return a new object representing the same encoding method as *this */ virtual EMSA* clone() = 0; + + /** + * @return the SCAN name of the encoding/padding scheme + */ + virtual std::string name() const = 0; }; /** diff --git a/src/lib/pk_pad/emsa1/emsa1.cpp b/src/lib/pk_pad/emsa1/emsa1.cpp index db9f2432f..76f668f83 100644 --- a/src/lib/pk_pad/emsa1/emsa1.cpp +++ b/src/lib/pk_pad/emsa1/emsa1.cpp @@ -7,6 +7,8 @@ #include <botan/emsa1.h> #include <botan/exceptn.h> +#include <botan/oids.h> +#include <botan/internal/padding.h> namespace Botan { @@ -94,4 +96,38 @@ bool EMSA1::verify(const secure_vector<uint8_t>& input, } } +AlgorithmIdentifier EMSA1::config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const + { + if(cert_hash_name != m_hash->name()) + throw Invalid_Argument("Hash function from opts and hash_fn argument" + " need to be identical"); + // check that the signature algorithm and the padding scheme fit + if(!sig_algo_and_pad_ok(key.algo_name(), "EMSA1")) + { + throw Invalid_Argument("Encoding scheme with canonical name EMSA1" + " not supported for signature algorithm " + key.algo_name()); + } + + AlgorithmIdentifier sig_algo; + sig_algo.oid = OIDS::lookup( key.algo_name() + "/" + name() ); + + std::string algo_name = key.algo_name(); + if(algo_name == "DSA" || + algo_name == "ECDSA" || + algo_name == "ECGDSA" || + algo_name == "ECKCDSA" || + algo_name == "GOST-34.10") + { + // for DSA, ECDSA, GOST parameters "SHALL" be empty + sig_algo.parameters = {}; + } + else + { + sig_algo.parameters = key.algorithm_identifier().parameters; + } + + return sig_algo; + } + } diff --git a/src/lib/pk_pad/emsa1/emsa1.h b/src/lib/pk_pad/emsa1/emsa1.h index 702db8f80..35071675f 100644 --- a/src/lib/pk_pad/emsa1/emsa1.h +++ b/src/lib/pk_pad/emsa1/emsa1.h @@ -27,6 +27,11 @@ class BOTAN_PUBLIC_API(2,0) EMSA1 final : public EMSA EMSA* clone() override; + virtual std::string name() const override + { return "EMSA1(" + m_hash->name() + ")"; }; + + AlgorithmIdentifier config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const override; private: size_t hash_output_length() const { return m_hash->output_length(); } diff --git a/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp b/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp index 4175fe4b3..5e5024806 100644 --- a/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp +++ b/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp @@ -8,6 +8,8 @@ #include <botan/emsa_pkcs1.h> #include <botan/hash_id.h> #include <botan/exceptn.h> +#include <botan/oids.h> +#include <botan/internal/padding.h> namespace Botan { @@ -81,6 +83,28 @@ bool EMSA_PKCS1v15::verify(const secure_vector<uint8_t>& coded, } } +AlgorithmIdentifier EMSA_PKCS1v15::config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const + { + if(cert_hash_name != m_hash->name()) + throw Invalid_Argument("Hash function from opts and hash_fn argument" + " need to be identical"); + // check that the signature algorithm and the padding scheme fit + if(!sig_algo_and_pad_ok(key.algo_name(), "EMSA3")) + { + throw Invalid_Argument("Encoding scheme with canonical name EMSA3" + " not supported for signature algorithm " + key.algo_name()); + } + + + AlgorithmIdentifier sig_algo; + sig_algo.oid = OIDS::lookup( key.algo_name() + "/" + name() ); + // for RSA PKCSv1.5 parameters "SHALL" be NULL as configured by + // RSA_PublicKey::algorithm_identifier() + sig_algo.parameters = key.algorithm_identifier().parameters; + return sig_algo; + } + EMSA_PKCS1v15::EMSA_PKCS1v15(HashFunction* hash) : m_hash(hash) { m_hash_id = pkcs_hash_id(m_hash->name()); @@ -92,6 +116,7 @@ EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw(const std::string& hash_algo) { m_hash_id = pkcs_hash_id(hash_algo); std::unique_ptr<HashFunction> hash(HashFunction::create(hash_algo)); + m_hash_name = hash->name(); m_hash_output_len = hash->output_length(); } else diff --git a/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h b/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h index b58ccb385..31032320e 100644 --- a/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h +++ b/src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.h @@ -37,6 +37,12 @@ class BOTAN_PUBLIC_API(2,0) EMSA_PKCS1v15 final : public EMSA bool verify(const secure_vector<uint8_t>&, const secure_vector<uint8_t>&, size_t) override; + + std::string name() const override + { return "EMSA3(" + m_hash->name() + ")"; } + + AlgorithmIdentifier config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const override; private: std::unique_ptr<HashFunction> m_hash; std::vector<uint8_t> m_hash_id; @@ -67,8 +73,16 @@ class BOTAN_PUBLIC_API(2,0) EMSA_PKCS1v15_Raw final : public EMSA * included in the signature. */ EMSA_PKCS1v15_Raw(const std::string& hash_algo = ""); + + std::string name() const override + { + if(m_hash_name.empty()) return "EMSA3(Raw)"; + else return "EMSA3(Raw," + m_hash_name + ")"; + } + private: size_t m_hash_output_len = 0; + std::string m_hash_name; std::vector<uint8_t> m_hash_id; secure_vector<uint8_t> m_message; }; diff --git a/src/lib/pk_pad/emsa_pssr/pssr.cpp b/src/lib/pk_pad/emsa_pssr/pssr.cpp index 941da7d99..510d84a99 100644 --- a/src/lib/pk_pad/emsa_pssr/pssr.cpp +++ b/src/lib/pk_pad/emsa_pssr/pssr.cpp @@ -10,6 +10,9 @@ #include <botan/rng.h> #include <botan/mgf1.h> #include <botan/internal/bit_ops.h> +#include <botan/oids.h> +#include <botan/der_enc.h> +#include <botan/internal/padding.h> namespace Botan { @@ -160,6 +163,50 @@ bool PSSR::verify(const secure_vector<uint8_t>& coded, return pss_verify(*m_hash, coded, raw, key_bits); } +std::string PSSR::name() const + { + return "EMSA4(" + m_hash->name() + ",MGF1," + std::to_string(m_SALT_SIZE) + ")"; + } + +AlgorithmIdentifier PSSR::config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const + { + if(cert_hash_name != m_hash->name()) + throw Invalid_Argument("Hash function from opts and hash_fn argument" + " need to be identical"); + // check that the signature algorithm and the padding scheme fit + if(!sig_algo_and_pad_ok(key.algo_name(), "EMSA4")) + { + throw Invalid_Argument("Encoding scheme with canonical name EMSA4" + " not supported for signature algorithm " + key.algo_name()); + } + + AlgorithmIdentifier sig_algo; + // hardcoded as RSA is the only valid algorithm for EMSA4 at the moment + sig_algo.oid = OIDS::lookup( "RSA/EMSA4" ); + + sig_algo.parameters = DER_Encoder() + .start_cons( SEQUENCE ) + .start_cons( ASN1_Tag(0), CONTEXT_SPECIFIC ) + .encode( AlgorithmIdentifier( cert_hash_name, AlgorithmIdentifier::USE_NULL_PARAM ) ) + .end_cons() + .start_cons( ASN1_Tag(1), CONTEXT_SPECIFIC ) + .encode( AlgorithmIdentifier( "MGF1", DER_Encoder() + .encode( AlgorithmIdentifier( cert_hash_name, AlgorithmIdentifier::USE_NULL_PARAM ) ) + .get_contents_unlocked() ) ) + .end_cons() + .start_cons( ASN1_Tag(2), CONTEXT_SPECIFIC ) + .encode( size_t( m_SALT_SIZE ) ) + .end_cons() + .start_cons( ASN1_Tag(3), CONTEXT_SPECIFIC ) + .encode( size_t( 1 ) ) // trailer field + .end_cons() + .end_cons() + .get_contents_unlocked(); + + return sig_algo; + } + PSSR_Raw::PSSR_Raw(HashFunction* h) : m_hash(h), m_SALT_SIZE(m_hash->output_length()) { @@ -210,4 +257,9 @@ bool PSSR_Raw::verify(const secure_vector<uint8_t>& coded, return pss_verify(*m_hash, coded, raw, key_bits); } +std::string PSSR_Raw::name() const + { + return "PSSR_Raw(" + m_hash->name() + ",MGF1," + std::to_string(m_SALT_SIZE) + ")"; + } + } diff --git a/src/lib/pk_pad/emsa_pssr/pssr.h b/src/lib/pk_pad/emsa_pssr/pssr.h index 85dfefc2c..61a4b9448 100644 --- a/src/lib/pk_pad/emsa_pssr/pssr.h +++ b/src/lib/pk_pad/emsa_pssr/pssr.h @@ -32,6 +32,11 @@ class BOTAN_PUBLIC_API(2,0) PSSR final : public EMSA PSSR(HashFunction* hash, size_t salt_size); EMSA* clone() override { return new PSSR(m_hash->clone(), m_SALT_SIZE); } + + std::string name() const override; + + AlgorithmIdentifier config_for_x509(const Private_Key& key, + const std::string& cert_hash_name) const override; private: void update(const uint8_t input[], size_t length) override; @@ -69,6 +74,8 @@ class BOTAN_DLL PSSR_Raw final : public EMSA PSSR_Raw(HashFunction* hash, size_t salt_size); EMSA* clone() override { return new PSSR_Raw(m_hash->clone(), m_SALT_SIZE); } + + std::string name() const override; private: void update(const uint8_t input[], size_t length) override; diff --git a/src/lib/pk_pad/emsa_raw/emsa_raw.h b/src/lib/pk_pad/emsa_raw/emsa_raw.h index f85595cdf..5f2b994f5 100644 --- a/src/lib/pk_pad/emsa_raw/emsa_raw.h +++ b/src/lib/pk_pad/emsa_raw/emsa_raw.h @@ -24,6 +24,10 @@ class BOTAN_PUBLIC_API(2,0) EMSA_Raw final : public EMSA explicit EMSA_Raw(size_t expected_hash_size = 0) : m_expected_size(expected_hash_size) {} + virtual std::string name() const override + { if(m_expected_size > 0) + return "Raw(" + std::to_string(m_expected_size) + ")"; + else return "Raw"; } private: void update(const uint8_t[], size_t) override; secure_vector<uint8_t> raw_data() override; diff --git a/src/lib/pk_pad/emsa_x931/emsa_x931.h b/src/lib/pk_pad/emsa_x931/emsa_x931.h index 0f186deb2..6ce9e339a 100644 --- a/src/lib/pk_pad/emsa_x931/emsa_x931.h +++ b/src/lib/pk_pad/emsa_x931/emsa_x931.h @@ -27,6 +27,9 @@ class BOTAN_PUBLIC_API(2,0) EMSA_X931 final : public EMSA explicit EMSA_X931(HashFunction* hash); EMSA* clone() override { return new EMSA_X931(m_hash->clone()); } + + virtual std::string name() const override + { return "EMSA2(" + m_hash->name() + ")"; }; private: void update(const uint8_t[], size_t) override; secure_vector<uint8_t> raw_data() override; diff --git a/src/lib/pk_pad/info.txt b/src/lib/pk_pad/info.txt index 4c25e4371..513c4d0ad 100644 --- a/src/lib/pk_pad/info.txt +++ b/src/lib/pk_pad/info.txt @@ -8,6 +8,10 @@ load_on auto rng </requires> +<header:internal> +padding.h +</header:internal> + <header:public> eme.h emsa.h diff --git a/src/lib/pk_pad/iso9796/iso9796.cpp b/src/lib/pk_pad/iso9796/iso9796.cpp index b8375af68..5b74319e0 100644 --- a/src/lib/pk_pad/iso9796/iso9796.cpp +++ b/src/lib/pk_pad/iso9796/iso9796.cpp @@ -250,6 +250,15 @@ bool ISO_9796_DS2::verify(const secure_vector<uint8_t>& const_coded, } /* + * Return the SCAN name + */ +std::string ISO_9796_DS2::name() const + { + return "ISO_9796_DS2(" + m_hash->name() + "," + + (m_implicit ? "imp" : "exp") + "," + std::to_string(m_SALT_SIZE) + ")"; + } + +/* * ISO-9796-2 signature scheme 3 * DS 3 is deterministic and equals DS2 without salt */ @@ -286,4 +295,12 @@ bool ISO_9796_DS3::verify(const secure_vector<uint8_t>& const_coded, { return iso9796_verification(const_coded, raw, key_bits, m_hash, 0); } +/* + * Return the SCAN name + */ +std::string ISO_9796_DS3::name() const + { + return "ISO_9796_DS3(" + m_hash->name() + "," + + (m_implicit ? "imp" : "exp") + ")"; + } } diff --git a/src/lib/pk_pad/iso9796/iso9796.h b/src/lib/pk_pad/iso9796/iso9796.h index 692ca1300..994295f0f 100644 --- a/src/lib/pk_pad/iso9796/iso9796.h +++ b/src/lib/pk_pad/iso9796/iso9796.h @@ -36,6 +36,8 @@ class BOTAN_PUBLIC_API(2,0) ISO_9796_DS2 final : public EMSA EMSA* clone() override {return new ISO_9796_DS2(m_hash->clone(), m_implicit, m_SALT_SIZE);} + + virtual std::string name() const override; private: void update(const uint8_t input[], size_t length) override; @@ -70,6 +72,8 @@ class BOTAN_PUBLIC_API(2,0) ISO_9796_DS3 final : public EMSA EMSA* clone() override {return new ISO_9796_DS3(m_hash->clone(), m_implicit);} + + virtual std::string name() const override; private: void update(const uint8_t input[], size_t length) override; diff --git a/src/lib/pk_pad/padding.cpp b/src/lib/pk_pad/padding.cpp new file mode 100644 index 000000000..134bb4101 --- /dev/null +++ b/src/lib/pk_pad/padding.cpp @@ -0,0 +1,42 @@ +/* +* Sets of allowed padding schemes for public key types +* +* This file was automatically generated by ./src/scripts/oids.py on 2017-12-20 +* +* All manual edits to this file will be lost. Edit the script +* then regenerate this source file. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/internal/padding.h> +#include <map> +#include <vector> +#include <string> +#include <algorithm> + +namespace Botan { + +const std::map<const std::string, std::vector<std::string>> allowed_signature_paddings = + { + { "DSA", {"EMSA1"} }, + { "ECDSA", {"EMSA1"} }, + { "ECGDSA", {"EMSA1"} }, + { "ECKCDSA", {"EMSA1"} }, + { "GOST-34.10", {"EMSA1"} }, + { "RSA", {"EMSA4", "EMSA3"} }, + }; + +const std::vector<std::string> get_sig_paddings(const std::string algo) + { + if(allowed_signature_paddings.count(algo) > 0) + return allowed_signature_paddings.at(algo); + return {}; + } + +bool sig_algo_and_pad_ok(const std::string algo, const std::string padding) + { + std::vector<std::string> pads = get_sig_paddings(algo); + return std::find(pads.begin(), pads.end(), padding) != pads.end(); + } +} diff --git a/src/lib/pk_pad/padding.h b/src/lib/pk_pad/padding.h new file mode 100644 index 000000000..ed05ec381 --- /dev/null +++ b/src/lib/pk_pad/padding.h @@ -0,0 +1,36 @@ +/* +* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PADDING_H_ +#define BOTAN_PADDING_H_ + +#include <botan/build.h> +#include <string> +#include <vector> + +namespace Botan { + +/** +* Returns the allowed padding schemes when using the given +* algorithm (key type) for creating digital signatures. +* +* @param algo the algorithm for which to look up supported padding schemes +* @return a vector of supported padding schemes +*/ +BOTAN_TEST_API const std::vector<std::string> get_sig_paddings(const std::string algo); + +/** +* Returns true iff the given padding scheme is valid for the given +* signature algorithm (key type). +* +* @param algo the signature algorithm to be used +* @param padding the padding scheme to be used +*/ +bool sig_algo_and_pad_ok(const std::string algo, const std::string padding); + +} + +#endif diff --git a/src/lib/x509/x509_ca.cpp b/src/lib/x509/x509_ca.cpp index f8daaf79a..cba3b9816 100644 --- a/src/lib/x509/x509_ca.cpp +++ b/src/lib/x509/x509_ca.cpp @@ -6,6 +6,7 @@ */ #include <botan/x509_ca.h> +#include <botan/x509self.h> #include <botan/pkcs10.h> #include <botan/pubkey.h> #include <botan/der_enc.h> @@ -14,8 +15,11 @@ #include <botan/oids.h> #include <botan/hash.h> #include <botan/key_constraint.h> +#include <botan/emsa.h> +#include <botan/scan_name.h> #include <algorithm> #include <iterator> +#include <map> namespace Botan { @@ -32,7 +36,32 @@ X509_CA::X509_CA(const X509_Certificate& c, if(!m_ca_cert.is_CA_cert()) throw Invalid_Argument("X509_CA: This certificate is not for a CA"); - m_signer.reset(choose_sig_format(key, rng, m_hash_fn, m_ca_sig_algo)); + std::map<std::string,std::string> opts; + // constructor without additional options: use the padding used in the CA certificate + // sig_oid_str = <sig_alg>/<padding>, so padding with all its options will look + // like a cipher mode to the scanner + std::string sig_oid_str = OIDS::lookup(c.signature_algorithm().oid); + SCAN_Name scanner(sig_oid_str); + std::string pad = scanner.cipher_mode(); + if(!pad.empty()) + opts.insert({"padding",pad}); + + m_signer.reset(choose_sig_format(key, opts, rng, hash_fn, m_ca_sig_algo)); + } + +/* +* Load the certificate and private key, and additional options +*/ +X509_CA::X509_CA(const X509_Certificate& ca_certificate, + const Private_Key& key, + const std::map<std::string,std::string>& opts, + const std::string& hash_fn, + RandomNumberGenerator& rng) : m_ca_cert(ca_certificate), m_hash_fn(hash_fn) + { + if(!m_ca_cert.is_CA_cert()) + throw Invalid_Argument("X509_CA: This certificate is not for a CA"); + + m_signer.reset(choose_sig_format(key, opts, rng, hash_fn, m_ca_sig_algo)); } /* @@ -242,7 +271,20 @@ X509_Certificate X509_CA::ca_certificate() const /* * Choose a signing format for the key */ + +PK_Signer* choose_sig_format(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& hash_fn, + AlgorithmIdentifier& sig_algo) + { + return choose_sig_format(key, {}, rng, hash_fn, sig_algo); + } + +/* +* Choose a signing format for the key +*/ PK_Signer* choose_sig_format(const Private_Key& key, + const std::map<std::string,std::string>& opts, RandomNumberGenerator& rng, const std::string& hash_fn, AlgorithmIdentifier& sig_algo) @@ -250,14 +292,14 @@ PK_Signer* choose_sig_format(const Private_Key& key, const std::string algo_name = key.algo_name(); std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_fn)); + std::string hash_name = hash->name(); + // check algo_name and set default std::string padding; - std::vector<uint8_t> algo_params; if(algo_name == "RSA") { - padding = "EMSA3"; - // for RSA PKCSv1.5 parameters "SHALL" be NULL - algo_params = key.algorithm_identifier().get_parameters(); + // set to EMSA3 for compatibility reasons, originally it was the only option + padding = "EMSA3(" + hash_name + ")"; } else if(algo_name == "DSA" || algo_name == "ECDSA" || @@ -265,21 +307,45 @@ PK_Signer* choose_sig_format(const Private_Key& key, algo_name == "ECKCDSA" || algo_name == "GOST-34.10") { - // for DSA, ECDSA, GOST parameters "SHALL" be empty - padding = "EMSA1"; + padding = "EMSA1(" + hash_name + ")"; } else { throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); } - const Signature_Format format = (key.message_parts() > 1) ? DER_SEQUENCE : IEEE_1363; + if(opts.count("padding") > 0 && !opts.at("padding").empty()) + { + padding = opts.at("padding"); + } - padding = padding + "(" + hash->name() + ")"; + // try to construct an EMSA object from the padding options or default + std::unique_ptr<EMSA> emsa = nullptr; + try + { + emsa.reset(get_emsa(padding)); + } + /* + * get_emsa will throw if opts contains {"padding",<valid_padding>} but + * <valid_padding> does not specify a hash function. + * Omitting it is valid since it needs to be identical to hash_fn. + * If it still throws, something happened that we cannot repair here, + * e.g. the algorithm/padding combination is not supported. + */ + catch(...) + { + emsa.reset(get_emsa(padding + "(" + hash_fn + ")")); + } + if(emsa == nullptr) + { + throw Invalid_Argument("Could not parse padding scheme " + padding); + } + + const Signature_Format format = (key.message_parts() > 1) ? DER_SEQUENCE : IEEE_1363; - sig_algo = AlgorithmIdentifier(OIDS::lookup(algo_name + "/" + padding), algo_params); + sig_algo = emsa->config_for_x509(key, hash_name); - return new PK_Signer(key, rng, padding, format); + return new PK_Signer(key, rng, emsa->name(), format); } } diff --git a/src/lib/x509/x509_ca.h b/src/lib/x509/x509_ca.h index 49005f530..4f1da51fa 100644 --- a/src/lib/x509/x509_ca.h +++ b/src/lib/x509/x509_ca.h @@ -132,6 +132,20 @@ class BOTAN_PUBLIC_API(2,0) X509_CA final const std::string& hash_fn, RandomNumberGenerator& rng); + /** + * Create a new CA object. + * @param ca_certificate the certificate of the CA + * @param key the private key of the CA + * @param opts additional options, e.g. padding, as key value pairs + * @param hash_fn name of a hash function to use for signing + * @param rng the random generator to use + */ + X509_CA(const X509_Certificate& ca_certificate, + const Private_Key& key, + const std::map<std::string,std::string>& opts, + const std::string& hash_fn, + RandomNumberGenerator& rng); + #if defined(BOTAN_HAS_SYSTEM_RNG) BOTAN_DEPRECATED("Use version taking RNG object") X509_CA(const X509_Certificate& ca_certificate, @@ -178,6 +192,28 @@ BOTAN_PUBLIC_API(2,0) PK_Signer* choose_sig_format(const Private_Key& key, const std::string& hash_fn, AlgorithmIdentifier& alg_id); +/** +* Choose the default signature format for a certain public key signature +* scheme. +* +* The only option recognized by opts at this moment is "padding" +* Find an entry from src/build-data/oids.txt under [signature] of the form +* <sig_algo>/<padding>[(<hash_algo>)] and add {"padding",<padding>} +* to opts. +* +* @param key will be the key to choose a padding scheme for +* @param opts contains additional options for building the certificate +* @param rng the random generator to use +* @param hash_fn is the desired hash function +* @param alg_id will be set to the chosen scheme +* @return A PK_Signer object for generating signatures +*/ + PK_Signer* choose_sig_format(const Private_Key& key, + const std::map<std::string,std::string>& opts, + RandomNumberGenerator& rng, + const std::string& hash_fn, + AlgorithmIdentifier& alg_id); + } #endif diff --git a/src/lib/x509/x509opt.cpp b/src/lib/x509/x509opt.cpp index 79c735a0f..e31ead91f 100644 --- a/src/lib/x509/x509opt.cpp +++ b/src/lib/x509/x509opt.cpp @@ -61,6 +61,11 @@ void X509_Cert_Options::CA_key(size_t limit) path_limit = limit; } +void X509_Cert_Options::set_padding_scheme(const std::string& scheme) + { + padding_scheme = scheme; + } + /* * Initialize the certificate options */ @@ -70,6 +75,8 @@ X509_Cert_Options::X509_Cert_Options(const std::string& initial_opts, is_CA = false; path_limit = 0; constraints = NO_CONSTRAINTS; + // use default for chosen algorithm + padding_scheme = ""; auto now = std::chrono::system_clock::now(); diff --git a/src/lib/x509/x509self.cpp b/src/lib/x509/x509self.cpp index ad0e9af94..108e0496b 100644 --- a/src/lib/x509/x509self.cpp +++ b/src/lib/x509/x509self.cpp @@ -33,7 +33,6 @@ void load_info(const X509_Cert_Options& opts, X509_DN& subject_dn, subject_alt.add_othername(OIDS::lookup("PKIX.XMPPAddr"), opts.xmpp, UTF8_STRING); } - } namespace X509 { @@ -50,8 +49,11 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, X509_DN subject_dn; AlternativeName subject_alt; + // for now, only the padding option is used + std::map<std::string,std::string> sig_opts = { {"padding",opts.padding_scheme} }; + std::vector<uint8_t> pub_key = X509::BER_encode(key); - std::unique_ptr<PK_Signer> signer(choose_sig_format(key, rng, hash_fn, sig_algo)); + std::unique_ptr<PK_Signer> signer(choose_sig_format(key, sig_opts, rng, hash_fn, sig_algo)); load_info(opts, subject_dn, subject_alt); Key_Constraints constraints; @@ -102,8 +104,11 @@ PKCS10_Request create_cert_req(const X509_Cert_Options& opts, X509_DN subject_dn; AlternativeName subject_alt; + // for now, only the padding option is used + std::map<std::string,std::string> sig_opts = { {"padding",opts.padding_scheme} }; + std::vector<uint8_t> pub_key = X509::BER_encode(key); - std::unique_ptr<PK_Signer> signer(choose_sig_format(key, rng, hash_fn, sig_algo)); + std::unique_ptr<PK_Signer> signer(choose_sig_format(key, sig_opts, rng, hash_fn, sig_algo)); load_info(opts, subject_dn, subject_alt); const size_t PKCS10_VERSION = 0; diff --git a/src/lib/x509/x509self.h b/src/lib/x509/x509self.h index 069403814..0cc12e98e 100644 --- a/src/lib/x509/x509self.h +++ b/src/lib/x509/x509self.h @@ -108,6 +108,8 @@ class BOTAN_PUBLIC_API(2,0) X509_Cert_Options final */ size_t path_limit; + std::string padding_scheme; + /** * The key constraints for the subject public key */ @@ -130,6 +132,11 @@ class BOTAN_PUBLIC_API(2,0) X509_Cert_Options final void CA_key(size_t limit = 1); /** + * Choose a padding scheme different from the default for the key used. + */ + void set_padding_scheme(const std::string& scheme); + + /** * Set the notBefore of the certificate. * @param time the notBefore value of the certificate */ diff --git a/src/scripts/oids.py b/src/scripts/oids.py index 7a6caa368..a307d579b 100755 --- a/src/scripts/oids.py +++ b/src/scripts/oids.py @@ -10,6 +10,8 @@ Botan is released under the Simplified BSD License (see license.txt) import sys import datetime import re +from collections import defaultdict + def format_map(m, for_oid = False): s = '' @@ -190,25 +192,90 @@ size_t lookup_ub(const OID& oid) """ % (sys.argv[0], datetime.date.today().strftime("%Y-%m-%d"), format_dn_ub_map(dn_ub,oid2str)) + +def format_set_map(m): + s = '' + for k in sorted(m.keys()): + v = m[k] + + if len(s) > 0: + s += ' ' + + s += '{ "%s", {' % k + for pad in v: + s += '"%s", ' % pad + if len(v) is not 0: + s = s[:-2] + s += '} },\n' + s = s[:-1] + return s + + +def format_pads_as_map(sig_dict): + return """/* +* Sets of allowed padding schemes for public key types +* +* This file was automatically generated by %s on %s +* +* All manual edits to this file will be lost. Edit the script +* then regenerate this source file. +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/internal/padding.h> +#include <map> +#include <vector> +#include <string> +#include <algorithm> + +namespace Botan { + +const std::map<const std::string, std::vector<std::string>> allowed_signature_paddings = + { + %s + }; + +__attribute__((visibility("default"))) const std::vector<std::string> get_sig_paddings(const std::string algo) + { + if(allowed_signature_paddings.count(algo) > 0) + return allowed_signature_paddings.at(algo); + return {}; + } + +bool sig_algo_and_pad_ok(const std::string algo, std::string padding) + { + std::vector<std::string> pads = get_sig_paddings(algo); + return std::find(pads.begin(), pads.end(), padding) != pads.end(); + } +} +""" % (sys.argv[0], datetime.date.today().strftime("%Y-%m-%d"), + format_set_map(sig_dict)) + + def main(args = None): """ Print header files (oids.cpp, dn_ub.cpp) depending on the first argument and on srs/build-data/oids.txt Choose 'oids' to print oids.cpp, needs to be written to src/lib/asn1/oids.cpp Choose 'dn_ub' to print dn_ub.cpp, needs to be written to src/lib/x509/X509_dn_ub.cpp + Choose 'pads' to print padding.cpp, needs to be written to src/lib/pk_pad/padding.cpp """ if args is None: args = sys.argv if len(args) < 2: - raise Exception("Use either 'oids' or 'dn_ub' as first argument") + raise Exception("Use either 'oids', 'dn_ub', 'pads' as first argument") - oid_lines = open('src/build-data/oids.txt').readlines() + oid_lines = open('./src/build-data/oids.txt').readlines() oid_re = re.compile("^([1-9][0-9.]+) = ([A-Za-z0-9_\./\(\), -]+)(?: = )?([0-9]+)?$") hdr_re = re.compile("^\[([a-z0-9_]+)\]$") + pad_re = re.compile("^([A-Za-z0-9_\., -]+)/([A-Za-z0-9_-]+)[A-Za-z0-9_\.\(\), -]*$") oid2str = {} str2oid = {} dn_ub = {} + sig2pads = defaultdict(set) + enc2pads = defaultdict(set) cur_hdr = None for line in oid_lines: @@ -242,6 +309,11 @@ def main(args = None): if match.lastindex < 3: raise Exception("Could not find an upper bound for DN " + match.group(1)) dn_ub[oid] = match.group(3) + # parse signature paddings + elif cur_hdr == "signature": + pad_match = pad_re.search(nam) + if pad_match is not None: + sig2pads[pad_match.group(1)].add(pad_match.group(2)) if nam in str2oid: #print "Duplicated name", nam, oid, str2oid[nam] @@ -254,6 +326,8 @@ def main(args = None): print format_as_ifs(oid2str, str2oid) elif args[1] == "dn_ub": print format_dn_ub_as_map(dn_ub,oid2str) + elif args[1] == "pads": + print format_pads_as_map(sig2pads) if __name__ == '__main__': diff --git a/src/tests/data/x509/misc/rsa_key.pem b/src/tests/data/x509/misc/rsa_key.pem new file mode 100644 index 000000000..cf84f4274 --- /dev/null +++ b/src/tests/data/x509/misc/rsa_key.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/AIBADANBgkqhkiG9w0BAQEFAASCBuYwggbiAgEAAoIBgQDdQRpj4Rbm/cHf +ep44Aqwo88WiF74nzzu3A/Ebj2bs/ATjOMEAzlWa4m6WzI1QJgtkizGfRTjOJVQ0 +fAQnkHNa5tKJk4HOTGzDJC1p4/VIU615gocdOKAwjBflxDCcGAkI5iDKffskYl7n +/0c9GmfCQmG13qbFGJXbQDSPoXyYcngNxfCIL90dIL/9/B/PCu9OGDcqKYCTAVTj +FFLlliGNTT5tmjasrgdpGJF1cN+V94OoRznwiNCRgNzvpgAxYfoED7BVTUzjcq/E +2dRHPDYdAO/kN8Tk1q/U71A8aAK4x3pAuprj1ujfAqPbTLkDlT6IfxGwG05Avxeh +Kqyk3tP0NnAjh6pg/bYAXoM9l32O68T0WKRBQFoUFMhg88qJm8o1CEvudr8VVZ/8 +i3S6VekQ8BFTJUN3P0qO7TINFha88OnUqBFGMRJBvXEyFXtHk4IOt0BTQB+e9/ku +diMduz8ekpVzmjrAPF5TP4cJrT7v3013Z4ECrdhCBAPl65t+rqUCAwEAAQKCAYBL +9r89zZlUY4l91haeemRrhw6y0V4LQv5onqTYZorbEMgIz4KMaUtA6z827TITc4xz +z8qQuW6AcJaRkobGorTIX/mnHIrzro+lDDW/ZnHfjENCNct3/+oX4PGPhKV/4kyv +znsxqsFgQ5n8I0xtMTSwoKP1kmVFxGQnK8sgCTzDBoIrkGs+Btju2ECyzi6JomJ5 +OJ0wD6HCVzGy6VZw1vPcFMo7TTg6X2HR9opyfPd3AM0mKJY+/GpHmqvAaAkm61EN +Mc+bnLgTVVd8khOUrAgkWVjfiCTi+LV9kMQ3YXr9QxtgmmsteW0s8ZTD//JRNR8w +fB2y7mkQHR51YN7nfJ9JvpqrEJ6Qr3uUTtVjlI0GCr2oizL0VZdugMlkv8y16pZU +zsI+wNJsis92Sq7KCWc6l1iyKCfJDPig0+hbHkQnNI6NlZfYcbotZ4RhVtjijpDn +95dhA1wK2iGjW/L8lAlfiYTuM4IJ0vrVvzbZGT4hwgmRpamp0qHCqWUgySzFZD0C +gcEA5iuI/sqtf15DtAi5TWLs4ABKcrwu/ic/AD8hxqZautXzylxHeo9Scq0N9R+l +SQcwucss29gRRoXZwKn5i0MTjTTLwX3SWC3rlNdadIwGvk59x7qBgEXjzeWCJe+D +HXGPnLaoUwLsyflHaLHcF4V2XoeY5t0/qG5zQ0/BVtedFpheK/KqZo8cfWG4cbZ5 +0GZDxLyDBsGv4ayDju/aL/VQiGxLF3BHr7rGdAikcF99Smc4umw84APjAfqTU4qV +nfBLAoHBAPYVbk32UlfskdEEnrRzqpnEaZa+PX+Wzb7xfVBuW+dY25Q8cmGjfF1x +uY9teu4ia6D/Ke8lWaRnG71PICiTLPi0eg1JL3Qzgm1SJQmZoK0wL00xRbizbMu6 +hZ5cYR2Z/CnVS9k4CClfyConSmuh3dBhcoYZGH1J/WZvvz+4TMipvIupJ68LkXO+ +DtJ4Qr5ksN0JUjhtZ3FoCJm8+50+nHtJ/5d9yS5TarImAYHgfIpyNOTbMKnSkXfX +Mzk2PPLmzwKBwDojgQUq2Mw1WVCea3/6nu6t5CA7HHuiGi3LxJJS7tQGuv/Ac2Wn +0iGZSM9D4RIjONGVWo1ldGel46zgwmHE3alrTpRfXcRcRQdhpj2OKR3k4ayTlaZ7 +AOG/OTKv3ySOzMG++aGOOZWC2+C8HGXslkumYJ7f//Zhf8fe220+JTXR4uei8hvZ +xk59YoOGnhpf2npVS5tnTS/pzYlLWIeIpYDwKb+P4uumd/5TOIYR+KnUjOW59V54 +XNzhGFmfxc8RJQKBwBK16HAnFXW3+BJTbpm73bHZXEno5xYnajdldyjBa114xSFN +Q0knPBKCziAYq+slVNel7xNO3LUCXfqT5JcRMa8rUchm0yPbssQLJePH+Y6Rhlcx +MuLrSY9n/DbhQUUV6zVnEWBPwVccAEUsPZ1Xbl0ku6d0iwcjtA+w2XLH2Za8SSi5 +UNofYAzT256nJDQDxerYhZbiwqW9ykGeO+dl1lINe1CScNSD5S3sc9rjLbT9IAZy +oA2ZhBP/mdZ0yEeTwwKBwE5evNH1Ze56EJpO17fDs6shqECYsOCWWauFVHQinnrO ++aO8ZnwOfLVy2AEAkusjadsmjMBM3NdFNK9L2mvdfFKOwZ/Cwrm2vnwh2kSFTksD +I29WFj6jwBHNBE+dCYXraRjqIFrP9puKg2ECydp9g4ruxX4Kf6thpz1ML7GAnx/K +xdVBeDJisyxk1g3WijkGfGne9bozZiWLo4jW96+qn2/0//jB8B5ORDIn8kS/TfFN +ezTQp0HUwoiHK+bS2l+FQg== +-----END PRIVATE KEY----- diff --git a/src/tests/test_pk_pad.cpp b/src/tests/test_pk_pad.cpp index bd33dde6c..ecfdba5d5 100644 --- a/src/tests/test_pk_pad.cpp +++ b/src/tests/test_pk_pad.cpp @@ -69,6 +69,101 @@ class EME_Decoding_Tests final : public Text_Based_Test BOTAN_REGISTER_TEST("pk_pad_eme", EME_Decoding_Tests); +class EMSA_unit_tests final : public Test + { + public: + std::vector<Test::Result> run() override + { + Test::Result name_tests("EMSA_name_tests"); + + std::vector<std::string> pads_need_hash = + { +#if BOTAN_HAS_EMSA1 + "EMSA1", +#endif +#if BOTAN_HAS_EMSA_X931 + "EMSA2", +#endif +#if BOTAN_HAS_EMSA_PKCS1 + "EMSA3", +#endif +#if BOTAN_HAS_EMSA_PSSR + "EMSA4", + "PSSR_Raw", +#endif +#if BOTAN_HAS_ISO_9796 + "ISO_9796_DS2", + "ISO_9796_DS3", +#endif + }; + + std::vector<std::string> pads_no_hash = + { +#if BOTAN_HAS_EMSA_RAW + "Raw", +#endif +#if BOTAN_HAS_EMSA_PKCS1 + "EMSA3(Raw)", + "EMSA3(Raw,SHA-512)", +#endif + }; + + for(auto pad : pads_need_hash) + { + try + { + std::unique_ptr<Botan::EMSA> emsa_1( + Botan::get_emsa(pad + "(" + Botan::hash_for_emsa(pad) + ")")); + std::unique_ptr<Botan::EMSA> emsa_2(Botan::get_emsa(emsa_1->name())); + name_tests.test_eq("EMSA_name_test for " + pad, + emsa_1->name(), emsa_2->name()); + } + catch(const std::exception& e) + { + name_tests.test_failure("EMSA_name_test for " + pad + ": " + e.what()); + } + } + + for(auto pad : pads_need_hash) + { + std::string algo_name = pad + "(YYZ)"; + try + { + std::unique_ptr<Botan::EMSA> emsa( + Botan::get_emsa(algo_name)); + name_tests.test_failure("EMSA_name_test for " + pad + ": " + + "Could create EMSA with fantasy hash YYZ"); + } + catch(const std::exception& e) + { + name_tests.test_eq("EMSA_name_test for " + pad, + e.what(), + "Could not find any algorithm named \"" + algo_name + "\""); + } + } + + for(auto pad : pads_no_hash) + { + try + { + std::unique_ptr<Botan::EMSA> emsa_1(Botan::get_emsa(pad)); + std::unique_ptr<Botan::EMSA> emsa_2(Botan::get_emsa(emsa_1->name())); + name_tests.test_eq("EMSA_name_test for " + pad, + emsa_1->name(), emsa_2->name()); + } + catch(const std::exception& e) + { + name_tests.test_failure("EMSA_name_test for " + pad + ": " + e.what()); + } + } + + + return { name_tests }; + } + }; + +BOTAN_REGISTER_TEST("pk_pad_emsa_unit", EMSA_unit_tests); + #endif } diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index a8849292a..11f2932a8 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -19,6 +19,7 @@ #include <botan/ber_dec.h> #include <botan/der_enc.h> #include <botan/oids.h> + #include <botan/internal/padding.h> #endif @@ -35,26 +36,28 @@ Botan::X509_Time from_date(const int y, const int m, const int d) } /* Return some option sets */ -Botan::X509_Cert_Options ca_opts() +Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") { Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing"); opts.uri = "https://botan.randombit.net"; opts.dns = "botan.randombit.net"; opts.email = "[email protected]"; + opts.set_padding_scheme(sig_padding); opts.CA_key(1); return opts; } -Botan::X509_Cert_Options req_opts1(const std::string& algo) +Botan::X509_Cert_Options req_opts1(const std::string& algo, const std::string& sig_padding = "") { Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); opts.uri = "https://botan.randombit.net"; opts.dns = "botan.randombit.net"; opts.email = "[email protected]"; + opts.set_padding_scheme(sig_padding); opts.not_before("160101200000Z"); opts.not_after("300101200000Z"); @@ -73,13 +76,14 @@ Botan::X509_Cert_Options req_opts1(const std::string& algo) return opts; } -Botan::X509_Cert_Options req_opts2() +Botan::X509_Cert_Options req_opts2(const std::string& sig_padding = "") { Botan::X509_Cert_Options opts("Test User 2/US/Botan Project/Testing"); opts.uri = "https://botan.randombit.net"; opts.dns = "botan.randombit.net"; opts.email = "[email protected]"; + opts.set_padding_scheme(sig_padding); opts.add_ex_constraint("PKIX.EmailProtection"); @@ -461,7 +465,7 @@ Test::Result test_x509_bmpstring() return result; } -Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") +Test::Result test_x509_cert(const std::string& sig_algo, const std::string& sig_padding = "", const std::string& hash_fn = "SHA-256") { Test::Result result("X509 Unit"); @@ -476,7 +480,7 @@ Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash } /* Create the self-signed cert */ - const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, Test::rng()); + const auto ca_cert = Botan::X509::create_self_signed_cert(ca_opts(sig_padding), *ca_key, hash_fn, Test::rng()); { const auto constraints = Botan::Key_Constraints(Botan::KEY_CERT_SIGN | Botan::CRL_SIGN); @@ -487,7 +491,7 @@ Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash std::unique_ptr<Botan::Private_Key> user1_key(make_a_private_key(sig_algo)); Botan::PKCS10_Request user1_req = - Botan::X509::create_cert_req(req_opts1(sig_algo), + Botan::X509::create_cert_req(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, Test::rng()); @@ -499,13 +503,13 @@ Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash std::unique_ptr<Botan::Private_Key> user2_key(make_a_private_key(sig_algo)); Botan::PKCS10_Request user2_req = - Botan::X509::create_cert_req(req_opts2(), + Botan::X509::create_cert_req(req_opts2(sig_padding), *user2_key, hash_fn, Test::rng()); /* Create the CA object */ - Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, Test::rng()); + Botan::X509_CA ca(ca_cert, *ca_key, {{"padding",sig_padding}}, hash_fn, Test::rng()); /* Sign the requests to create the certs */ Botan::X509_Certificate user1_cert = @@ -519,7 +523,8 @@ Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash from_date(2033, 01, 01)); // user#1 creates a self-signed cert on the side - const auto user1_ss_cert = Botan::X509::create_self_signed_cert(req_opts1(sig_algo), *user1_key, hash_fn, Test::rng()); + const auto user1_ss_cert = + Botan::X509::create_self_signed_cert(req_opts1(sig_algo, sig_padding), *user1_key, hash_fn, Test::rng()); { auto constrains = req_opts1(sig_algo).constraints; @@ -718,7 +723,7 @@ Test::Result test_usage(const std::string& sig_algo, const std::string& hash_fn return result; } -Test::Result test_self_issued(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") +Test::Result test_self_issued(const std::string& sig_algo, const std::string& sig_padding = "", const std::string& hash_fn = "SHA-256") { using Botan::Key_Constraints; @@ -736,10 +741,10 @@ Test::Result test_self_issued(const std::string& sig_algo, const std::string& ha // create the self-signed cert const Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert( - ca_opts(), *ca_key, hash_fn, Test::rng()); + ca_opts(sig_padding), *ca_key, hash_fn, Test::rng()); /* Create the CA object */ - const Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, Test::rng()); + const Botan::X509_CA ca(ca_cert, *ca_key, {{"padding",sig_padding}}, hash_fn, Test::rng()); std::unique_ptr<Botan::Private_Key> user_key(make_a_private_key(sig_algo)); @@ -747,6 +752,7 @@ Test::Result test_self_issued(const std::string& sig_algo, const std::string& ha // but signed by a CA, not signed by it's own private key Botan::X509_Cert_Options opts = ca_opts(); opts.constraints = Key_Constraints::DIGITAL_SIGNATURE; + opts.set_padding_scheme(sig_padding); const Botan::PKCS10_Request self_issued_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, Test::rng()); @@ -1063,7 +1069,7 @@ class String_Extension final : public Botan::Certificate_Extension std::string m_contents; }; -Test::Result test_x509_extensions(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") +Test::Result test_x509_extensions(const std::string& sig_algo, const std::string& sig_padding = "", const std::string& hash_fn = "SHA-256") { using Botan::Key_Constraints; @@ -1080,10 +1086,11 @@ Test::Result test_x509_extensions(const std::string& sig_algo, const std::string } /* Create the self-signed cert */ - Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, Test::rng()); + Botan::X509_Certificate ca_cert = + Botan::X509::create_self_signed_cert(ca_opts(sig_padding), *ca_key, hash_fn, Test::rng()); /* Create the CA object */ - Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, Test::rng()); + Botan::X509_CA ca(ca_cert, *ca_key, {{"padding",sig_padding}}, hash_fn, Test::rng()); std::unique_ptr<Botan::Private_Key> user_key(make_a_private_key(sig_algo)); @@ -1096,6 +1103,7 @@ Test::Result test_x509_extensions(const std::string& sig_algo, const std::string const Botan::OID ku_oid = Botan::OIDS::lookup("X509v3.KeyUsage"); req_extensions.add(new String_Extension("AAAAAAAAAAAAAABCDEF"), false); opts.extensions = req_extensions; + opts.set_padding_scheme(sig_padding); /* Create a self-signed certificate */ const Botan::X509_Certificate self_signed_cert = Botan::X509::create_self_signed_cert( @@ -1220,6 +1228,99 @@ Test::Result test_hashes(const std::string& algo, const std::string& hash_fn = " return result; } +/* + * @brief checks the configurability of the EMSA4(RSA-PSS) signature scheme + * + * For the other algorithms than RSA, only one padding is supported right now. + */ +Test::Result test_padding_config() { + // Throughout the test, some synonyms for EMSA4 are used, e.g. PSSR, EMSA-PSS + Test::Result test_result("X509 Padding Config"); + + std::unique_ptr<Botan::Private_Key> sk(Botan::PKCS8::load_key( + Test::data_file("x509/misc/rsa_key.pem"), Test::rng())); + + // Create X509 CA certificate; EMSA3 is used for signing by default + Botan::X509_Cert_Options opt("TESTCA"); + opt.CA_key(); + Botan::X509_Certificate ca_cert_def = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng()); + test_result.test_eq("CA certificate signature algorithm (default)", + Botan::OIDS::lookup(ca_cert_def.signature_algorithm().oid),"RSA/EMSA3(SHA-512)"); + + // Create X509 CA certificate; RSA-PSS is explicitly set + opt.set_padding_scheme("PSSR"); + Botan::X509_Certificate ca_cert_exp = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng()); + test_result.test_eq("CA certificate signature algorithm (explicit)", + Botan::OIDS::lookup(ca_cert_exp.signature_algorithm().oid),"RSA/EMSA4"); + + // Try to set a padding scheme that is not supported for signing with the given key type + opt.set_padding_scheme("EMSA1"); + try + { + Botan::X509_Certificate ca_cert_wrong = Botan::X509::create_self_signed_cert(opt, (*sk), "SHA-512", Test::rng()); + test_result.test_failure("Could build CA certitiface with invalid encoding scheme EMSA1 for key type " + sk->algo_name()); + } + catch (const Botan::Invalid_Argument& e) + { + test_result.test_eq("Build CA certitiface with invalid encoding scheme EMSA1 for key type " + + sk->algo_name(), e.what(), + "Invalid argument Encoding scheme with canonical name EMSA1 not supported for signature algorithm RSA"); + } + test_result.test_eq("CA certificate signature algorithm (explicit)", + Botan::OIDS::lookup(ca_cert_exp.signature_algorithm().oid),"RSA/EMSA4"); + + // Prepare a signing request for the end certificate + Botan::X509_Cert_Options req_opt("endpoint"); + req_opt.set_padding_scheme("EMSA4(SHA-512,MGF1,64)"); + auto not_before = Botan::calendar_point(2017, 1, 1, 1, 1, + 1).to_std_timepoint(); + auto not_after = Botan::calendar_point(2018, 1, 1, 1, 1, + 1).to_std_timepoint(); + Botan::PKCS10_Request end_req = Botan::X509::create_cert_req(req_opt, (*sk), "SHA-512", Test::rng()); + test_result.test_eq("Certificate request signature algorithm", Botan::OIDS::lookup(end_req.signature_algorithm().oid),"RSA/EMSA4"); + + // Create X509 CA object: will fail as the chosen hash functions differ + try + { + Botan::X509_CA ca_fail(ca_cert_exp, (*sk), {{"padding","EMSA4(SHA-256)"}},"SHA-512", Test::rng()); + test_result.test_failure("Configured conflicting hash functions for CA"); + } + catch(const Botan::Invalid_Argument& e) + { + test_result.test_eq("Configured conflicting hash functions for CA", + e.what(), + "Invalid argument Hash function from opts and hash_fn argument need to be identical"); + } + + // Create X509 CA object: its signer will use the padding scheme from the CA certificate, i.e. EMSA3 + Botan::X509_CA ca_def(ca_cert_def, (*sk), "SHA-512", Test::rng()); + Botan::X509_Certificate end_cert_emsa3 = ca_def.sign_request(end_req, Test::rng(), Botan::X509_Time(not_before), Botan::X509_Time(not_after)); + test_result.test_eq("End certificate signature algorithm", Botan::OIDS::lookup(end_cert_emsa3.signature_algorithm().oid), "RSA/EMSA3(SHA-512)"); + + // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is different from the CA certificate's scheme + Botan::X509_CA ca_diff(ca_cert_def, (*sk), {{"padding","EMSA-PSS"}}, "SHA-512", Test::rng()); + Botan::X509_Certificate end_cert_diff_emsa4 = ca_diff.sign_request(end_req, Test::rng(), Botan::X509_Time(not_before), Botan::X509_Time(not_after)); + test_result.test_eq("End certificate signature algorithm", Botan::OIDS::lookup(end_cert_diff_emsa4.signature_algorithm().oid), "RSA/EMSA4"); + + // Create X509 CA object: its signer will use the explicitly configured padding scheme, which is identical to the CA certificate's scheme + Botan::X509_CA ca_exp(ca_cert_exp, (*sk), {{"padding","EMSA4(SHA-512,MGF1,64)"}},"SHA-512", Test::rng()); + Botan::X509_Certificate end_cert_emsa4= ca_exp.sign_request(end_req, Test::rng(), Botan::X509_Time(not_before), Botan::X509_Time(not_after)); + test_result.test_eq("End certificate signature algorithm", Botan::OIDS::lookup(end_cert_emsa4.signature_algorithm().oid), "RSA/EMSA4"); + + // Check CRL signature algorithm + Botan::X509_CRL crl = ca_exp.new_crl(Test::rng()); + test_result.test_eq("CRL signature algorithm", Botan::OIDS::lookup(crl.signature_algorithm().oid), "RSA/EMSA4"); + + // sanity check for verification, the heavy lifting is done in the other unit tests + const Botan::Certificate_Store_In_Memory trusted(ca_exp.ca_certificate()); + const Botan::Path_Validation_Restrictions restrictions(false, 80); + const Botan::Path_Validation_Result validation_result = Botan::x509_path_validate( + end_cert_emsa4, restrictions, trusted); + test_result.confirm("EMSA4-signed certificate validates", validation_result.successful_validation()); + + return test_result; +} + class X509_Cert_Unit_Tests final : public Test { public: @@ -1239,16 +1340,17 @@ class X509_Cert_Unit_Tests final : public Test if(algo == "RSA") continue; #endif - - try - { - cert_result.merge(test_x509_cert(algo)); - } - catch(std::exception& e) + for(auto padding_scheme : Botan::get_sig_paddings(algo)) { - cert_result.test_failure("test_x509_cert " + algo, e.what()); + try + { + cert_result.merge(test_x509_cert(algo, padding_scheme)); + } + catch(std::exception& e) + { + cert_result.test_failure("test_x509_cert " + algo, e.what()); + } } - try { usage_result.merge(test_usage(algo)); @@ -1257,23 +1359,27 @@ class X509_Cert_Unit_Tests final : public Test { usage_result.test_failure("test_usage " + algo, e.what()); } - - try - { - self_issued_result.merge(test_self_issued(algo)); - } - catch(std::exception& e) - { - self_issued_result.test_failure("test_self_issued " + algo, e.what()); - } - - try + for(auto padding_scheme : Botan::get_sig_paddings(algo)) { - extensions_result.merge(test_x509_extensions(algo)); + try + { + self_issued_result.merge(test_self_issued(algo, padding_scheme)); + } + catch(std::exception& e) + { + self_issued_result.test_failure("test_self_issued " + algo, e.what()); + } } - catch(std::exception& e) + for(auto padding_scheme : Botan::get_sig_paddings(algo)) { - extensions_result.test_failure("test_extensions " + algo, e.what()); + try + { + extensions_result.merge(test_x509_extensions(algo, padding_scheme)); + } + catch(std::exception& e) + { + extensions_result.test_failure("test_extensions " + algo, e.what()); + } } } @@ -1282,6 +1388,17 @@ class X509_Cert_Unit_Tests final : public Test results.push_back(self_issued_result); results.push_back(extensions_result); + Test::Result pad_config_result("X509 Padding Config"); + try + { + pad_config_result.merge(test_padding_config()); + } + catch(const std::exception& e) + { + pad_config_result.test_failure("test_padding_config", e.what()); + } + results.push_back(pad_config_result); + const std::vector<std::string> pk_algos { "DH", "ECDH", "RSA", "ElGamal", "GOST-34.10", |