diff options
author | Jack Lloyd <[email protected]> | 2016-08-19 16:03:40 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-08-19 16:03:40 -0400 |
commit | 165a21393f0061a6f3c68b9244a20b41c16c2a78 (patch) | |
tree | ff78cfd1907d0a208c0bb42b73622b0310172633 | |
parent | f26dfb3572aaab003e0c80002615d190488fb613 (diff) | |
parent | 6cbff45093199d821dee7ee74380474300f49948 (diff) |
Merge GH #591
Change behavior of default key usage encoding, default now omits
the key usage unless the user set a value.
Fix allowed_usage which could produce incorrect results.
More X.509 tests
-rw-r--r-- | src/lib/cert/x509/key_constraint.cpp | 63 | ||||
-rw-r--r-- | src/lib/cert/x509/key_constraint.h | 28 | ||||
-rw-r--r-- | src/lib/cert/x509/x509_ca.cpp | 18 | ||||
-rw-r--r-- | src/lib/cert/x509/x509_ext.cpp | 10 | ||||
-rw-r--r-- | src/lib/cert/x509/x509cert.cpp | 9 | ||||
-rw-r--r-- | src/lib/cert/x509/x509self.cpp | 35 | ||||
-rw-r--r-- | src/lib/math/bigint/big_code.cpp | 11 | ||||
-rw-r--r-- | src/lib/math/bigint/bigint.h | 9 | ||||
-rw-r--r-- | src/lib/pubkey/dsa/dsa.cpp | 5 | ||||
-rw-r--r-- | src/lib/pubkey/ecdsa/ecdsa.cpp | 5 | ||||
-rw-r--r-- | src/lib/pubkey/ecgdsa/ecgdsa.cpp | 5 | ||||
-rw-r--r-- | src/lib/pubkey/eckcdsa/eckcdsa.cpp | 7 | ||||
-rw-r--r-- | src/tests/test_bigint.cpp | 27 | ||||
-rw-r--r-- | src/tests/unit_x509.cpp | 606 |
14 files changed, 606 insertions, 232 deletions
diff --git a/src/lib/cert/x509/key_constraint.cpp b/src/lib/cert/x509/key_constraint.cpp index 24791b34a..a90af013c 100644 --- a/src/lib/cert/x509/key_constraint.cpp +++ b/src/lib/cert/x509/key_constraint.cpp @@ -1,69 +1,46 @@ /* * KeyUsage * (C) 1999-2007 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ #include <botan/key_constraint.h> #include <botan/x509_key.h> -#include <botan/ber_dec.h> namespace Botan { -namespace BER { - -/* -* Decode a BER encoded KeyUsage -*/ -void decode(BER_Decoder& source, Key_Constraints& key_usage) - { - BER_Object obj = source.get_next_object(); - - if(obj.type_tag != BIT_STRING || obj.class_tag != UNIVERSAL) - throw BER_Bad_Tag("Bad tag for usage constraint", - obj.type_tag, obj.class_tag); - if(obj.value.size() != 2 && obj.value.size() != 3) - throw BER_Decoding_Error("Bad size for BITSTRING in usage constraint"); - if(obj.value[0] >= 8) - throw BER_Decoding_Error("Invalid unused bits in usage constraint"); - - const byte mask = (0xFF << obj.value[0]); - obj.value[obj.value.size()-1] &= mask; - - u16bit usage = 0; - for(size_t j = 1; j != obj.value.size(); ++j) - usage = (obj.value[j] << 8) | usage; - - key_usage = Key_Constraints(usage); - } - -} - /* -* Find the allowable key constraints +* Make sure the given key constraints are permitted for the given key type */ -Key_Constraints find_constraints(const Public_Key& pub_key, - Key_Constraints limits) +void verify_cert_constraints_valid_for_key_type(const Public_Key& pub_key, + Key_Constraints constraints) { const std::string name = pub_key.algo_name(); - size_t constraints = 0; + size_t permitted = 0; if(name == "DH" || name == "ECDH") - constraints |= KEY_AGREEMENT; + { + permitted |= KEY_AGREEMENT | ENCIPHER_ONLY | DECIPHER_ONLY; + } if(name == "RSA" || name == "ElGamal") - constraints |= KEY_ENCIPHERMENT | DATA_ENCIPHERMENT; + { + permitted |= KEY_ENCIPHERMENT | DATA_ENCIPHERMENT; + } if(name == "RSA" || name == "RW" || name == "NR" || - name == "DSA" || name == "ECDSA") - constraints |= DIGITAL_SIGNATURE | NON_REPUDIATION; - - if(limits) - constraints &= limits; - - return Key_Constraints(constraints); + name == "DSA" || name == "ECDSA" || name == "ECGDSA" || name == "ECKCDSA") + { + permitted |= DIGITAL_SIGNATURE | NON_REPUDIATION | KEY_CERT_SIGN | CRL_SIGN; + } + + if ( ( constraints & permitted ) != constraints ) + { + throw Exception("Constraint not permitted for key type " + name); + } } } diff --git a/src/lib/cert/x509/key_constraint.h b/src/lib/cert/x509/key_constraint.h index 179e413b5..b67eb7010 100644 --- a/src/lib/cert/x509/key_constraint.h +++ b/src/lib/cert/x509/key_constraint.h @@ -1,6 +1,7 @@ /* * Enumerations * (C) 1999-2007 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,7 +9,7 @@ #ifndef BOTAN_ENUMS_H__ #define BOTAN_ENUMS_H__ -#include <botan/ber_dec.h> +#include <botan/build.h> namespace Botan { @@ -32,26 +33,13 @@ enum Key_Constraints { class Public_Key; /** -* Create the key constraints for a specific public key. -* @param pub_key the public key from which the basic set of -* constraints to be placed in the return value is derived -* @param limits additional limits that will be incorporated into the -* return value -* @return combination of key type specific constraints and -* additional limits +* Check that key constraints are permitted for a specific public key. +* @param pub_key the public key on which the constraints shall be enforced on +* @param constrains the constraints that shall be enforced on the key +* @throw Exception if the given constraints are not permitted for this key */ - -BOTAN_DLL Key_Constraints find_constraints(const Public_Key& pub_key, - Key_Constraints limits); - -/** -* BER Decoding Function for key constraints -*/ -namespace BER { - -void BOTAN_DLL decode(BER_Decoder&, Key_Constraints&); - -} +BOTAN_DLL void verify_cert_constraints_valid_for_key_type(const Public_Key& pub_key, + Key_Constraints constraints); } diff --git a/src/lib/cert/x509/x509_ca.cpp b/src/lib/cert/x509/x509_ca.cpp index 147fdd6ad..58c6676f4 100644 --- a/src/lib/cert/x509/x509_ca.cpp +++ b/src/lib/cert/x509/x509_ca.cpp @@ -52,11 +52,14 @@ X509_Certificate X509_CA::sign_request(const PKCS10_Request& req, { Key_Constraints constraints; if(req.is_CA()) + { constraints = Key_Constraints(KEY_CERT_SIGN | CRL_SIGN); + } else { std::unique_ptr<Public_Key> key(req.subject_public_key()); - constraints = find_constraints(*key, req.constraints()); + verify_cert_constraints_valid_for_key_type(*key, req.constraints()); + constraints = req.constraints(); } Extensions extensions; @@ -65,7 +68,10 @@ X509_Certificate X509_CA::sign_request(const PKCS10_Request& req, new Cert_Extension::Basic_Constraints(req.is_CA(), req.path_limit()), true); - extensions.add(new Cert_Extension::Key_Usage(constraints), true); + if(constraints != NO_CONSTRAINTS) + { + extensions.add(new Cert_Extension::Key_Usage(constraints), true); + } extensions.add(new Cert_Extension::Authority_Key_ID(m_cert.subject_key_id())); extensions.add(new Cert_Extension::Subject_Key_ID(req.raw_public_key())); @@ -233,11 +239,17 @@ PK_Signer* choose_sig_format(const Private_Key& key, std::string padding; if(algo_name == "RSA") + { padding = "EMSA3"; - else if(algo_name == "DSA" || algo_name == "ECDSA" ) + } + else if(algo_name == "DSA" || algo_name == "ECDSA" || algo_name == "ECGDSA" || algo_name == "ECKCDSA") + { padding = "EMSA1"; + } else + { throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); + } const Signature_Format format = (key.message_parts() > 1) ? DER_SEQUENCE : IEEE_1363; diff --git a/src/lib/cert/x509/x509_ext.cpp b/src/lib/cert/x509/x509_ext.cpp index 986254bc9..650c20d53 100644 --- a/src/lib/cert/x509/x509_ext.cpp +++ b/src/lib/cert/x509/x509_ext.cpp @@ -1,6 +1,7 @@ /* * X.509 Certificate Extensions * (C) 1999-2010,2012 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -293,7 +294,9 @@ void Key_Usage::decode_inner(const std::vector<byte>& in) u16bit usage = 0; for(size_t i = 1; i != obj.value.size(); ++i) - usage = (obj.value[i] << 8) | usage; + { + usage = (obj.value[i] << 8*(sizeof(usage)-i)) | usage; + } m_constraints = Key_Constraints(usage); } @@ -818,13 +821,12 @@ std::vector<byte> Unknown_Critical_Extension::encode_inner() const throw Not_Implemented("Unknown_Critical_Extension encoding"); } -void Unknown_Critical_Extension::decode_inner(const std::vector<byte>& buf) +void Unknown_Critical_Extension::decode_inner(const std::vector<byte>&) { } -void Unknown_Critical_Extension::contents_to(Data_Store& info, Data_Store&) const +void Unknown_Critical_Extension::contents_to(Data_Store&, Data_Store&) const { - // TODO: textual representation? } } diff --git a/src/lib/cert/x509/x509cert.cpp b/src/lib/cert/x509/x509cert.cpp index bd3aff6d5..20a4bca25 100644 --- a/src/lib/cert/x509/x509cert.cpp +++ b/src/lib/cert/x509/x509cert.cpp @@ -1,6 +1,7 @@ /* * X.509 Certificates * (C) 1999-2010,2015 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -257,7 +258,7 @@ bool X509_Certificate::allowed_usage(Key_Constraints usage) const { if(constraints() == NO_CONSTRAINTS) return true; - return ((constraints() & usage) != 0); + return ((constraints() & usage) == usage); } bool X509_Certificate::allowed_extended_usage(const std::string& usage) const @@ -565,7 +566,7 @@ std::string X509_Certificate::to_string() const if(constraints & DIGITAL_SIGNATURE) out << " Digital Signature\n"; if(constraints & NON_REPUDIATION) - out << " Non-Repuidation\n"; + out << " Non-Repudiation\n"; if(constraints & KEY_ENCIPHERMENT) out << " Key Encipherment\n"; if(constraints & DATA_ENCIPHERMENT) @@ -576,6 +577,10 @@ std::string X509_Certificate::to_string() const out << " Cert Sign\n"; if(constraints & CRL_SIGN) out << " CRL Sign\n"; + if(constraints & ENCIPHER_ONLY) + out << " Encipher Only\n"; + if(constraints & DECIPHER_ONLY) + out << " Decipher Only\n"; } std::vector<std::string> policies = this->policies(); diff --git a/src/lib/cert/x509/x509self.cpp b/src/lib/cert/x509/x509self.cpp index 8b9aeda09..102e24f77 100644 --- a/src/lib/cert/x509/x509self.cpp +++ b/src/lib/cert/x509/x509self.cpp @@ -55,9 +55,14 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, Key_Constraints constraints; if(opts.is_CA) + { constraints = Key_Constraints(KEY_CERT_SIGN | CRL_SIGN); + } else - constraints = find_constraints(key, opts.constraints); + { + verify_cert_constraints_valid_for_key_type(key, opts.constraints); + constraints = opts.constraints; + } Extensions extensions; @@ -65,7 +70,10 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit), true); - extensions.add(new Cert_Extension::Key_Usage(constraints), true); + if(constraints != NO_CONSTRAINTS) + { + extensions.add(new Cert_Extension::Key_Usage(constraints), true); + } extensions.add(new Cert_Extension::Subject_Key_ID(pub_key)); @@ -99,16 +107,27 @@ PKCS10_Request create_cert_req(const X509_Cert_Options& opts, const size_t PKCS10_VERSION = 0; + Key_Constraints constraints; + if(opts.is_CA) + { + constraints = Key_Constraints(KEY_CERT_SIGN | CRL_SIGN); + } + else + { + verify_cert_constraints_valid_for_key_type(key, opts.constraints); + constraints = opts.constraints; + } + Extensions extensions; extensions.add( new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); - extensions.add( - new Cert_Extension::Key_Usage( - opts.is_CA ? Key_Constraints(KEY_CERT_SIGN | CRL_SIGN) : - find_constraints(key, opts.constraints) - ) - ); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add( + new Cert_Extension::Key_Usage(constraints)); + } extensions.add( new Cert_Extension::Extended_Key_Usage(opts.ex_constraints)); extensions.add( diff --git a/src/lib/math/bigint/big_code.cpp b/src/lib/math/bigint/big_code.cpp index 299fdc246..c8687715d 100644 --- a/src/lib/math/bigint/big_code.cpp +++ b/src/lib/math/bigint/big_code.cpp @@ -98,6 +98,17 @@ void BigInt::encode_1363(byte output[], size_t bytes, const BigInt& n) } /* +* Encode two BigInt, with leading 0s if needed, and concatenate +*/ +secure_vector<byte> BigInt::encode_fixed_length_int_pair(const BigInt& n1, const BigInt& n2, size_t bytes) + { + secure_vector<byte> output(2 * bytes); + BigInt::encode_1363(output.data(), bytes, n1); + BigInt::encode_1363(output.data() + bytes, bytes, n2); + return output; + } + +/* * Decode a BigInt */ BigInt BigInt::decode(const byte buf[], size_t length, Base base) diff --git a/src/lib/math/bigint/bigint.h b/src/lib/math/bigint/bigint.h index 2963ba35d..a61bee39c 100644 --- a/src/lib/math/bigint/bigint.h +++ b/src/lib/math/bigint/bigint.h @@ -566,6 +566,15 @@ class BOTAN_DLL BigInt static void encode_1363(byte out[], size_t bytes, const BigInt& n); + /** + * Encode two BigInt to a byte array according to IEEE 1363 + * @param n1 the first BigInt to encode + * @param n2 the second BigInt to encode + * @param bytes the length of the encoding of each single BigInt + * @result a secure_vector<byte> containing the concatenation of the two encoded BigInt + */ + static secure_vector<byte> encode_fixed_length_int_pair(const BigInt& n1, const BigInt& n2, size_t bytes); + private: secure_vector<word> m_reg; Sign m_signedness = Positive; diff --git a/src/lib/pubkey/dsa/dsa.cpp b/src/lib/pubkey/dsa/dsa.cpp index 6effb81dd..399756b1a 100644 --- a/src/lib/pubkey/dsa/dsa.cpp +++ b/src/lib/pubkey/dsa/dsa.cpp @@ -133,10 +133,7 @@ DSA_Signature_Operation::raw_sign(const byte msg[], size_t msg_len, BOTAN_ASSERT(s != 0, "invalid s"); BOTAN_ASSERT(r != 0, "invalid r"); - secure_vector<byte> output(2*m_q.bytes()); - r.binary_encode(&output[output.size() / 2 - r.bytes()]); - s.binary_encode(&output[output.size() - s.bytes()]); - return output; + return BigInt::encode_fixed_length_int_pair(r, s, m_q.bytes()); } /** diff --git a/src/lib/pubkey/ecdsa/ecdsa.cpp b/src/lib/pubkey/ecdsa/ecdsa.cpp index 6fe7ce319..264a36963 100644 --- a/src/lib/pubkey/ecdsa/ecdsa.cpp +++ b/src/lib/pubkey/ecdsa/ecdsa.cpp @@ -86,10 +86,7 @@ ECDSA_Signature_Operation::raw_sign(const byte msg[], size_t msg_len, BOTAN_ASSERT(s != 0, "invalid s"); BOTAN_ASSERT(r != 0, "invalid r"); - secure_vector<byte> output(2*m_order.bytes()); - r.binary_encode(&output[output.size() / 2 - r.bytes()]); - s.binary_encode(&output[output.size() - s.bytes()]); - return output; + return BigInt::encode_fixed_length_int_pair(r, s, m_order.bytes()); } /** diff --git a/src/lib/pubkey/ecgdsa/ecgdsa.cpp b/src/lib/pubkey/ecgdsa/ecgdsa.cpp index 3e14aa8f4..30ea32817 100644 --- a/src/lib/pubkey/ecgdsa/ecgdsa.cpp +++ b/src/lib/pubkey/ecgdsa/ecgdsa.cpp @@ -73,10 +73,7 @@ ECGDSA_Signature_Operation::raw_sign(const byte msg[], size_t msg_len, BOTAN_ASSERT(s != 0, "invalid s"); BOTAN_ASSERT(r != 0, "invalid r"); - secure_vector<byte> output(2*m_order.bytes()); - r.binary_encode(&output[output.size() / 2 - r.bytes()]); - s.binary_encode(&output[output.size() - s.bytes()]); - return output; + return BigInt::encode_fixed_length_int_pair(r, s, m_order.bytes()); } /** diff --git a/src/lib/pubkey/eckcdsa/eckcdsa.cpp b/src/lib/pubkey/eckcdsa/eckcdsa.cpp index 83439332e..5ca89675c 100644 --- a/src/lib/pubkey/eckcdsa/eckcdsa.cpp +++ b/src/lib/pubkey/eckcdsa/eckcdsa.cpp @@ -100,10 +100,9 @@ ECKCDSA_Signature_Operation::raw_sign(const byte msg[], size_t, const BigInt s = m_mod_order.multiply(m_x, k - w); BOTAN_ASSERT(s != 0, "invalid s"); - secure_vector<byte> signature(r.bytes() + s.bytes()); - r.binary_encode(signature.data()); - s.binary_encode(&signature[r.bytes()]); - return signature; + secure_vector<byte> output = BigInt::encode_1363(r, c.size()); + output += BigInt::encode_1363(s, m_mod_order.get_modulus().bytes()); + return output; } /** diff --git a/src/tests/test_bigint.cpp b/src/tests/test_bigint.cpp index 6f3d603db..cee7b5b8b 100644 --- a/src/tests/test_bigint.cpp +++ b/src/tests/test_bigint.cpp @@ -30,6 +30,7 @@ class BigInt_Unit_Tests : public Test results.push_back(test_bigint_sizes()); results.push_back(test_random_integer()); + results.push_back(test_encode()); return results; } @@ -143,6 +144,32 @@ class BigInt_Unit_Tests : public Test return result; } + + Test::Result test_encode() + { + Test::Result result("BigInt encoding functions"); + + const BigInt n1(0xffff); + const BigInt n2(1023); + + Botan::secure_vector<byte> encoded_n1 = BigInt::encode_1363(n1, 256); + Botan::secure_vector<byte> encoded_n2 = BigInt::encode_1363(n2, 256); + Botan::secure_vector<byte> expected = encoded_n1; + expected += encoded_n2; + + Botan::secure_vector<byte> encoded_n1_n2 = BigInt::encode_fixed_length_int_pair(n1, n2, 256); + result.test_eq("encode_fixed_length_int_pair", encoded_n1_n2, expected); + + for (size_t i = 0; i < 256 - n1.bytes(); ++i) + { + if ( encoded_n1[i] != 0 ) + { + result.test_failure("encode_1363", "no zero byte"); + } + } + + return result; + } }; BOTAN_REGISTER_TEST("bigint_unit", BigInt_Unit_Tests); diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index 93e26eee2..4d3c63a1b 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -1,5 +1,6 @@ /* * (C) 2009 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -28,6 +29,14 @@ #include <botan/ecdsa.h> #endif +#if defined(BOTAN_HAS_ECGDSA) + #include <botan/ecgdsa.h> +#endif + +#if defined(BOTAN_HAS_ECKCDSA) + #include <botan/eckcdsa.h> +#endif + #endif namespace Botan_Tests { @@ -56,7 +65,7 @@ Botan::X509_Cert_Options ca_opts() return opts; } -Botan::X509_Cert_Options req_opts1() +Botan::X509_Cert_Options req_opts1(const std::string& algo) { Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); @@ -64,6 +73,18 @@ Botan::X509_Cert_Options req_opts1() opts.dns = "botan.randombit.net"; opts.email = "[email protected]"; + opts.not_before("1601012000Z"); + opts.not_after("3001012000Z"); + + if(algo == "RSA") + { + opts.constraints = Botan::Key_Constraints(Botan::KEY_ENCIPHERMENT); + } + else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") + { + opts.constraints = Botan::Key_Constraints(Botan::DIGITAL_SIGNATURE); + } + return opts; } @@ -80,168 +101,186 @@ Botan::X509_Cert_Options req_opts2() return opts; } -std::unique_ptr<Botan::Private_Key> make_a_private_key() +std::unique_ptr<Botan::Private_Key> make_a_private_key(const std::string& algo) { +#if defined(BOTAN_HAS_RSA) + if(algo == "RSA") + { + return std::unique_ptr<Botan::Private_Key>(new Botan::RSA_PrivateKey(Test::rng(), 1024)); + } +#endif +#if defined(BOTAN_HAS_DSA) + if(algo == "DSA") + { + Botan::DL_Group grp("dsa/botan/2048"); + return std::unique_ptr<Botan::Private_Key>(new Botan::DSA_PrivateKey(Test::rng(), grp)); + } +#endif #if defined(BOTAN_HAS_ECDSA) - Botan::EC_Group grp("secp256r1"); - return std::unique_ptr<Botan::Private_Key>(new Botan::ECDSA_PrivateKey(Test::rng(), grp)); -#elif defined(BOTAN_HAS_RSA) - return std::unique_ptr<Botan::Private_Key>(new Botan::RSA_PrivateKey(Test::rng(), 1024)); -#elif defined(BOTAN_HAS_DSA) - Botan::DL_Group grp("dsa/botan/2048"); - return std::unique_ptr<Botan::Private_Key>(new Botan::DSA_PrivateKey(Test::rng(), grp)); -#else - return std::unique_ptr<Botan::Private_Key>(nullptr); + if(algo == "ECDSA") + { + Botan::EC_Group grp("secp256r1"); + return std::unique_ptr<Botan::Private_Key>(new Botan::ECDSA_PrivateKey(Test::rng(), grp)); + } +#endif +#if defined(BOTAN_HAS_ECGDSA) + if(algo == "ECGDSA") + { + Botan::EC_Group grp("brainpool256r1"); + return std::unique_ptr<Botan::Private_Key>(new Botan::ECGDSA_PrivateKey(Test::rng(), grp)); + } #endif +#if defined(BOTAN_HAS_ECKCDSA) + if(algo == "ECKCDSA") + { + Botan::EC_Group grp("brainpool256r1"); + return std::unique_ptr<Botan::Private_Key>(new Botan::ECKCDSA_PrivateKey(Test::rng(), grp)); + } +#endif + return std::unique_ptr<Botan::Private_Key>(nullptr); } -class X509_Cert_Unit_Tests : public Test + +Test::Result test_x509_dates() { - public: - std::vector<Test::Result> run() override; + Test::Result result("X509_Time"); + + Botan::X509_Time time; + result.confirm("unset time not set", !time.time_is_set()); + time = Botan::X509_Time("0802011822Z", Botan::ASN1_Tag::UTC_TIME); + result.confirm("time set after construction", time.time_is_set()); + result.test_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC"); + + const std::vector<std::string> valid = { + "0802010000Z", + "0802011724Z", + "0406142334Z", + "9906142334Z", + "0006142334Z", + + "080201000000Z", + "080201172412Z", + "040614233433Z", + "990614233444Z", + "000614233455Z", + }; - private: - Test::Result test_x509_dates() - { - Test::Result result("X509_Time"); - - Botan::X509_Time time; - result.confirm("unset time not set", !time.time_is_set()); - time = Botan::X509_Time("0802011822Z", Botan::ASN1_Tag::UTC_TIME); - result.confirm("time set after construction", time.time_is_set()); - result.test_eq("time readable_string", time.readable_string(), "2008/02/01 18:22:00 UTC"); - - const std::vector<std::string> valid = { - "0802010000Z", - "0802011724Z", - "0406142334Z", - "9906142334Z", - "0006142334Z", - - "080201000000Z", - "080201172412Z", - "040614233433Z", - "990614233444Z", - "000614233455Z", - }; - - // Dates that are valid per X.500 but rejected as unsupported - const std::vector<std::string> valid_but_unsup = { - "0802010000-0000", - "0802011724+0000", - "0406142334-0500", - "9906142334+0500", - "0006142334-0530", - "0006142334+0530", - - "080201000000-0000", - "080201172412+0000", - "040614233433-0500", - "990614233444+0500", - "000614233455-0530", - "000614233455+0530", - }; - - const std::vector<std::string> invalid = { - "", - " ", - "2008`02-01", - "9999-02-01", - "2000-02-01 17", - "999921", - - // valid length 13 -> range check - "080201000061Z", // seconds too big (61) - "080201000060Z", // seconds too big (60, leap seconds not covered by the standard) - "0802010000-1Z", // seconds too small (-1) - "080201006000Z", // minutes too big (60) - "080201240000Z", // hours too big (24:00) - - // valid length 13 -> invalid numbers - "08020123112 Z", - "08020123112!Z", - "08020123112,Z", - "08020123112\nZ", - "080201232 33Z", - "080201232!33Z", - "080201232,33Z", - "080201232\n33Z", - "0802012 3344Z", - "0802012!3344Z", - "0802012,3344Z", - "08022\n334455Z", - "08022 334455Z", - "08022!334455Z", - "08022,334455Z", - "08022\n334455Z", - "082 33445511Z", - "082!33445511Z", - "082,33445511Z", - "082\n33445511Z", - "2 2211221122Z", - "2!2211221122Z", - "2,2211221122Z", - "2\n2211221122Z", - - // wrong time zone - "0802010000", - "0802010000z" - }; - - for(auto&& v : valid) - { - Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); - } + // Dates that are valid per X.500 but rejected as unsupported + const std::vector<std::string> valid_but_unsup = { + "0802010000-0000", + "0802011724+0000", + "0406142334-0500", + "9906142334+0500", + "0006142334-0530", + "0006142334+0530", + + "080201000000-0000", + "080201172412+0000", + "040614233433-0500", + "990614233444+0500", + "000614233455-0530", + "000614233455+0530", + }; - for(auto&& v : valid_but_unsup) - { - result.test_throws("valid but unsupported", [v]() { Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); }); - } + const std::vector<std::string> invalid = { + "", + " ", + "2008`02-01", + "9999-02-01", + "2000-02-01 17", + "999921", + + // valid length 13 -> range check + "080201000061Z", // seconds too big (61) + "080201000060Z", // seconds too big (60, leap seconds not covered by the standard) + "0802010000-1Z", // seconds too small (-1) + "080201006000Z", // minutes too big (60) + "080201240000Z", // hours too big (24:00) + + // valid length 13 -> invalid numbers + "08020123112 Z", + "08020123112!Z", + "08020123112,Z", + "08020123112\nZ", + "080201232 33Z", + "080201232!33Z", + "080201232,33Z", + "080201232\n33Z", + "0802012 3344Z", + "0802012!3344Z", + "0802012,3344Z", + "08022\n334455Z", + "08022 334455Z", + "08022!334455Z", + "08022,334455Z", + "08022\n334455Z", + "082 33445511Z", + "082!33445511Z", + "082,33445511Z", + "082\n33445511Z", + "2 2211221122Z", + "2!2211221122Z", + "2,2211221122Z", + "2\n2211221122Z", + + // wrong time zone + "0802010000", + "0802010000z" + }; - for(auto&& v : invalid) - { - result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); }); - } + for(auto&& v : valid) + { + Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); + } - return result; - } - }; + for(auto&& v : valid_but_unsup) + { + result.test_throws("valid but unsupported", [v]() { Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); }); + } -std::vector<Test::Result> X509_Cert_Unit_Tests::run() + for(auto&& v : invalid) + { + result.test_throws("invalid", [v]() { Botan::X509_Time t(v, Botan::ASN1_Tag::UTC_TIME); }); + } + + return result; + } + +Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") { - std::vector<Test::Result> results; Test::Result result("X509 Unit"); - const std::string hash_fn = "SHA-256"; - /* Create the CA's key and self-signed cert */ - std::unique_ptr<Botan::Private_Key> ca_key(make_a_private_key()); + std::unique_ptr<Botan::Private_Key> ca_key(make_a_private_key(sig_algo)); if(!ca_key) { - // Failure because X.509 enabled but no RSA or ECDSA seems off - result.test_failure("Skipping due to no enabled signature algorithms"); - results.push_back(result); - return results; + // Failure because X.509 enabled but requested signature algorithm is not present + result.test_note("Skipping due to missing signature algorithm: " + sig_algo); + return result; } + /* Create the self-signed cert */ Botan::X509_Certificate ca_cert = Botan::X509::create_self_signed_cert(ca_opts(), *ca_key, hash_fn, Test::rng()); + result.test_eq("ca key usage", (ca_cert.constraints() & Botan::Key_Constraints(Botan::KEY_CERT_SIGN | Botan::CRL_SIGN)) == + Botan::Key_Constraints(Botan::KEY_CERT_SIGN | Botan::CRL_SIGN), true); + /* Create user #1's key and cert request */ - std::unique_ptr<Botan::Private_Key> user1_key(make_a_private_key()); + 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(), + Botan::X509::create_cert_req(req_opts1(sig_algo), *user1_key, hash_fn, Test::rng()); /* Create user #2's key and cert request */ - std::unique_ptr<Botan::Private_Key> user2_key(make_a_private_key()); + 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(), @@ -258,15 +297,41 @@ std::vector<Test::Result> X509_Cert_Unit_Tests::run() from_date(2008, 01, 01), from_date(2033, 01, 01)); - Botan::X509_Certificate user2_cert = ca.sign_request(user2_req, Test::rng(), - from_date(2008, 01, 01), - from_date(2033, 01, 01)); + Botan::X509_Certificate user2_cert = + ca.sign_request(user2_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2033, 01, 01)); + + result.test_eq("user1 key usage", (user1_cert.constraints() & req_opts1(sig_algo).constraints) == req_opts1(sig_algo).constraints, true); + + /* Copy, assign and compare */ + Botan::X509_Certificate user1_cert_copy(user1_cert); + result.test_eq("certificate copy", user1_cert == user1_cert_copy, true); + + user1_cert_copy = user1_cert; + result.test_eq("certificate assignment", user1_cert == user1_cert_copy, true); + + Botan::X509_Certificate user1_cert_differ = + ca.sign_request(user1_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2032, 01, 01)); + + result.test_eq("certificate differs", user1_cert == user1_cert_differ, false); + + /* Get cert data */ + result.test_eq("x509 version", user1_cert.x509_version(), size_t(3)); + + result.test_eq("issuer info CN", user1_cert.issuer_info("CN").at(0), ca_opts().common_name); + result.test_eq("issuer info Country", user1_cert.issuer_info("C").at(0), ca_opts().country); + result.test_eq("issuer info Orga", user1_cert.issuer_info("O").at(0), ca_opts().organization); + result.test_eq("issuer info OrgaUnit", user1_cert.issuer_info("OU").at(0), ca_opts().org_unit); + Botan::X509_CRL crl1 = ca.new_crl(Test::rng()); /* Verify the certs */ Botan::Certificate_Store_In_Memory store; - store.add_certificate(ca_cert); + store.add_certificate(ca.ca_certificate()); Botan::Path_Validation_Restrictions restrictions(false); @@ -316,11 +381,280 @@ std::vector<Test::Result> X509_Cert_Unit_Tests::run() result_u2 = Botan::x509_path_validate(user2_cert, restrictions, store); result.test_eq("user 2 still revoked", result_u2.result_string(), revoked_str); - results.push_back(result); - results.push_back(test_x509_dates()); - return results; + return result; + } + +Test::Result test_usage(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") + { + using Botan::Key_Constraints; + + Test::Result result("X509 Usage"); + + /* Create the CA's key and self-signed cert */ + std::unique_ptr<Botan::Private_Key> ca_key(make_a_private_key(sig_algo)); + + if(!ca_key) + { + // Failure because X.509 enabled but requested signature algorithm is not present + result.test_note("Skipping due to missing signature algorithm: " + sig_algo); + return result; + } + + /* Create the self-signed cert */ + Botan::X509_Certificate ca_cert = + Botan::X509::create_self_signed_cert(ca_opts(), + *ca_key, + hash_fn, + Test::rng()); + + /* Create the CA object */ + Botan::X509_CA ca(ca_cert, *ca_key, hash_fn); + + std::unique_ptr<Botan::Private_Key> user1_key(make_a_private_key(sig_algo)); + + Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); + opts.constraints = Key_Constraints::DIGITAL_SIGNATURE; + + Botan::PKCS10_Request user1_req = + Botan::X509::create_cert_req(opts, + *user1_key, + hash_fn, + Test::rng()); + + Botan::X509_Certificate user1_cert = + ca.sign_request(user1_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2033, 01, 01)); + + // cert only allows digitalSignature, but we check for both digitalSignature and cRLSign + result.test_eq("key usage cRLSign not allowed", user1_cert.allowed_usage(Key_Constraints(Key_Constraints::DIGITAL_SIGNATURE | + Key_Constraints::CRL_SIGN)), false); + + // cert only allows digitalSignature, so checking for only that should be ok + result.confirm("key usage digitalSignature allowed", user1_cert.allowed_usage(Key_Constraints::DIGITAL_SIGNATURE)); + + opts.constraints = Key_Constraints(Key_Constraints::DIGITAL_SIGNATURE | Key_Constraints::CRL_SIGN); + + Botan::PKCS10_Request mult_usage_req = + Botan::X509::create_cert_req(opts, + *user1_key, + hash_fn, + Test::rng()); + + Botan::X509_Certificate mult_usage_cert = + ca.sign_request(mult_usage_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2033, 01, 01)); + + // cert allows multiple usages, so each one of them as well as both together should be allowed + result.confirm("key usage multiple digitalSignature allowed", mult_usage_cert.allowed_usage(Key_Constraints::DIGITAL_SIGNATURE)); + result.confirm("key usage multiple cRLSign allowed", mult_usage_cert.allowed_usage(Key_Constraints::CRL_SIGN)); + result.confirm("key usage multiple digitalSignature and cRLSign allowed", mult_usage_cert.allowed_usage( + Key_Constraints(Key_Constraints::DIGITAL_SIGNATURE | Key_Constraints::CRL_SIGN))); + + opts.constraints = Key_Constraints::NO_CONSTRAINTS; + + Botan::PKCS10_Request no_usage_req = + Botan::X509::create_cert_req(opts, + *user1_key, + hash_fn, + Test::rng()); + + Botan::X509_Certificate no_usage_cert = + ca.sign_request(no_usage_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2033, 01, 01)); + + // cert allows every usage + result.confirm("key usage digitalSignature allowed", no_usage_cert.allowed_usage(Key_Constraints::DIGITAL_SIGNATURE)); + result.confirm("key usage cRLSign allowed", no_usage_cert.allowed_usage(Key_Constraints::CRL_SIGN)); + + return result; + } + +using Botan::Key_Constraints; + +/** +* @brief Some typical key usage scenarios (taken from RFC 5280, sec. 4.2.1.3) +*/ +struct typical_usage_constraints + { + // ALL constraints are not typical at all, but we use them for a negative test + Key_Constraints all = Key_Constraints( + Key_Constraints::DIGITAL_SIGNATURE | Key_Constraints::NON_REPUDIATION | Key_Constraints::KEY_ENCIPHERMENT | + Key_Constraints::DATA_ENCIPHERMENT | Key_Constraints::KEY_AGREEMENT | Key_Constraints::KEY_CERT_SIGN | + Key_Constraints::CRL_SIGN | Key_Constraints::ENCIPHER_ONLY | Key_Constraints::DECIPHER_ONLY); + + Key_Constraints ca = Key_Constraints(Key_Constraints::KEY_CERT_SIGN); + Key_Constraints sign_data = Key_Constraints(Key_Constraints::DIGITAL_SIGNATURE); + Key_Constraints non_repudiation = Key_Constraints(Key_Constraints::NON_REPUDIATION | Key_Constraints::DIGITAL_SIGNATURE); + Key_Constraints key_encipherment = Key_Constraints(Key_Constraints::KEY_ENCIPHERMENT); + Key_Constraints data_encipherment = Key_Constraints(Key_Constraints::DATA_ENCIPHERMENT); + Key_Constraints key_agreement = Key_Constraints(Key_Constraints::KEY_AGREEMENT); + Key_Constraints key_agreement_encipher_only = Key_Constraints(Key_Constraints::KEY_AGREEMENT | Key_Constraints::ENCIPHER_ONLY); + Key_Constraints key_agreement_decipher_only = Key_Constraints(Key_Constraints::KEY_AGREEMENT | Key_Constraints::DECIPHER_ONLY); + Key_Constraints crl_sign = Key_Constraints(Key_Constraints::CRL_SIGN); + Key_Constraints sign_everything = Key_Constraints(Key_Constraints::DIGITAL_SIGNATURE | Key_Constraints::KEY_CERT_SIGN | Key_Constraints::CRL_SIGN); + }; + + +Test::Result test_valid_constraints(const std::string& pk_algo) + { + Test::Result result("X509 Valid Constraints"); + + std::unique_ptr<Botan::Private_Key> key(make_a_private_key(pk_algo)); + + if(!key) + { + // Failure because X.509 enabled but requested algorithm is not present + result.test_note("Skipping due to missing signature algorithm: " + pk_algo); + return result; + } + + // should not throw on empty constraints + verify_cert_constraints_valid_for_key_type(*key, Key_Constraints(Key_Constraints::NO_CONSTRAINTS)); + + // now check some typical usage scenarios for the given key type + typical_usage_constraints typical_usage; + + if(pk_algo == "DH" || pk_algo == "ECDH") + { + // DH and ECDH only for key agreement + result.test_throws("all constraints not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.all); }); + result.test_throws("cert sign not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.ca); }); + result.test_throws("signature not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.sign_data); }); + result.test_throws("non repudiation not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.non_repudiation); }); + result.test_throws("key encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_encipherment); }); + result.test_throws("data encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.data_encipherment); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.key_agreement); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.key_agreement_encipher_only); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.key_agreement_decipher_only); + + result.test_throws("crl sign not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.crl_sign); }); + result.test_throws("sign, cert sign, crl sign not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.sign_everything); }); + } + else if(pk_algo == "RSA") + { + // RSA can do everything except key agreement + result.test_throws("all constraints not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.all); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.ca); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.sign_data); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.non_repudiation); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.key_encipherment); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.data_encipherment); + + result.test_throws("key agreement not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement); }); + result.test_throws("key agreement, encipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_encipher_only); }); + result.test_throws("key agreement, decipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_decipher_only); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.crl_sign); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.sign_everything); + } + else if(pk_algo == "ElGamal") + { + // only ElGamal encryption is currently implemented + result.test_throws("all constraints not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.all); }); + result.test_throws("cert sign not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.ca); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.non_repudiation); + + result.test_throws("key encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_encipherment); }); + result.test_throws("data encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.data_encipherment); }); + + result.test_throws("key agreement not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement); }); + result.test_throws("key agreement, encipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_encipher_only); }); + result.test_throws("key agreement, decipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_decipher_only); }); + result.test_throws("crl sign not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.crl_sign); }); + result.test_throws("sign, cert sign, crl sign not permitted not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.sign_everything); }); + } + else if(pk_algo == "RW" || pk_algo == "NR" || pk_algo == "DSA" || + pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA") + { + // these are signature algorithms only + result.test_throws("all constraints not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.all); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.ca); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.sign_data); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.non_repudiation); + + result.test_throws("key encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_encipherment); }); + result.test_throws("data encipherment not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.data_encipherment); }); + result.test_throws("key agreement not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement); }); + result.test_throws("key agreement, encipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_encipher_only); }); + result.test_throws("key agreement, decipher only not permitted", [&key, &typical_usage]() { verify_cert_constraints_valid_for_key_type(*key, + typical_usage.key_agreement_decipher_only); }); + + verify_cert_constraints_valid_for_key_type(*key, typical_usage.crl_sign); + verify_cert_constraints_valid_for_key_type(*key, typical_usage.sign_everything); + } + + return result; } + +class X509_Cert_Unit_Tests : public Test + { + public: + std::vector<Test::Result> run() override + { + std::vector<Test::Result> results; + const std::vector<std::string> sig_algos { "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA" }; + Test::Result cert_result("X509 Unit"); + Test::Result usage_result("X509 Usage"); + + for(const auto& algo : sig_algos) + { + cert_result.merge(test_x509_cert(algo)); + usage_result.merge(test_usage(algo)); + } + + results.push_back(cert_result); + results.push_back(usage_result); + + const std::vector<std::string> pk_algos { "DH", "ECDH", "RSA", "ElGamal", "RW", "NR", + "DSA", "ECDSA", "ECGDSA", "ECKCDSA" }; + Test::Result valid_constraints_result("X509 Valid Constraints"); + + for(const auto& algo : pk_algos) + { + valid_constraints_result.merge(test_valid_constraints(algo)); + } + + results.push_back(valid_constraints_result); + results.push_back(test_x509_dates()); + + return results; + } + }; + BOTAN_REGISTER_TEST("unit_x509", X509_Cert_Unit_Tests); #endif |