diff options
Diffstat (limited to 'comm/third_party/botan/src/lib/x509')
61 files changed, 12636 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/lib/x509/asn1_alt_name.cpp b/comm/third_party/botan/src/lib/x509/asn1_alt_name.cpp new file mode 100644 index 0000000000..a347a3114c --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/asn1_alt_name.cpp @@ -0,0 +1,265 @@ +/* +* AlternativeName +* (C) 1999-2007 Jack Lloyd +* 2007 Yves Jerschow +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_types.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/oids.h> +#include <botan/internal/stl_util.h> +#include <botan/parsing.h> +#include <botan/loadstor.h> + +#include <sstream> + +namespace Botan { + +/* +* Create an AlternativeName +*/ +AlternativeName::AlternativeName(const std::string& email_addr, + const std::string& uri, + const std::string& dns, + const std::string& ip) + { + add_attribute("RFC822", email_addr); + add_attribute("DNS", dns); + add_attribute("URI", uri); + add_attribute("IP", ip); + } + +/* +* Add an attribute to an alternative name +*/ +void AlternativeName::add_attribute(const std::string& type, + const std::string& value) + { + if(type.empty() || value.empty()) + return; + + auto range = m_alt_info.equal_range(type); + for(auto j = range.first; j != range.second; ++j) + if(j->second == value) + return; + + multimap_insert(m_alt_info, type, value); + } + +/* +* Add an OtherName field +*/ +void AlternativeName::add_othername(const OID& oid, const std::string& value, + ASN1_Tag type) + { + if(value.empty()) + return; + multimap_insert(m_othernames, oid, ASN1_String(value, type)); + } + +/* +* Return all of the alternative names +*/ +std::multimap<std::string, std::string> AlternativeName::contents() const + { + std::multimap<std::string, std::string> names; + + for(auto i = m_alt_info.begin(); i != m_alt_info.end(); ++i) + { + multimap_insert(names, i->first, i->second); + } + + for(auto i = m_othernames.begin(); i != m_othernames.end(); ++i) + { + multimap_insert(names, i->first.to_formatted_string(), i->second.value()); + } + + return names; + } + +bool AlternativeName::has_field(const std::string& attr) const + { + auto range = m_alt_info.equal_range(attr); + return (range.first != range.second); + } + +std::string AlternativeName::get_first_attribute(const std::string& attr) const + { + auto i = m_alt_info.lower_bound(attr); + if(i != m_alt_info.end() && i->first == attr) + return i->second; + + return ""; + } + +std::vector<std::string> AlternativeName::get_attribute(const std::string& attr) const + { + std::vector<std::string> results; + auto range = m_alt_info.equal_range(attr); + for(auto i = range.first; i != range.second; ++i) + results.push_back(i->second); + return results; + } + +X509_DN AlternativeName::dn() const + { + X509_DN dn; + auto range = m_alt_info.equal_range("DN"); + + for(auto i = range.first; i != range.second; ++i) + { + std::istringstream strm(i->second); + strm >> dn; + } + + return dn; + } + +/* +* Return if this object has anything useful +*/ +bool AlternativeName::has_items() const + { + return (m_alt_info.size() > 0 || m_othernames.size() > 0); + } + +namespace { + +/* +* DER encode an AlternativeName entry +*/ +void encode_entries(DER_Encoder& encoder, + const std::multimap<std::string, std::string>& attr, + const std::string& type, ASN1_Tag tagging) + { + auto range = attr.equal_range(type); + + for(auto i = range.first; i != range.second; ++i) + { + if(type == "RFC822" || type == "DNS" || type == "URI") + { + ASN1_String asn1_string(i->second, IA5_STRING); + encoder.add_object(tagging, CONTEXT_SPECIFIC, asn1_string.value()); + } + else if(type == "IP") + { + const uint32_t ip = string_to_ipv4(i->second); + uint8_t ip_buf[4] = { 0 }; + store_be(ip, ip_buf); + encoder.add_object(tagging, CONTEXT_SPECIFIC, ip_buf, 4); + } + else if (type == "DN") + { + std::stringstream ss(i->second); + X509_DN dn; + ss >> dn; + encoder.encode(dn); + } + } + } + +} + +/* +* DER encode an AlternativeName extension +*/ +void AlternativeName::encode_into(DER_Encoder& der) const + { + der.start_cons(SEQUENCE); + + encode_entries(der, m_alt_info, "RFC822", ASN1_Tag(1)); + encode_entries(der, m_alt_info, "DNS", ASN1_Tag(2)); + encode_entries(der, m_alt_info, "DN", ASN1_Tag(4)); + encode_entries(der, m_alt_info, "URI", ASN1_Tag(6)); + encode_entries(der, m_alt_info, "IP", ASN1_Tag(7)); + + for(auto i = m_othernames.begin(); i != m_othernames.end(); ++i) + { + der.start_explicit(0) + .encode(i->first) + .start_explicit(0) + .encode(i->second) + .end_explicit() + .end_explicit(); + } + + der.end_cons(); + } + +/* +* Decode a BER encoded AlternativeName +*/ +void AlternativeName::decode_from(BER_Decoder& source) + { + BER_Decoder names = source.start_cons(SEQUENCE); + + // FIXME this is largely a duplication of GeneralName::decode_from + + while(names.more_items()) + { + BER_Object obj = names.get_next_object(); + + if(obj.is_a(0, CONTEXT_SPECIFIC)) + { + BER_Decoder othername(obj); + + OID oid; + othername.decode(oid); + if(othername.more_items()) + { + BER_Object othername_value_outer = othername.get_next_object(); + othername.verify_end(); + + if(othername_value_outer.is_a(0, ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED)) == false) + throw Decoding_Error("Invalid tags on otherName value"); + + BER_Decoder othername_value_inner(othername_value_outer); + + BER_Object value = othername_value_inner.get_next_object(); + othername_value_inner.verify_end(); + + if(ASN1_String::is_string_type(value.type()) && value.get_class() == UNIVERSAL) + { + add_othername(oid, ASN1::to_string(value), value.type()); + } + } + } + if(obj.is_a(1, CONTEXT_SPECIFIC)) + { + add_attribute("RFC822", ASN1::to_string(obj)); + } + else if(obj.is_a(2, CONTEXT_SPECIFIC)) + { + add_attribute("DNS", ASN1::to_string(obj)); + } + else if(obj.is_a(6, CONTEXT_SPECIFIC)) + { + add_attribute("URI", ASN1::to_string(obj)); + } + else if(obj.is_a(4, ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED))) + { + BER_Decoder dec(obj); + X509_DN dn; + std::stringstream ss; + + dec.decode(dn); + ss << dn; + + add_attribute("DN", ss.str()); + } + else if(obj.is_a(7, CONTEXT_SPECIFIC)) + { + if(obj.length() == 4) + { + const uint32_t ip = load_be<uint32_t>(obj.bits(), 0); + add_attribute("IP", ipv4_to_string(ip)); + } + } + + } + } + +} diff --git a/comm/third_party/botan/src/lib/x509/asn1_alt_name.h b/comm/third_party/botan/src/lib/x509/asn1_alt_name.h new file mode 100644 index 0000000000..b0ca1d87e2 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/asn1_alt_name.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_ALT_NAME_H_ +#define BOTAN_X509_ALT_NAME_H_ + +#include <botan/pkix_types.h> +BOTAN_DEPRECATED_HEADER(asn1_alt_name.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/asn1_attribute.h b/comm/third_party/botan/src/lib/x509/asn1_attribute.h new file mode 100644 index 0000000000..0139c18c70 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/asn1_attribute.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ASN1_ATTRIBUTE_H_ +#define BOTAN_ASN1_ATTRIBUTE_H_ + +#include <botan/pkix_types.h> +BOTAN_DEPRECATED_HEADER(asn1_attribute.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/cert_status.cpp b/comm/third_party/botan/src/lib/x509/cert_status.cpp new file mode 100644 index 0000000000..e22d6e1be1 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/cert_status.cpp @@ -0,0 +1,125 @@ +/* +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_enums.h> + +namespace Botan { + +const char* to_string(Certificate_Status_Code code) + { + switch(code) + { + case Certificate_Status_Code::VERIFIED: + return "Verified"; + case Certificate_Status_Code::OCSP_RESPONSE_GOOD: + return "OCSP response accepted as affirming unrevoked status for certificate"; + case Certificate_Status_Code::OCSP_SIGNATURE_OK: + return "Signature on OCSP response was found valid"; + case Certificate_Status_Code::VALID_CRL_CHECKED: + return "Valid CRL examined"; + + case Certificate_Status_Code::CERT_SERIAL_NEGATIVE: + return "Certificate serial number is negative"; + case Certificate_Status_Code::DN_TOO_LONG: + return "Distinguished name too long"; + case Certificate_Status_Code::OCSP_NO_REVOCATION_URL: + return "OCSP URL not available"; + case Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE: + return "OCSP server not available"; + + case Certificate_Status_Code::NO_REVOCATION_DATA: + return "No revocation data"; + case Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK: + return "Signature method too weak"; + case Certificate_Status_Code::UNTRUSTED_HASH: + return "Hash function used is considered too weak for security"; + + case Certificate_Status_Code::CERT_NOT_YET_VALID: + return "Certificate is not yet valid"; + case Certificate_Status_Code::CERT_HAS_EXPIRED: + return "Certificate has expired"; + case Certificate_Status_Code::OCSP_NOT_YET_VALID: + return "OCSP is not yet valid"; + case Certificate_Status_Code::OCSP_HAS_EXPIRED: + return "OCSP response has expired"; + case Certificate_Status_Code::OCSP_IS_TOO_OLD: + return "OCSP response is too old"; + case Certificate_Status_Code::CRL_NOT_YET_VALID: + return "CRL response is not yet valid"; + case Certificate_Status_Code::CRL_HAS_EXPIRED: + return "CRL has expired"; + + case Certificate_Status_Code::CERT_ISSUER_NOT_FOUND: + return "Certificate issuer not found"; + case Certificate_Status_Code::CANNOT_ESTABLISH_TRUST: + return "Cannot establish trust"; + case Certificate_Status_Code::CERT_CHAIN_LOOP: + return "Loop in certificate chain"; + case Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT: + return "Certificate chain does not end in a CA certificate"; + case Certificate_Status_Code::CHAIN_NAME_MISMATCH: + return "Certificate issuer does not match subject of issuing cert"; + + case Certificate_Status_Code::POLICY_ERROR: + return "Certificate policy error"; + case Certificate_Status_Code::DUPLICATE_CERT_POLICY: + return "Certificate contains duplicate policy"; + case Certificate_Status_Code::INVALID_USAGE: + return "Certificate does not allow the requested usage"; + case Certificate_Status_Code::CERT_CHAIN_TOO_LONG: + return "Certificate chain too long"; + case Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER: + return "CA certificate not allowed to issue certs"; + case Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER: + return "CA certificate not allowed to issue CRLs"; + case Certificate_Status_Code::NO_MATCHING_CRLDP: + return "No CRL with matching distribution point for certificate"; + case Certificate_Status_Code::OCSP_CERT_NOT_LISTED: + return "OCSP cert not listed"; + case Certificate_Status_Code::OCSP_BAD_STATUS: + return "OCSP bad status"; + case Certificate_Status_Code::CERT_NAME_NOMATCH: + return "Certificate does not match provided name"; + case Certificate_Status_Code::NAME_CONSTRAINT_ERROR: + return "Certificate does not pass name constraint"; + case Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION: + return "Unknown critical extension encountered"; + case Certificate_Status_Code::DUPLICATE_CERT_EXTENSION: + return "Duplicate certificate extension encountered"; + case Certificate_Status_Code::EXT_IN_V1_V2_CERT: + return "Encountered extension in certificate with version that does not allow it"; + case Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT: + return "Encountered v2 identifiers in v1 certificate"; + case Certificate_Status_Code::OCSP_SIGNATURE_ERROR: + return "OCSP signature error"; + case Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND: + return "Unable to find certificate issusing OCSP response"; + case Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE: + return "OCSP issuer's keyusage prohibits OCSP"; + case Certificate_Status_Code::OCSP_RESPONSE_INVALID: + return "OCSP parsing valid"; + case Certificate_Status_Code::OCSP_NO_HTTP: + return "OCSP requests not available, no HTTP support compiled in"; + case Certificate_Status_Code::CERT_IS_REVOKED: + return "Certificate is revoked"; + case Certificate_Status_Code::CRL_BAD_SIGNATURE: + return "CRL bad signature"; + case Certificate_Status_Code::SIGNATURE_ERROR: + return "Signature error"; + case Certificate_Status_Code::CERT_PUBKEY_INVALID: + return "Certificate public key invalid"; + case Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN: + return "Certificate signed with unknown/unavailable algorithm"; + case Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS: + return "Certificate signature has invalid parameters"; + + // intentionally no default so we are warned if new enum values are added + } + + return nullptr; + } + +} diff --git a/comm/third_party/botan/src/lib/x509/cert_status.h b/comm/third_party/botan/src/lib/x509/cert_status.h new file mode 100644 index 0000000000..f330ff348f --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/cert_status.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_PATH_RESULT_H_ +#define BOTAN_X509_PATH_RESULT_H_ + +#include <botan/pkix_enums.h> +BOTAN_DEPRECATED_HEADER(cert_status.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor.cpp b/comm/third_party/botan/src/lib/x509/certstor.cpp new file mode 100644 index 0000000000..a066f208d4 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor.cpp @@ -0,0 +1,233 @@ +/* +* Certificate Store +* (C) 1999-2010,2013 Jack Lloyd +* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor.h> +#include <botan/pkix_types.h> +#include <botan/internal/filesystem.h> +#include <botan/hash.h> +#include <botan/data_src.h> + +namespace Botan { + +Certificate_Store::~Certificate_Store() {} + +std::shared_ptr<const X509_Certificate> +Certificate_Store::find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const + { + const auto certs = find_all_certs(subject_dn, key_id); + + if(certs.empty()) + { + return nullptr; // certificate not found + } + + // `count` might be greater than 1, but we'll just select the first match + return certs.front(); + } + +std::shared_ptr<const X509_CRL> Certificate_Store::find_crl_for(const X509_Certificate&) const + { + return {}; + } + +void Certificate_Store_In_Memory::add_certificate(const X509_Certificate& cert) + { + for(const auto& c : m_certs) + if(*c == cert) + return; + + m_certs.push_back(std::make_shared<const X509_Certificate>(cert)); + } + +void Certificate_Store_In_Memory::add_certificate(std::shared_ptr<const X509_Certificate> cert) + { + for(const auto& c : m_certs) + if(*c == *cert) + return; + + m_certs.push_back(cert); + } + +std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const + { + std::vector<X509_DN> subjects; + for(const auto& cert : m_certs) + subjects.push_back(cert->subject_dn()); + return subjects; + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + for(const auto& cert : m_certs) + { + // Only compare key ids if set in both call and in the cert + if(key_id.size()) + { + std::vector<uint8_t> skid = cert->subject_key_id(); + + if(skid.size() && skid != key_id) // no match + continue; + } + + if(cert->subject_dn() == subject_dn) + return cert; + } + + return nullptr; + } + +std::vector<std::shared_ptr<const X509_Certificate>> Certificate_Store_In_Memory::find_all_certs( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + std::vector<std::shared_ptr<const X509_Certificate>> matches; + + for(const auto& cert : m_certs) + { + if(key_id.size()) + { + std::vector<uint8_t> skid = cert->subject_key_id(); + + if(skid.size() && skid != key_id) // no match + continue; + } + + if(cert->subject_dn() == subject_dn) + matches.push_back(cert); + } + + return matches; + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_Memory::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + if(key_hash.size() != 20) + throw Invalid_Argument("Certificate_Store_In_Memory::find_cert_by_pubkey_sha1 invalid hash"); + + std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-1")); + + for(const auto& cert : m_certs){ + hash->update(cert->subject_public_key_bitstring()); + if(key_hash == hash->final_stdvec()) //final_stdvec also clears the hash to initial state + return cert; + } + + return nullptr; + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_Memory::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const + { + if(subject_hash.size() != 32) + throw Invalid_Argument("Certificate_Store_In_Memory::find_cert_by_raw_subject_dn_sha256 invalid hash"); + + std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-256")); + + for(const auto& cert : m_certs){ + hash->update(cert->raw_subject_dn()); + if(subject_hash == hash->final_stdvec()) //final_stdvec also clears the hash to initial state + return cert; + } + + return nullptr; + } + +void Certificate_Store_In_Memory::add_crl(const X509_CRL& crl) + { + std::shared_ptr<const X509_CRL> crl_s = std::make_shared<const X509_CRL>(crl); + return add_crl(crl_s); + } + +void Certificate_Store_In_Memory::add_crl(std::shared_ptr<const X509_CRL> crl) + { + X509_DN crl_issuer = crl->issuer_dn(); + + for(auto& c : m_crls) + { + // Found an update of a previously existing one; replace it + if(c->issuer_dn() == crl_issuer) + { + if(c->this_update() <= crl->this_update()) + c = crl; + return; + } + } + + // Totally new CRL, add to the list + m_crls.push_back(crl); + } + +std::shared_ptr<const X509_CRL> Certificate_Store_In_Memory::find_crl_for(const X509_Certificate& subject) const + { + const std::vector<uint8_t>& key_id = subject.authority_key_id(); + + for(const auto& c : m_crls) + { + // Only compare key ids if set in both call and in the CRL + if(key_id.size()) + { + std::vector<uint8_t> akid = c->authority_key_id(); + + if(akid.size() && akid != key_id) // no match + continue; + } + + if(c->issuer_dn() == subject.issuer_dn()) + return c; + } + + return {}; + } + +Certificate_Store_In_Memory::Certificate_Store_In_Memory(const X509_Certificate& cert) + { + add_certificate(cert); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +Certificate_Store_In_Memory::Certificate_Store_In_Memory(const std::string& dir) + { + if(dir.empty()) + return; + + std::vector<std::string> maybe_certs = get_files_recursive(dir); + + if(maybe_certs.empty()) + { + maybe_certs.push_back(dir); + } + + for(auto&& cert_file : maybe_certs) + { + try + { + DataSource_Stream src(cert_file, true); + while(!src.end_of_data()) + { + try + { + m_certs.push_back(std::make_shared<X509_Certificate>(src)); + } + catch(std::exception&) + { + // stop searching for other certificate at first exception + break; + } + } + } + catch(std::exception&) + { + } + } + } +#endif + +} diff --git a/comm/third_party/botan/src/lib/x509/certstor.h b/comm/third_party/botan/src/lib/x509/certstor.h new file mode 100644 index 0000000000..6901589d26 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor.h @@ -0,0 +1,165 @@ +/* +* Certificate Store +* (C) 1999-2010,2013 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_H_ +#define BOTAN_CERT_STORE_H_ + +#include <botan/x509cert.h> +#include <botan/x509_crl.h> + +namespace Botan { + +/** +* Certificate Store Interface +*/ +class BOTAN_PUBLIC_API(2,0) Certificate_Store + { + public: + virtual ~Certificate_Store(); + + /** + * Find a certificate by Subject DN and (optionally) key identifier + * @param subject_dn the subject's distinguished name + * @param key_id an optional key id + * @return a matching certificate or nullptr otherwise + * If more than one certificate in the certificate store matches, then + * a single value is selected arbitrarily. + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const; + + /** + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + virtual std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const = 0; + + + /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. Used for OCSP. + * @param key_hash SHA-1 hash of the subject's public key + * @return a matching certificate or nullptr otherwise + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const = 0; + + /** + * Find a certificate by searching for one with a matching SHA-256 hash of + * raw subject name. Used for OCSP. + * @param subject_hash SHA-256 hash of the subject's raw name + * @return a matching certificate or nullptr otherwise + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const = 0; + + /** + * Finds a CRL for the given certificate + * @param subject the subject certificate + * @return the CRL for subject or nullptr otherwise + */ + virtual std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const; + + /** + * @return whether the certificate is known + * @param cert certififcate to be searched + */ + bool certificate_known(const X509_Certificate& cert) const + { + return find_cert(cert.subject_dn(), cert.subject_key_id()) != nullptr; + } + + // remove this (used by TLS::Server) + virtual std::vector<X509_DN> all_subjects() const = 0; + }; + +/** +* In Memory Certificate Store +*/ +class BOTAN_PUBLIC_API(2,0) Certificate_Store_In_Memory final : public Certificate_Store + { + public: + /** + * Attempt to parse all files in dir (including subdirectories) + * as certificates. Ignores errors. + */ + explicit Certificate_Store_In_Memory(const std::string& dir); + + /** + * Adds given certificate to the store. + */ + explicit Certificate_Store_In_Memory(const X509_Certificate& cert); + + /** + * Create an empty store. + */ + Certificate_Store_In_Memory() = default; + + /** + * Add a certificate to the store. + * @param cert certificate to be added + */ + void add_certificate(const X509_Certificate& cert); + + /** + * Add a certificate already in a shared_ptr to the store. + * @param cert certificate to be added + */ + void add_certificate(std::shared_ptr<const X509_Certificate> cert); + + /** + * Add a certificate revocation list (CRL) to the store. + * @param crl CRL to be added + */ + void add_crl(const X509_CRL& crl); + + /** + * Add a certificate revocation list (CRL) to the store as a shared_ptr + * @param crl CRL to be added + */ + void add_crl(std::shared_ptr<const X509_CRL> crl); + + /** + * @return DNs for all certificates managed by the store + */ + std::vector<X509_DN> all_subjects() const override; + + /* + * Find a certificate by Subject DN and (optionally) key identifier + * @return the first certificate that matches + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const override; + + /* + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Finds a CRL for the given certificate + */ + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + private: + // TODO: Add indexing on the DN and key id to avoid linear search + std::vector<std::shared_ptr<const X509_Certificate>> m_certs; + std::vector<std::shared_ptr<const X509_CRL>> m_crls; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp b/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp new file mode 100644 index 0000000000..9804759b96 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp @@ -0,0 +1,148 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019 Patrick Schmidt +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_flatfile.h> +#include <botan/pkix_types.h> +#include <botan/data_src.h> +#include <botan/pem.h> +#include <stdexcept> + +namespace Botan { +namespace { +std::vector<std::vector<uint8_t>> decode_all_certificates(DataSource& source) + { + std::vector<std::vector<uint8_t>> pems; + + while(!source.end_of_data()) + { + std::string label; + std::vector<uint8_t> cert; + try + { + cert = unlock(PEM_Code::decode(source, label)); + + if(label == "CERTIFICATE" || label == "X509 CERTIFICATE" || label == "TRUSTED CERTIFICATE") + { + pems.push_back(cert); + } + } + catch(const Decoding_Error&) {} + } + + return pems; + } +} + +Flatfile_Certificate_Store::Flatfile_Certificate_Store(const std::string& file, bool ignore_non_ca) + { + if(file.empty()) + { + throw Invalid_Argument("Flatfile_Certificate_Store::Flatfile_Certificate_Store invalid file path"); + } + + DataSource_Stream file_stream(file); + + for(const std::vector<uint8_t>& der : decode_all_certificates(file_stream)) + { + std::shared_ptr<const X509_Certificate> cert = std::make_shared<const X509_Certificate>(der.data(), der.size()); + + /* + * Various weird or misconfigured system roots include intermediate certificates, + * or even stranger certificates which are not valid for cert issuance at all. + * Previously this code would error on such cases as an obvious misconfiguration, + * but we cannot fix the trust store. So instead just ignore any such certificate. + */ + if(cert->is_self_signed() && cert->is_CA_cert()) + { + m_all_subjects.push_back(cert->subject_dn()); + m_dn_to_cert[cert->subject_dn()].push_back(cert); + m_pubkey_sha1_to_cert.emplace(cert->subject_public_key_bitstring_sha1(), cert); + m_subject_dn_sha256_to_cert.emplace(cert->raw_subject_dn_sha256(), cert); + } + else if(!ignore_non_ca) + { + throw Invalid_Argument("Flatfile_Certificate_Store received non CA cert " + cert->subject_dn().to_string()); + } + } + + if(m_all_subjects.empty()) + { + throw Invalid_Argument("Flatfile_Certificate_Store::Flatfile_Certificate_Store cert file is empty"); + } + } + +std::vector<X509_DN> Flatfile_Certificate_Store::all_subjects() const + { + return m_all_subjects; + } + +std::vector<std::shared_ptr<const X509_Certificate>> Flatfile_Certificate_Store::find_all_certs( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + std::vector<std::shared_ptr<const X509_Certificate>> found_certs; + try + { + const auto certs = m_dn_to_cert.at(subject_dn); + + for(auto cert : certs) + { + if(key_id.empty() || key_id == cert->subject_key_id()) + { + found_certs.push_back(cert); + } + } + } + catch(const std::out_of_range&) + { + return {}; + } + + return found_certs; + } + +std::shared_ptr<const X509_Certificate> +Flatfile_Certificate_Store::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + if(key_hash.size() != 20) + { + throw Invalid_Argument("Flatfile_Certificate_Store::find_cert_by_pubkey_sha1 invalid hash"); + } + + auto found_cert = m_pubkey_sha1_to_cert.find(key_hash); + + if(found_cert != m_pubkey_sha1_to_cert.end()) + { + return found_cert->second; + } + + return nullptr; + } + +std::shared_ptr<const X509_Certificate> +Flatfile_Certificate_Store::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const + { + if(subject_hash.size() != 32) + { throw Invalid_Argument("Flatfile_Certificate_Store::find_cert_by_raw_subject_dn_sha256 invalid hash"); } + + auto found_cert = m_subject_dn_sha256_to_cert.find(subject_hash); + + if(found_cert != m_subject_dn_sha256_to_cert.end()) + { + return found_cert->second; + } + + return nullptr; + } + +std::shared_ptr<const X509_CRL> Flatfile_Certificate_Store::find_crl_for(const X509_Certificate& subject) const + { + BOTAN_UNUSED(subject); + return {}; + } +} diff --git a/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.h b/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.h new file mode 100644 index 0000000000..1608aaa118 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_flatfile/certstor_flatfile.h @@ -0,0 +1,77 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019 Patrick Schmidt +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_FLATFILE_H_ +#define BOTAN_CERT_STORE_FLATFILE_H_ + +#include <botan/certstor.h> + +#include <vector> +#include <memory> +#include <map> + +namespace Botan { +/** +* Certificate Store that is backed by a file of PEMs of trusted CAs. +*/ +class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certificate_Store + { + public: + /** + * Construct a new Certificate_Store given a file path to a file including + * PEMs of trusted self-signed CAs. + * + * @param file the name of the file to read certificates from + * @param ignore_non_ca if true, certs that are not self-signed CA certs will + * be ignored. Otherwise (if false), an exception will be thrown instead. + */ + Flatfile_Certificate_Store(const std::string& file, bool ignore_non_ca = false); + + Flatfile_Certificate_Store(const Flatfile_Certificate_Store&) = default; + Flatfile_Certificate_Store(Flatfile_Certificate_Store&&) = default; + Flatfile_Certificate_Store& operator=(const Flatfile_Certificate_Store&) = default; + Flatfile_Certificate_Store& operator=(Flatfile_Certificate_Store&&) = default; + + /** + * @return DNs for all certificates managed by the store + */ + std::vector<X509_DN> all_subjects() const override; + + /** + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. + * @return a matching certificate or nullptr otherwise + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Fetching CRLs is not supported by this certificate store. This will + * always return an empty list. + */ + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + + private: + std::vector<X509_DN> m_all_subjects; + std::map<X509_DN, std::vector<std::shared_ptr<const X509_Certificate>>> m_dn_to_cert; + std::map<std::vector<uint8_t>, std::shared_ptr<const X509_Certificate>> m_pubkey_sha1_to_cert; + std::map<std::vector<uint8_t>, std::shared_ptr<const X509_Certificate>> m_subject_dn_sha256_to_cert; + }; +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_flatfile/info.txt b/comm/third_party/botan/src/lib/x509/certstor_flatfile/info.txt new file mode 100644 index 0000000000..902997be73 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_flatfile/info.txt @@ -0,0 +1,12 @@ +<defines> +CERTSTOR_FLATFILE -> 20190410 +</defines> + +<os_features> +filesystem +</os_features> + +<header:public> +certstor_flatfile.h +</header:public> + diff --git a/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.cpp b/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.cpp new file mode 100644 index 0000000000..ab4b8e64a7 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.cpp @@ -0,0 +1,335 @@ +/* +* Certificate Store in SQL +* (C) 2016 Kai Michaelis, Rohde & Schwarz Cybersecurity +* (C) 2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_sql.h> +#include <botan/pk_keys.h> +#include <botan/ber_dec.h> +#include <botan/pkcs8.h> +#include <botan/data_src.h> +#include <botan/pkix_types.h> + +namespace Botan { + +Certificate_Store_In_SQL::Certificate_Store_In_SQL(std::shared_ptr<SQL_Database> db, + const std::string& passwd, + RandomNumberGenerator& rng, + const std::string& table_prefix) : + m_rng(rng), + m_database(db), + m_prefix(table_prefix), + m_password(passwd) + { + m_database->create_table("CREATE TABLE IF NOT EXISTS " + + m_prefix + "certificates ( \ + fingerprint BLOB PRIMARY KEY, \ + subject_dn BLOB, \ + key_id BLOB, \ + priv_fingerprint BLOB, \ + certificate BLOB UNIQUE NOT NULL\ + )"); + m_database->create_table("CREATE TABLE IF NOT EXISTS " + m_prefix + "keys (\ + fingerprint BLOB PRIMARY KEY, \ + key BLOB UNIQUE NOT NULL \ + )"); + m_database->create_table("CREATE TABLE IF NOT EXISTS " + m_prefix + "revoked (\ + fingerprint BLOB PRIMARY KEY, \ + reason BLOB NOT NULL, \ + time BLOB NOT NULL \ + )"); + } + +// Certificate handling +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_SQL::find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const + { + std::shared_ptr<SQL_Database::Statement> stmt; + + const std::vector<uint8_t> dn_encoding = subject_dn.BER_encode(); + + if(key_id.empty()) + { + stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE subject_dn == ?1 LIMIT 1"); + stmt->bind(1, dn_encoding); + } + else + { + stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE\ + subject_dn == ?1 AND (key_id == NULL OR key_id == ?2) LIMIT 1"); + stmt->bind(1, dn_encoding); + stmt->bind(2,key_id); + } + + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + return std::make_shared<X509_Certificate>(std::vector<uint8_t>(blob.first, blob.first + blob.second)); + } + + return std::shared_ptr<const X509_Certificate>(); + } + +std::vector<std::shared_ptr<const X509_Certificate>> +Certificate_Store_In_SQL::find_all_certs(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const + { + std::vector<std::shared_ptr<const X509_Certificate>> certs; + + std::shared_ptr<SQL_Database::Statement> stmt; + + const std::vector<uint8_t> dn_encoding = subject_dn.BER_encode(); + + if(key_id.empty()) + { + stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE subject_dn == ?1"); + stmt->bind(1, dn_encoding); + } + else + { + stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE\ + subject_dn == ?1 AND (key_id == NULL OR key_id == ?2)"); + stmt->bind(1, dn_encoding); + stmt->bind(2, key_id); + } + + std::shared_ptr<const X509_Certificate> cert; + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + certs.push_back(std::make_shared<X509_Certificate>( + std::vector<uint8_t>(blob.first,blob.first + blob.second))); + } + + return certs; + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_SQL::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& /*key_hash*/) const + { + throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_pubkey_sha1"); + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_SQL::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& /*subject_hash*/) const + { + throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_raw_subject_dn_sha256"); + } + +std::shared_ptr<const X509_CRL> +Certificate_Store_In_SQL::find_crl_for(const X509_Certificate& subject) const + { + auto all_crls = generate_crls(); + + for(auto crl: all_crls) + { + if(!crl.get_revoked().empty() && crl.issuer_dn() == subject.issuer_dn()) + return std::shared_ptr<X509_CRL>(new X509_CRL(crl)); + } + + return std::shared_ptr<X509_CRL>(); + } + +std::vector<X509_DN> Certificate_Store_In_SQL::all_subjects() const + { + std::vector<X509_DN> ret; + auto stmt = m_database->new_statement("SELECT subject_dn FROM " + m_prefix + "certificates"); + + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + BER_Decoder dec(blob.first,blob.second); + X509_DN dn; + + dn.decode_from(dec); + + ret.push_back(dn); + } + + return ret; + } + +bool Certificate_Store_In_SQL::insert_cert(const X509_Certificate& cert) + { + const std::vector<uint8_t> dn_encoding = cert.subject_dn().BER_encode(); + const std::vector<uint8_t> cert_encoding = cert.BER_encode(); + + auto stmt = m_database->new_statement("INSERT OR REPLACE INTO " + + m_prefix + "certificates (\ + fingerprint, \ + subject_dn, \ + key_id, \ + priv_fingerprint, \ + certificate \ + ) VALUES ( ?1, ?2, ?3, ?4, ?5 )"); + + stmt->bind(1,cert.fingerprint("SHA-256")); + stmt->bind(2,dn_encoding); + stmt->bind(3,cert.subject_key_id()); + stmt->bind(4,std::vector<uint8_t>()); + stmt->bind(5,cert_encoding); + stmt->spin(); + + return true; + } + + +bool Certificate_Store_In_SQL::remove_cert(const X509_Certificate& cert) + { + if(!find_cert(cert.subject_dn(),cert.subject_key_id())) + return false; + + auto stmt = m_database->new_statement("DELETE FROM " + m_prefix + "certificates WHERE fingerprint == ?1"); + + stmt->bind(1,cert.fingerprint("SHA-256")); + stmt->spin(); + + return true; + } + +// Private key handling +std::shared_ptr<const Private_Key> Certificate_Store_In_SQL::find_key(const X509_Certificate& cert) const + { + auto stmt = m_database->new_statement("SELECT key FROM " + m_prefix + "keys " + "JOIN " + m_prefix + "certificates ON " + + m_prefix + "keys.fingerprint == " + m_prefix + "certificates.priv_fingerprint " + "WHERE " + m_prefix + "certificates.fingerprint == ?1"); + stmt->bind(1,cert.fingerprint("SHA-256")); + + std::shared_ptr<const Private_Key> key; + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + DataSource_Memory src(blob.first,blob.second); + key.reset(PKCS8::load_key(src, m_rng, m_password)); + } + + return key; + } + +std::vector<std::shared_ptr<const X509_Certificate>> +Certificate_Store_In_SQL::find_certs_for_key(const Private_Key& key) const + { + auto fpr = key.fingerprint_private("SHA-256"); + auto stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE priv_fingerprint == ?1"); + + stmt->bind(1,fpr); + + std::vector<std::shared_ptr<const X509_Certificate>> certs; + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + certs.push_back(std::make_shared<X509_Certificate>( + std::vector<uint8_t>(blob.first,blob.first + blob.second))); + } + + return certs; + } + +bool Certificate_Store_In_SQL::insert_key(const X509_Certificate& cert, const Private_Key& key) { + insert_cert(cert); + + if(find_key(cert)) + return false; + + auto pkcs8 = PKCS8::BER_encode(key, m_rng, m_password); + auto fpr = key.fingerprint_private("SHA-256"); + + auto stmt1 = m_database->new_statement( + "INSERT OR REPLACE INTO " + m_prefix + "keys ( fingerprint, key ) VALUES ( ?1, ?2 )"); + + stmt1->bind(1,fpr); + stmt1->bind(2,pkcs8.data(),pkcs8.size()); + stmt1->spin(); + + auto stmt2 = m_database->new_statement( + "UPDATE " + m_prefix + "certificates SET priv_fingerprint = ?1 WHERE fingerprint == ?2"); + + stmt2->bind(1,fpr); + stmt2->bind(2,cert.fingerprint("SHA-256")); + stmt2->spin(); + + return true; + } + +void Certificate_Store_In_SQL::remove_key(const Private_Key& key) + { + auto fpr = key.fingerprint_private("SHA-256"); + auto stmt = m_database->new_statement("DELETE FROM " + m_prefix + "keys WHERE fingerprint == ?1"); + + stmt->bind(1,fpr); + stmt->spin(); + } + +// Revocation +void Certificate_Store_In_SQL::revoke_cert(const X509_Certificate& cert, CRL_Code code, const X509_Time& time) + { + insert_cert(cert); + + auto stmt1 = m_database->new_statement( + "INSERT OR REPLACE INTO " + m_prefix + "revoked ( fingerprint, reason, time ) VALUES ( ?1, ?2, ?3 )"); + + stmt1->bind(1,cert.fingerprint("SHA-256")); + stmt1->bind(2,code); + + if(time.time_is_set()) + { + stmt1->bind(3, time.BER_encode()); + } + else + { + stmt1->bind(3, static_cast<size_t>(-1)); + } + + stmt1->spin(); + } + +void Certificate_Store_In_SQL::affirm_cert(const X509_Certificate& cert) + { + auto stmt = m_database->new_statement("DELETE FROM " + m_prefix + "revoked WHERE fingerprint == ?1"); + + stmt->bind(1,cert.fingerprint("SHA-256")); + stmt->spin(); + } + +std::vector<X509_CRL> Certificate_Store_In_SQL::generate_crls() const + { + auto stmt = m_database->new_statement( + "SELECT certificate,reason,time FROM " + m_prefix + "revoked " + "JOIN " + m_prefix + "certificates ON " + + m_prefix + "certificates.fingerprint == " + m_prefix + "revoked.fingerprint"); + + std::map<X509_DN,std::vector<CRL_Entry>> crls; + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + auto cert = X509_Certificate( + std::vector<uint8_t>(blob.first,blob.first + blob.second)); + auto code = static_cast<CRL_Code>(stmt->get_size_t(1)); + auto ent = CRL_Entry(cert,code); + + auto i = crls.find(cert.issuer_dn()); + if(i == crls.end()) + { + crls.insert(std::make_pair(cert.issuer_dn(),std::vector<CRL_Entry>({ent}))); + } + else + { + i->second.push_back(ent); + } + } + + std::vector<X509_CRL> ret; + X509_Time t(std::chrono::system_clock::now()); + + for(auto p: crls) + { + ret.push_back(X509_CRL(p.first,t,t,p.second)); + } + + return ret; + } + +} diff --git a/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.h b/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.h new file mode 100644 index 0000000000..fd80eb1919 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sql/certstor_sql.h @@ -0,0 +1,119 @@ +/* +* Certificate Store in SQL +* (C) 2016 Kai Michaelis, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_SQL_H_ +#define BOTAN_CERT_STORE_SQL_H_ + +#include <botan/certstor.h> +#include <botan/x509cert.h> +#include <botan/x509_crl.h> +#include <botan/database.h> +#include <botan/mutex.h> + +namespace Botan { + +class Private_Key; +class RandomNumberGenerator; + +/** + * Certificate and private key store backed by an SQL database. + */ +class BOTAN_PUBLIC_API(2,0) Certificate_Store_In_SQL : public Certificate_Store + { + public: + /** + * Create/open a certificate store. + * @param db underlying database storage + * @param passwd password to encrypt private keys in the database + * @param rng used for encrypting keys + * @param table_prefix optional prefix for db table names + */ + explicit Certificate_Store_In_SQL(const std::shared_ptr<SQL_Database> db, + const std::string& passwd, + RandomNumberGenerator& rng, + const std::string& table_prefix = ""); + + /** + * Returns the first certificate with matching subject DN and optional key ID. + */ + std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + /* + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Returns all subject DNs known to the store instance. + */ + std::vector<X509_DN> all_subjects() const override; + + /** + * Inserts "cert" into the store, returns false if the certificate is + * already known and true if insertion was successful. + */ + bool insert_cert(const X509_Certificate& cert); + + /** + * Removes "cert" from the store. Returns false if the certificate could not + * be found and true if removal was successful. + */ + bool remove_cert(const X509_Certificate& cert); + + /// Returns the private key for "cert" or an empty shared_ptr if none was found. + std::shared_ptr<const Private_Key> find_key(const X509_Certificate&) const; + + /// Returns all certificates for private key "key". + std::vector<std::shared_ptr<const X509_Certificate>> + find_certs_for_key(const Private_Key& key) const; + + /** + * Inserts "key" for "cert" into the store, returns false if the key is + * already known and true if insertion was successful. + */ + bool insert_key(const X509_Certificate& cert, const Private_Key& key); + + /// Removes "key" from the store. + void remove_key(const Private_Key& key); + + /// Marks "cert" as revoked starting from "time". + void revoke_cert(const X509_Certificate&, CRL_Code, const X509_Time& time = X509_Time()); + + /// Reverses the revokation for "cert". + void affirm_cert(const X509_Certificate&); + + /** + * Generates Certificate Revocation Lists for all certificates marked as revoked. + * A CRL is returned for each unique issuer DN. + */ + std::vector<X509_CRL> generate_crls() const; + + /** + * Generates a CRL for all certificates issued by the given issuer. + */ + std::shared_ptr<const X509_CRL> + find_crl_for(const X509_Certificate& issuer) const override; + + private: + RandomNumberGenerator& m_rng; + std::shared_ptr<SQL_Database> m_database; + std::string m_prefix; + std::string m_password; + mutex_type m_mutex; + }; + +} +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_sql/info.txt b/comm/third_party/botan/src/lib/x509/certstor_sql/info.txt new file mode 100644 index 0000000000..a6046e5d73 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sql/info.txt @@ -0,0 +1,3 @@ +<defines> +CERTSTOR_SQL -> 20160818 +</defines> diff --git a/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.cpp b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.cpp new file mode 100644 index 0000000000..b7c066483d --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.cpp @@ -0,0 +1,19 @@ +/* +* Certificate Store in SQL +* (C) 2016 Kai Michaelis, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_sqlite.h> +#include <botan/sqlite3.h> + +namespace Botan { + +Certificate_Store_In_SQLite::Certificate_Store_In_SQLite(const std::string& db_path, + const std::string& passwd, + RandomNumberGenerator& rng, + const std::string& table_prefix) : + Certificate_Store_In_SQL(std::make_shared<Sqlite3_Database>(db_path), passwd, rng, table_prefix) + {} +} diff --git a/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.h b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.h new file mode 100644 index 0000000000..6d4187e148 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/certstor_sqlite.h @@ -0,0 +1,34 @@ +/* +* Certificate Store in SQL +* (C) 2016 Kai Michaelis, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_SQLITE_H_ +#define BOTAN_CERT_STORE_SQLITE_H_ + +#include <botan/certstor_sql.h> + +namespace Botan { + +/** +* Certificate and private key store backed by an sqlite (https://sqlite.org) database. +*/ +class BOTAN_PUBLIC_API(2,0) Certificate_Store_In_SQLite final : public Certificate_Store_In_SQL + { + public: + /** + * Create/open a certificate store. + * @param db_path path to the database file + * @param passwd password to encrypt private keys in the database + * @param rng used for encrypting keys + * @param table_prefix optional prefix for db table names + */ + Certificate_Store_In_SQLite(const std::string& db_path, + const std::string& passwd, + RandomNumberGenerator& rng, + const std::string& table_prefix = ""); + }; +} +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_sqlite3/info.txt b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/info.txt new file mode 100644 index 0000000000..39b8c579b8 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_sqlite3/info.txt @@ -0,0 +1,8 @@ +<defines> +CERTSTOR_SQLITE3 -> 20160818 +</defines> + +<requires> +certstor_sql +sqlite3 +</requires> diff --git a/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.cpp b/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.cpp new file mode 100644 index 0000000000..0fca1a9757 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.cpp @@ -0,0 +1,70 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_system.h> +#include <botan/pkix_types.h> +#include <botan/x509cert.h> + +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + #include <botan/certstor_macos.h> +#elif defined(BOTAN_HAS_CERTSTOR_WINDOWS) + #include <botan/certstor_windows.h> +#elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) + #include <botan/certstor_flatfile.h> +#endif + +namespace Botan { + +System_Certificate_Store::System_Certificate_Store() + { +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + m_system_store = std::make_shared<Certificate_Store_MacOS>(); +#elif defined(BOTAN_HAS_CERTSTOR_WINDOWS) + m_system_store = std::make_shared<Certificate_Store_Windows>(); +#elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) + m_system_store = std::make_shared<Flatfile_Certificate_Store>(BOTAN_SYSTEM_CERT_BUNDLE, true); +#else + throw Not_Implemented("No system certificate store available in this build"); +#endif + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const + { + return m_system_store->find_cert(subject_dn, key_id); + } + +std::vector<std::shared_ptr<const X509_Certificate>> +System_Certificate_Store::find_all_certs(const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + return m_system_store->find_all_certs(subject_dn, key_id); + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + return m_system_store->find_cert_by_pubkey_sha1(key_hash); + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const + { + return m_system_store->find_cert_by_raw_subject_dn_sha256(subject_hash); + } + +std::shared_ptr<const X509_CRL> +System_Certificate_Store::find_crl_for(const X509_Certificate& subject) const + { + return m_system_store->find_crl_for(subject); + } + +std::vector<X509_DN> System_Certificate_Store::all_subjects() const + { + return m_system_store->all_subjects(); + } + +} diff --git a/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.h b/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.h new file mode 100644 index 0000000000..3a0fc6153a --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system/certstor_system.h @@ -0,0 +1,42 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SYSTEM_CERT_STORE_H_ +#define BOTAN_SYSTEM_CERT_STORE_H_ + +#include <botan/certstor.h> + +namespace Botan { + +class BOTAN_PUBLIC_API(2,11) System_Certificate_Store final : public Certificate_Store + { + public: + + System_Certificate_Store(); + + std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::vector<std::shared_ptr<const X509_Certificate>> + find_all_certs(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + + std::vector<X509_DN> all_subjects() const override; + + private: + std::shared_ptr<Certificate_Store> m_system_store; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_system/info.txt b/comm/third_party/botan/src/lib/x509/certstor_system/info.txt new file mode 100644 index 0000000000..c9b3ca73ef --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system/info.txt @@ -0,0 +1,8 @@ +<defines> +CERTSTOR_SYSTEM -> 20190411 +</defines> + +<os_features> +apple_keychain +filesystem +</os_features> diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.cpp b/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.cpp new file mode 100644 index 0000000000..1836f3da4b --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.cpp @@ -0,0 +1,470 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019-2020 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <algorithm> +#include <array> + +#include <botan/ber_dec.h> +#include <botan/certstor_macos.h> +#include <botan/data_src.h> +#include <botan/der_enc.h> +#include <botan/exceptn.h> +#include <botan/pkix_types.h> + +#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> + +namespace Botan { + +namespace { + +/** + * Abstract RAII wrapper for CFTypeRef-style object handles + * All of those xxxRef types are eventually typedefs to void* + */ +template<typename T> +class scoped_CFType + { + public: + explicit scoped_CFType(T value) + : m_value(value) + { + } + + scoped_CFType(const scoped_CFType<T>& rhs) = delete; + scoped_CFType(scoped_CFType<T>&& rhs) : + m_value(std::move(rhs.m_value)) + { + rhs.m_value = nullptr; + } + + ~scoped_CFType() + { + if(m_value) + { + CFRelease(m_value); + } + } + + operator bool() const { return m_value != nullptr; } + + void assign(T value) + { + BOTAN_ASSERT(m_value == nullptr, "scoped_CFType was not set yet"); + m_value = value; + } + + T& get() { return m_value; } + const T& get() const { return m_value; } + + private: + T m_value; + }; + +/** + * Apple's DN parser "normalizes" ASN1 'PrintableString' into upper-case values + * and strips leading, trailing as well as multiple white spaces. + * See: opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html + */ +X509_DN normalize(const X509_DN& dn) + { + X509_DN result; + + for(const auto& rdn : dn.dn_info()) + { + // TODO: C++14 - use std::get<ASN1_String>(), resp. std::get<OID>() + const auto oid = rdn.first; + auto str = rdn.second; + + if(str.tagging() == ASN1_Tag::PRINTABLE_STRING) + { + std::string normalized; + normalized.reserve(str.value().size()); + for(const char c : str.value()) + { + if(c != ' ') + { + // store all 'normal' characters as upper case + normalized.push_back(::toupper(c)); + } + else if(!normalized.empty() && normalized.back() != ' ') + { + // remove leading and squash multiple white spaces + normalized.push_back(c); + } + } + + if(normalized.back() == ' ') + { + // remove potential remaining single trailing white space char + normalized.erase(normalized.end() - 1); + } + + str = ASN1_String(normalized, str.tagging()); + } + + result.add_attribute(oid, str); + } + + return result; + } + +std::vector<uint8_t> normalizeAndSerialize(const X509_DN& dn) + { + std::vector<uint8_t> result_dn; + DER_Encoder encoder(result_dn); + normalize(dn).encode_into(encoder); + return result_dn; + } + +std::string to_string(const CFStringRef cfstring) + { + const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8); + + if(ccstr != nullptr) + { + return std::string(ccstr); + } + + auto utf16_pairs = CFStringGetLength(cfstring); + auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8); + + std::vector<char> cstr(max_utf8_bytes, '\0'); + auto result = CFStringGetCString(cfstring, + cstr.data(), cstr.size(), + kCFStringEncodingUTF8); + + return (result) ? std::string(cstr.data()) : std::string(); + } + +std::string to_string(const OSStatus status) + { + scoped_CFType<CFStringRef> eCFString( + SecCopyErrorMessageString(status, nullptr)); + return to_string(eCFString.get()); + } + +void check_success(const OSStatus status, const std::string context) + { + if(errSecSuccess == status) + { + return; + } + + throw Internal_Error( + std::string("failed to " + context + ": " + to_string(status))); + } + +template <typename T> +void check_notnull(const T& value, const std::string context) + { + if(value) + { + return; + } + + throw Internal_Error(std::string("failed to ") + context); + } + +} // namespace + +/** + * Internal class implementation (i.e. Pimpl) to keep the required platform- + * dependent members of Certificate_Store_MacOS contained in this compilation + * unit. + */ +class Certificate_Store_MacOS_Impl + { + private: + static constexpr const char* system_roots = + "/System/Library/Keychains/SystemRootCertificates.keychain"; + static constexpr const char* system_keychain = + "/Library/Keychains/System.keychain"; + + public: + /** + * Wraps a list of search query parameters that are later passed into + * Apple's certifificate store API. The class provides some convenience + * functionality and handles the query paramenter's data lifetime. + */ + class Query + { + public: + Query() = default; + ~Query() = default; + Query(Query&& other) = default; + Query& operator=(Query&& other) = default; + + Query(const Query& other) = delete; + Query& operator=(const Query& other) = delete; + + public: + void addParameter(CFStringRef key, CFTypeRef value) + { + m_keys.emplace_back(key); + m_values.emplace_back(value); + } + + void addParameter(CFStringRef key, std::vector<uint8_t> value) + { + // TODO C++17: std::vector::emplace_back will return the reference + // to the inserted object straight away. + m_data_store.emplace_back(std::move(value)); + const auto& data = m_data_store.back(); + + m_data_refs.emplace_back(CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + data.data(), + data.size(), + kCFAllocatorNull)); + const auto& data_ref = m_data_refs.back(); + check_notnull(data_ref, "create CFDataRef of search object failed"); + + addParameter(key, data_ref.get()); + } + + /** + * Amends the user-provided search query with generic filter rules + * for the associated system keychains and transforms it into a + * representation that can be passed to the Apple keychain API. + */ + scoped_CFType<CFDictionaryRef> prepare(const CFArrayRef& keychains, + const SecPolicyRef& policy) + { + addParameter(kSecClass, kSecClassCertificate); + addParameter(kSecReturnRef, kCFBooleanTrue); + addParameter(kSecMatchLimit, kSecMatchLimitAll); + addParameter(kSecMatchTrustedOnly, kCFBooleanTrue); + addParameter(kSecMatchSearchList, keychains); + addParameter(kSecMatchPolicy, policy); + + BOTAN_ASSERT_EQUAL(m_keys.size(), m_values.size(), "valid key-value pairs"); + + auto query = scoped_CFType<CFDictionaryRef>(CFDictionaryCreate( + kCFAllocatorDefault, (const void**)m_keys.data(), + (const void**)m_values.data(), m_keys.size(), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + check_notnull(query, "create search query"); + + return query; + } + + private: + using Data = std::vector<std::vector<uint8_t>>; + using DataRefs = std::vector<scoped_CFType<CFDataRef>>; + using Keys = std::vector<CFStringRef>; + using Values = std::vector<CFTypeRef>; + + Data m_data_store; //! makes sure that data parameters are kept alive + DataRefs m_data_refs; //! keeps track of CFDataRef objects refering into \p m_data_store + Keys m_keys; //! ordered list of search parameter keys + Values m_values; //! ordered list of search parameter values + }; + + public: + Certificate_Store_MacOS_Impl() : + m_policy(SecPolicyCreateBasicX509()), + m_system_roots(nullptr), + m_system_chain(nullptr), + m_keychains(nullptr) + { + check_success(SecKeychainOpen(system_roots, &m_system_roots.get()), + "open system root certificates"); + check_success(SecKeychainOpen(system_keychain, &m_system_chain.get()), + "open system keychain"); + check_notnull(m_system_roots, "open system root certificate chain"); + check_notnull(m_system_chain, "open system certificate chain"); + + // m_keychains is merely a convenience list view into all open keychain + // objects. This list is required in prepareQuery(). + std::array<const void*, 2> keychains{{ + m_system_roots.get(), + m_system_chain.get() + }}; + + m_keychains.assign( + CFArrayCreate(kCFAllocatorDefault, + keychains.data(), + keychains.size(), + &kCFTypeArrayCallBacks)); + check_notnull(m_keychains, "initialize keychain array"); + } + + std::shared_ptr<const X509_Certificate> findOne(Query query) const + { + query.addParameter(kSecMatchLimit, kSecMatchLimitOne); + + scoped_CFType<CFTypeRef> result(nullptr); + search(std::move(query), &result.get()); + + return (result) ? readCertificate(result.get()) : nullptr; + } + + std::vector<std::shared_ptr<const X509_Certificate>> findAll(Query query) const + { + query.addParameter(kSecMatchLimit, kSecMatchLimitAll); + + scoped_CFType<CFArrayRef> result(nullptr); + search(std::move(query), (CFTypeRef*)&result.get()); + + std::vector<std::shared_ptr<const X509_Certificate>> output; + + if(result) + { + const auto count = CFArrayGetCount(result.get()); + BOTAN_ASSERT(count > 0, "certificate result list contains data"); + + for(unsigned int i = 0; i < count; ++i) + { + auto cert = CFArrayGetValueAtIndex(result.get(), i); + output.emplace_back(readCertificate(cert)); + } + } + + return output; + } + + protected: + void search(Query query, CFTypeRef* result) const + { + scoped_CFType<CFDictionaryRef> fullQuery(query.prepare(keychains(), policy())); + + auto status = SecItemCopyMatching(fullQuery.get(), result); + + if(errSecItemNotFound == status) + { + return; // no matches + } + + check_success(status, "look up certificate"); + check_notnull(result, "look up certificate (invalid result value)"); + } + + /** + * Convert a CFTypeRef object into a Botan::X509_Certificate + */ + std::shared_ptr<const X509_Certificate> readCertificate(CFTypeRef object) const + { + if(!object || CFGetTypeID(object) != SecCertificateGetTypeID()) + { + throw Internal_Error("cannot convert CFTypeRef to SecCertificateRef"); + } + + auto cert = static_cast<SecCertificateRef>(const_cast<void*>(object)); + + scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert)); + check_notnull(derData, "read extracted certificate"); + + const auto data = CFDataGetBytePtr(derData.get()); + const auto length = CFDataGetLength(derData.get()); + + DataSource_Memory ds(data, length); + return std::make_shared<Botan::X509_Certificate>(ds); + } + + CFArrayRef keychains() const { return m_keychains.get(); } + SecPolicyRef policy() const { return m_policy.get(); } + + private: + scoped_CFType<SecPolicyRef> m_policy; + scoped_CFType<SecKeychainRef> m_system_roots; + scoped_CFType<SecKeychainRef> m_system_chain; + scoped_CFType<CFArrayRef> m_keychains; + }; + +// +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// +// Implementation of Botan::Certificate_Store interface ... +// +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// + +Certificate_Store_MacOS::Certificate_Store_MacOS() : + m_impl(std::make_shared<Certificate_Store_MacOS_Impl>()) + { + } + +std::vector<X509_DN> Certificate_Store_MacOS::all_subjects() const + { + // Note: This fetches and parses all certificates in the trust store. + // Apple's API provides SecCertificateCopyNormalizedSubjectSequence + // which facilitates reading the certificate DN without parsing the + // entire certificate via Botan::X509_Certificate. However, this + // function applies the same DN "normalization" as stated above. + const auto certificates = m_impl->findAll({}); + + std::vector<X509_DN> output; + std::transform(certificates.cbegin(), certificates.cend(), + std::back_inserter(output), + [](const std::shared_ptr<const X509_Certificate> cert) + { + return cert->subject_dn(); + }); + + return output; + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_MacOS::find_cert(const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + Certificate_Store_MacOS_Impl::Query query; + query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn)); + + if(!key_id.empty()) + { + query.addParameter(kSecAttrSubjectKeyID, key_id); + } + + return m_impl->findOne(std::move(query)); + } + +std::vector<std::shared_ptr<const X509_Certificate>> Certificate_Store_MacOS::find_all_certs( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + Certificate_Store_MacOS_Impl::Query query; + query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn)); + + if(!key_id.empty()) + { + query.addParameter(kSecAttrSubjectKeyID, key_id); + } + + return m_impl->findAll(std::move(query)); + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_MacOS::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + if(key_hash.size() != 20) + { + throw Invalid_Argument("Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash"); + } + + Certificate_Store_MacOS_Impl::Query query; + query.addParameter(kSecAttrPublicKeyHash, key_hash); + + return m_impl->findOne(std::move(query)); + } + +std::shared_ptr<const X509_Certificate> +Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const + { + BOTAN_UNUSED(subject_hash); + throw Not_Implemented("Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256"); + } + +std::shared_ptr<const X509_CRL> Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const + { + BOTAN_UNUSED(subject); + return {}; + } + +} // namespace Botan diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.h b/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.h new file mode 100644 index 0000000000..e7416e631c --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_macos/certstor_macos.h @@ -0,0 +1,81 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_SYSTEM_MACOS_H_ +#define BOTAN_CERT_STORE_SYSTEM_MACOS_H_ + +#include <memory> + +#include <botan/certstor.h> + +namespace Botan { + +class Certificate_Store_MacOS_Impl; + +/** +* Certificate Store that is backed by the system trust store on macOS. This +* opens a handle to the macOS keychain and serves certificate queries directly +* from there. +*/ +class BOTAN_PUBLIC_API(2, 10) Certificate_Store_MacOS final : public Certificate_Store + { + public: + Certificate_Store_MacOS(); + + Certificate_Store_MacOS(const Certificate_Store_MacOS&) = default; + Certificate_Store_MacOS(Certificate_Store_MacOS&&) = default; + Certificate_Store_MacOS& operator=(const Certificate_Store_MacOS&) = default; + Certificate_Store_MacOS& operator=(Certificate_Store_MacOS&&) = default; + + /** + * @return DNs for all certificates managed by the store + */ + std::vector<X509_DN> all_subjects() const override; + + /** + * Find a certificate by Subject DN and (optionally) key identifier + * @return the first certificate that matches + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const override; + + /** + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. + * @return a matching certificate or nullptr otherwise + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + /** + * @throws Botan::Not_Implemented + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Fetching CRLs is not supported by the keychain on macOS. This will + * always return an empty list. + */ + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + + private: + std::shared_ptr<Certificate_Store_MacOS_Impl> m_impl; + }; + +} + +#endif
\ No newline at end of file diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_macos/info.txt b/comm/third_party/botan/src/lib/x509/certstor_system_macos/info.txt new file mode 100644 index 0000000000..723ec61bd9 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_macos/info.txt @@ -0,0 +1,15 @@ +<defines> +CERTSTOR_MACOS -> 20190207 +</defines> + +<os_features> +apple_keychain +</os_features> + +<header:public> +certstor_macos.h +</header:public> + +<frameworks> +macos -> CoreFoundation,Security +</frameworks> diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.cpp b/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.cpp new file mode 100644 index 0000000000..51530954ac --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.cpp @@ -0,0 +1,258 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2018-2019 Patrik Fiedler, Tim Oesterreich +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_windows.h> +#include <botan/pkix_types.h> +#include <botan/der_enc.h> + +#include <array> +#include <vector> + +#define NOMINMAX 1 +#define _WINSOCKAPI_ // stop windows.h including winsock.h +#include <windows.h> +#include <wincrypt.h> + +#define WINCRYPT_UNUSED_PARAM 0 // for avoiding warnings when passing NULL to unused params in win32 api that accept integer types + +namespace Botan { +namespace { + +using Cert_Pointer = std::shared_ptr<const Botan::X509_Certificate>; +using Cert_Vector = std::vector<Cert_Pointer>; +const std::array<const char*, 2> cert_store_names{"Root", "CA"}; + +/** + * Abstract RAII wrapper for PCCERT_CONTEXT and HCERTSTORE + * The Windows API partly takes care of those pointers destructions itself. + * Especially, iteratively calling `CertFindCertificateInStore` with the previous PCCERT_CONTEXT + * will free the context and return a new one. In this case, this guard takes care of freeing the context + * in case of an exception and at the end of the iterative process. + */ +template<class T> +class Handle_Guard + { + public: + Handle_Guard(T context) + : m_context(context) + { + } + + Handle_Guard(const Handle_Guard<T>& rhs) = delete; + Handle_Guard(Handle_Guard<T>&& rhs) : + m_context(std::move(rhs.m_context)) + { + rhs.m_context = nullptr; + } + + ~Handle_Guard() + { + close<T>(); + } + + operator bool() const + { + return m_context != nullptr; + } + + bool assign(T context) + { + m_context = context; + return m_context != nullptr; + } + + T& get() + { + return m_context; + } + + const T& get() const + { + return m_context; + } + + T operator->() + { + return m_context; + } + + private: + template<class T2 = T> + typename std::enable_if<std::is_same<T2, PCCERT_CONTEXT>::value>::type close() + { + if(m_context) + { + CertFreeCertificateContext(m_context); + } + } + + template<class T2 = T> + typename std::enable_if<std::is_same<T2, HCERTSTORE>::value>::type close() + { + if(m_context) + { + // second parameter is a flag that tells the store how to deallocate memory + // using the default "0", this function works like decreasing the reference counter + // in a shared_ptr + CertCloseStore(m_context, 0); + } + } + + T m_context; + }; + +HCERTSTORE open_cert_store(const char* cert_store_name) + { + auto store = CertOpenSystemStoreA(WINCRYPT_UNUSED_PARAM, cert_store_name); + if(!store) + { + throw Botan::Internal_Error( + "failed to open windows certificate store '" + std::string(cert_store_name) + + "' (Error Code: " + + std::to_string(::GetLastError()) + ")"); + } + return store; + } + +Cert_Vector search_cert_stores(const _CRYPTOAPI_BLOB& blob, const DWORD& find_type, + std::function<bool(const Cert_Vector& certs, Cert_Pointer cert)> filter, + bool return_on_first_found) + { + Cert_Vector certs; + for(const auto store_name : cert_store_names) + { + Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name); + Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr; + while(cert_context.assign(CertFindCertificateInStore( + windows_cert_store.get(), PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + WINCRYPT_UNUSED_PARAM, find_type, + &blob, cert_context.get()))) + { + auto cert = std::make_shared<X509_Certificate>(cert_context->pbCertEncoded, cert_context->cbCertEncoded); + if(filter(certs, cert)) + { + if(return_on_first_found) + { + return {cert}; + } + certs.push_back(cert); + } + } + } + + return certs; + } + +bool already_contains_certificate(const Cert_Vector& certs, Cert_Pointer cert) + { + return std::any_of(certs.begin(), certs.end(), [&](std::shared_ptr<const Botan::X509_Certificate> c) + { + return *c == *cert; + }); + } + +Cert_Vector find_cert_by_dn_and_key_id(const Botan::X509_DN& subject_dn, + const std::vector<uint8_t>& key_id, + bool return_on_first_found) + { + _CRYPTOAPI_BLOB blob; + DWORD find_type; + std::vector<uint8_t> dn_data; + + // if key_id is available, prefer searching that, as it should be "more unique" than the subject DN + if(key_id.empty()) + { + find_type = CERT_FIND_SUBJECT_NAME; + DER_Encoder encoder(dn_data); + subject_dn.encode_into(encoder); + blob.cbData = static_cast<DWORD>(dn_data.size()); + blob.pbData = reinterpret_cast<BYTE*>(dn_data.data()); + } + else + { + find_type = CERT_FIND_KEY_IDENTIFIER; + blob.cbData = static_cast<DWORD>(key_id.size()); + blob.pbData = const_cast<BYTE*>(key_id.data()); + } + + auto filter = [&](const Cert_Vector& certs, Cert_Pointer cert) + { + return !already_contains_certificate(certs, cert) && (key_id.empty() || cert->subject_dn() == subject_dn); + }; + + return search_cert_stores(blob, find_type, filter, return_on_first_found); + } +} // namespace + +Certificate_Store_Windows::Certificate_Store_Windows() {} + +std::vector<X509_DN> Certificate_Store_Windows::all_subjects() const + { + std::vector<X509_DN> subject_dns; + for(const auto store_name : cert_store_names) + { + Handle_Guard<HCERTSTORE> windows_cert_store = open_cert_store(store_name); + Handle_Guard<PCCERT_CONTEXT> cert_context = nullptr; + + // Handle_Guard::assign exchanges the underlying pointer. No RAII is needed here, because the Windows API takes care of + // freeing the previous context. + while(cert_context.assign(CertEnumCertificatesInStore(windows_cert_store.get(), cert_context.get()))) + { + X509_Certificate cert(cert_context->pbCertEncoded, cert_context->cbCertEncoded); + subject_dns.push_back(cert.subject_dn()); + } + } + + return subject_dns; + } + +Cert_Pointer Certificate_Store_Windows::find_cert(const Botan::X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + const auto certs = find_cert_by_dn_and_key_id(subject_dn, key_id, true); + return certs.empty() ? nullptr : certs.front(); + } + +Cert_Vector Certificate_Store_Windows::find_all_certs( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + return find_cert_by_dn_and_key_id(subject_dn, key_id, false); + } + +Cert_Pointer Certificate_Store_Windows::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + if(key_hash.size() != 20) + { + throw Invalid_Argument("Certificate_Store_Windows::find_cert_by_pubkey_sha1 invalid hash"); + } + + CRYPT_HASH_BLOB blob; + blob.cbData = static_cast<DWORD>(key_hash.size()); + blob.pbData = const_cast<BYTE*>(key_hash.data()); + + auto filter = [](const Cert_Vector&, Cert_Pointer) { return true; }; + + const auto certs = search_cert_stores(blob, CERT_FIND_KEY_IDENTIFIER, filter, true); + return certs.empty() ? nullptr : certs.front(); + } + +Cert_Pointer Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256( + const std::vector<uint8_t>& subject_hash) const + { + BOTAN_UNUSED(subject_hash); + throw Not_Implemented("Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256"); + } + +std::shared_ptr<const X509_CRL> Certificate_Store_Windows::find_crl_for(const X509_Certificate& subject) const + { + // TODO: this could be implemented by using the CertFindCRLInStore function + BOTAN_UNUSED(subject); + return {}; + } +} diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.h b/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.h new file mode 100644 index 0000000000..f47e718c85 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_windows/certstor_windows.h @@ -0,0 +1,70 @@ +/* +* Certificate Store +* (C) 1999-2019 Jack Lloyd +* (C) 2019 Patrick Schmidt +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CERT_STORE_SYSTEM_WINDOWS_H_ +#define BOTAN_CERT_STORE_SYSTEM_WINDOWS_H_ + +#include <botan/certstor.h> + +namespace Botan { +/** +* Certificate Store that is backed by the system trust store on Windows. +*/ +class BOTAN_PUBLIC_API(2, 11) Certificate_Store_Windows final : public Certificate_Store + { + public: + Certificate_Store_Windows(); + + Certificate_Store_Windows(const Certificate_Store_Windows&) = default; + Certificate_Store_Windows(Certificate_Store_Windows&&) = default; + Certificate_Store_Windows& operator=(const Certificate_Store_Windows&) = default; + Certificate_Store_Windows& operator=(Certificate_Store_Windows&&) = default; + + /** + * @return DNs for all certificates managed by the store + */ + std::vector<X509_DN> all_subjects() const override; + + /** + * Find a certificate by Subject DN and (optionally) key identifier + * @return the first certificate that matches + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const override; + + /** + * Find all certificates with a given Subject DN. + * Subject DN and even the key identifier might not be unique. + */ + std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs( + const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. + * @return a matching certificate or nullptr otherwise + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + /** + * @throws Botan::Not_Implemented + */ + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + /** + * Not Yet Implemented + * @return nullptr; + */ + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + }; +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/certstor_system_windows/info.txt b/comm/third_party/botan/src/lib/x509/certstor_system_windows/info.txt new file mode 100644 index 0000000000..6d70de8bf2 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/certstor_system_windows/info.txt @@ -0,0 +1,16 @@ +<defines> +CERTSTOR_WINDOWS -> 20190430 +</defines> + +<os_features> +win32,certificate_store +</os_features> + +<header:public> +certstor_windows.h +</header:public> + +<libs> +windows -> crypt32 +mingw -> crypt32 +</libs> diff --git a/comm/third_party/botan/src/lib/x509/crl_ent.cpp b/comm/third_party/botan/src/lib/x509/crl_ent.cpp new file mode 100644 index 0000000000..d83d43d73c --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/crl_ent.cpp @@ -0,0 +1,140 @@ +/* +* CRL Entry +* (C) 1999-2010 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_crl.h> +#include <botan/x509cert.h> +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/bigint.h> + +namespace Botan { + +struct CRL_Entry_Data + { + std::vector<uint8_t> m_serial; + X509_Time m_time; + CRL_Code m_reason = UNSPECIFIED; + Extensions m_extensions; + }; + +/* +* Create a CRL_Entry +*/ +CRL_Entry::CRL_Entry(const X509_Certificate& cert, CRL_Code why) + { + m_data.reset(new CRL_Entry_Data); + m_data->m_serial = cert.serial_number(); + m_data->m_time = X509_Time(std::chrono::system_clock::now()); + m_data->m_reason = why; + + if(why != UNSPECIFIED) + { + m_data->m_extensions.add(new Cert_Extension::CRL_ReasonCode(why)); + } + } + +/* +* Compare two CRL_Entrys for equality +*/ +bool operator==(const CRL_Entry& a1, const CRL_Entry& a2) + { + if(a1.serial_number() != a2.serial_number()) + return false; + if(a1.expire_time() != a2.expire_time()) + return false; + if(a1.reason_code() != a2.reason_code()) + return false; + return true; + } + +/* +* Compare two CRL_Entrys for inequality +*/ +bool operator!=(const CRL_Entry& a1, const CRL_Entry& a2) + { + return !(a1 == a2); + } + +/* +* DER encode a CRL_Entry +*/ +void CRL_Entry::encode_into(DER_Encoder& der) const + { + der.start_cons(SEQUENCE) + .encode(BigInt::decode(serial_number())) + .encode(expire_time()) + .start_cons(SEQUENCE) + .encode(extensions()) + .end_cons() + .end_cons(); + } + +/* +* Decode a BER encoded CRL_Entry +*/ +void CRL_Entry::decode_from(BER_Decoder& source) + { + BigInt serial_number_bn; + + std::unique_ptr<CRL_Entry_Data> data(new CRL_Entry_Data); + + BER_Decoder entry = source.start_cons(SEQUENCE); + + entry.decode(serial_number_bn).decode(data->m_time); + data->m_serial = BigInt::encode(serial_number_bn); + + if(entry.more_items()) + { + entry.decode(data->m_extensions); + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_ReasonCode>()) + { + data->m_reason = ext->get_reason(); + } + else + { + data->m_reason = UNSPECIFIED; + } + } + + entry.end_cons(); + + m_data.reset(data.release()); + } + +const CRL_Entry_Data& CRL_Entry::data() const + { + if(!m_data) + { + throw Invalid_State("CRL_Entry_Data uninitialized"); + } + + return *m_data.get(); + } + +const std::vector<uint8_t>& CRL_Entry::serial_number() const + { + return data().m_serial; + } + +const X509_Time& CRL_Entry::expire_time() const + { + return data().m_time; + } + +CRL_Code CRL_Entry::reason_code() const + { + return data().m_reason; + } + +const Extensions& CRL_Entry::extensions() const + { + return data().m_extensions; + } + + +} diff --git a/comm/third_party/botan/src/lib/x509/crl_ent.h b/comm/third_party/botan/src/lib/x509/crl_ent.h new file mode 100644 index 0000000000..aa60d4172b --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/crl_ent.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CRL_ENTRY_H_ +#define BOTAN_CRL_ENTRY_H_ + +#include <botan/x509_crl.h> +BOTAN_DEPRECATED_HEADER(crl_ent.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/datastor.cpp b/comm/third_party/botan/src/lib/x509/datastor.cpp new file mode 100644 index 0000000000..2cdd3458ca --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/datastor.cpp @@ -0,0 +1,205 @@ +/* +* Data Store +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/datastor.h> +#include <botan/exceptn.h> +#include <botan/parsing.h> +#include <botan/hex.h> +#include <botan/internal/stl_util.h> + +namespace Botan { + +/* +* Data_Store Equality Comparison +*/ +bool Data_Store::operator==(const Data_Store& other) const + { + return (m_contents == other.m_contents); + } + +/* +* Check if this key has at least one value +*/ +bool Data_Store::has_value(const std::string& key) const + { + return (m_contents.lower_bound(key) != m_contents.end()); + } + +/* +* Search based on an arbitrary predicate +*/ +std::multimap<std::string, std::string> Data_Store::search_for( + std::function<bool (std::string, std::string)> predicate) const + { + std::multimap<std::string, std::string> out; + + for(auto i = m_contents.begin(); i != m_contents.end(); ++i) + if(predicate(i->first, i->second)) + out.insert(std::make_pair(i->first, i->second)); + + return out; + } + +/* +* Search based on key equality +*/ +std::vector<std::string> Data_Store::get(const std::string& looking_for) const + { + std::vector<std::string> out; + auto range = m_contents.equal_range(looking_for); + for(auto i = range.first; i != range.second; ++i) + out.push_back(i->second); + return out; + } + +/* +* Get a single atom +*/ +std::string Data_Store::get1(const std::string& key) const + { + std::vector<std::string> vals = get(key); + + if(vals.empty()) + throw Invalid_State("Data_Store::get1: No values set for " + key); + if(vals.size() > 1) + throw Invalid_State("Data_Store::get1: More than one value for " + key); + + return vals[0]; + } + +std::string Data_Store::get1(const std::string& key, + const std::string& default_value) const + { + std::vector<std::string> vals = get(key); + + if(vals.size() > 1) + throw Invalid_State("Data_Store::get1: More than one value for " + key); + + if(vals.empty()) + return default_value; + + return vals[0]; + } + +/* +* Get a single std::vector atom +*/ +std::vector<uint8_t> +Data_Store::get1_memvec(const std::string& key) const + { + std::vector<std::string> vals = get(key); + + if(vals.empty()) + return std::vector<uint8_t>(); + + if(vals.size() > 1) + throw Invalid_State("Data_Store::get1_memvec: Multiple values for " + + key); + + return hex_decode(vals[0]); + } + +/* +* Get a single uint32_t atom +*/ +uint32_t Data_Store::get1_uint32(const std::string& key, + uint32_t default_val) const + { + std::vector<std::string> vals = get(key); + + if(vals.empty()) + return default_val; + else if(vals.size() > 1) + throw Invalid_State("Data_Store::get1_uint32: Multiple values for " + key); + + return to_u32bit(vals[0]); + } + +/* +* Insert a single key and value +*/ +void Data_Store::add(const std::string& key, const std::string& val) + { + multimap_insert(m_contents, key, val); + } + +/* +* Insert a single key and value +*/ +void Data_Store::add(const std::string& key, uint32_t val) + { + add(key, std::to_string(val)); + } + +/* +* Insert a single key and value +*/ +void Data_Store::add(const std::string& key, const secure_vector<uint8_t>& val) + { + add(key, hex_encode(val.data(), val.size())); + } + +void Data_Store::add(const std::string& key, const std::vector<uint8_t>& val) + { + add(key, hex_encode(val.data(), val.size())); + } + +/* +* Insert a mapping of key/value pairs +*/ +void Data_Store::add(const std::multimap<std::string, std::string>& in) + { + std::multimap<std::string, std::string>::const_iterator i = in.begin(); + while(i != in.end()) + { + m_contents.insert(*i); + ++i; + } + } + +/* +* Create and populate a X509_DN +*/ +X509_DN create_dn(const Data_Store& info) + { + auto names = info.search_for( + [](const std::string& key, const std::string&) + { + return (key.find("X520.") != std::string::npos); + }); + + X509_DN dn; + + for(auto i = names.begin(); i != names.end(); ++i) + dn.add_attribute(i->first, i->second); + + return dn; + } + +/* +* Create and populate an AlternativeName +*/ +AlternativeName create_alt_name(const Data_Store& info) + { + auto names = info.search_for( + [](const std::string& key, const std::string&) + { + return (key == "RFC822" || + key == "DNS" || + key == "URI" || + key == "IP"); + }); + + AlternativeName alt_name; + + for(auto i = names.begin(); i != names.end(); ++i) + alt_name.add_attribute(i->first, i->second); + + return alt_name; + } + +} diff --git a/comm/third_party/botan/src/lib/x509/datastor.h b/comm/third_party/botan/src/lib/x509/datastor.h new file mode 100644 index 0000000000..1ff85c22cf --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/datastor.h @@ -0,0 +1,85 @@ +/* +* Data Store +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_DATA_STORE_H_ +#define BOTAN_DATA_STORE_H_ + +#include <botan/pkix_types.h> +#include <functional> +#include <string> +#include <vector> +#include <map> + +BOTAN_FUTURE_INTERNAL_HEADER(datastor.h) + +namespace Botan { + +/** +* Data Store +* +* This class is used internally by the library, and exposed for ABI +* reasons. There is no reason for applications to use this type directly. +* It will be removed in a future major release. +*/ +class BOTAN_UNSTABLE_API Data_Store final + { + public: + /** + * A search function + */ + bool operator==(const Data_Store&) const; + + std::multimap<std::string, std::string> search_for( + std::function<bool (std::string, std::string)> predicate) const; + + std::vector<std::string> get(const std::string&) const; + + std::string get1(const std::string& key) const; + + std::string get1(const std::string& key, + const std::string& default_value) const; + + std::vector<uint8_t> get1_memvec(const std::string&) const; + uint32_t get1_uint32(const std::string&, uint32_t = 0) const; + + bool has_value(const std::string&) const; + + void add(const std::multimap<std::string, std::string>&); + void add(const std::string&, const std::string&); + void add(const std::string&, uint32_t); + void add(const std::string&, const secure_vector<uint8_t>&); + void add(const std::string&, const std::vector<uint8_t>&); + private: + std::multimap<std::string, std::string> m_contents; + }; + +/* +* Data Store Extraction Operations +*/ + +/* +* Create and populate a X509_DN +* @param info data store containing DN information +* @return DN containing attributes from data store +*/ +BOTAN_PUBLIC_API(2,0) X509_DN +BOTAN_DEPRECATED("Avoid roundtripping names through Data_Store") +create_dn(const Data_Store& info); + +/* +* Create and populate an AlternativeName +* @param info data store containing AlternativeName information +* @return AlternativeName containing attributes from data store +*/ +BOTAN_PUBLIC_API(2,0) AlternativeName +BOTAN_DEPRECATED("Avoid roundtripping names through Data_Store") +create_alt_name(const Data_Store& info); + + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/info.txt b/comm/third_party/botan/src/lib/x509/info.txt new file mode 100644 index 0000000000..20a1aa2b08 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/info.txt @@ -0,0 +1,12 @@ +<defines> +X509_CERTIFICATES -> 20151023 +X509 -> 20180911 +OCSP -> 20161118 +</defines> + +<requires> +asn1 +pubkey +sha1 +sha2_32 +</requires> diff --git a/comm/third_party/botan/src/lib/x509/key_constraint.cpp b/comm/third_party/botan/src/lib/x509/key_constraint.cpp new file mode 100644 index 0000000000..09a4c059a3 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/key_constraint.cpp @@ -0,0 +1,106 @@ +/* +* KeyUsage +* (C) 1999-2007,2016 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_types.h> +#include <botan/pk_keys.h> +#include <vector> + +namespace Botan { + +std::string key_constraints_to_string(Key_Constraints constraints) + { + std::vector<std::string> str; + + if(constraints == NO_CONSTRAINTS) + return "no_constraints"; + + if(constraints & DIGITAL_SIGNATURE) + str.push_back("digital_signature"); + + if(constraints & NON_REPUDIATION) + str.push_back("non_repudiation"); + + if(constraints & KEY_ENCIPHERMENT) + str.push_back("key_encipherment"); + + if(constraints & DATA_ENCIPHERMENT) + str.push_back("data_encipherment"); + + if(constraints & KEY_AGREEMENT) + str.push_back("key_agreement"); + + if(constraints & KEY_CERT_SIGN) + str.push_back("key_cert_sign"); + + if(constraints & CRL_SIGN) + str.push_back("crl_sign"); + + if(constraints & ENCIPHER_ONLY) + str.push_back("encipher_only"); + + if(constraints & DECIPHER_ONLY) + str.push_back("decipher_only"); + + // Not 0 (checked at start) but nothing matched above! + if(str.empty()) + return "other_unknown_constraints"; + + if(str.size() == 1) + return str[0]; + + std::string out; + for(size_t i = 0; i < str.size() - 1; ++i) + { + out += str[i]; + out += ','; + } + out += str[str.size() - 1]; + + return out; + } + +/* +* Make sure the given key constraints are permitted for the given key type +*/ +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 permitted = 0; + + const bool can_agree = (name == "DH" || name == "ECDH"); + const bool can_encrypt = (name == "RSA" || name == "ElGamal"); + + const bool can_sign = + (name == "RSA" || name == "DSA" || + name == "ECDSA" || name == "ECGDSA" || name == "ECKCDSA" || name == "Ed25519" || + name == "GOST-34.10" || name == "GOST-34.10-2012-256" || name == "GOST-34.10-2012-512"); + + if(can_agree) + { + permitted |= KEY_AGREEMENT | ENCIPHER_ONLY | DECIPHER_ONLY; + } + + if(can_encrypt) + { + permitted |= KEY_ENCIPHERMENT | DATA_ENCIPHERMENT; + } + + if(can_sign) + { + permitted |= DIGITAL_SIGNATURE | NON_REPUDIATION | KEY_CERT_SIGN | CRL_SIGN; + } + + if(Key_Constraints(constraints & permitted) != constraints) + { + throw Invalid_Argument("Invalid " + name + " constraints " + key_constraints_to_string(constraints)); + } + } + +} diff --git a/comm/third_party/botan/src/lib/x509/key_constraint.h b/comm/third_party/botan/src/lib/x509/key_constraint.h new file mode 100644 index 0000000000..5d5c7b0832 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/key_constraint.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_KEY_CONSTRAINT_H_ +#define BOTAN_KEY_CONSTRAINT_H_ + +#include <botan/pkix_enums.h> +BOTAN_DEPRECATED_HEADER(key_constraint.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/name_constraint.cpp b/comm/third_party/botan/src/lib/x509/name_constraint.cpp new file mode 100644 index 0000000000..c9045729de --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/name_constraint.cpp @@ -0,0 +1,273 @@ +/* +* X.509 Name Constraint +* (C) 2015 Kai Michaelis +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_types.h> +#include <botan/ber_dec.h> +#include <botan/loadstor.h> +#include <botan/x509cert.h> +#include <botan/parsing.h> +#include <sstream> + +namespace Botan { + +class DER_Encoder; + +GeneralName::GeneralName(const std::string& str) : GeneralName() + { + size_t p = str.find(':'); + + if(p != std::string::npos) + { + m_type = str.substr(0, p); + m_name = str.substr(p + 1, std::string::npos); + } + else + { + throw Invalid_Argument("Failed to decode Name Constraint"); + } + } + +void GeneralName::encode_into(DER_Encoder&) const + { + throw Not_Implemented("GeneralName encoding"); + } + +void GeneralName::decode_from(class BER_Decoder& ber) + { + BER_Object obj = ber.get_next_object(); + + if(obj.is_a(1, CONTEXT_SPECIFIC)) + { + m_type = "RFC822"; + m_name = ASN1::to_string(obj); + } + else if(obj.is_a(2, CONTEXT_SPECIFIC)) + { + m_type = "DNS"; + m_name = ASN1::to_string(obj); + } + else if(obj.is_a(6, CONTEXT_SPECIFIC)) + { + m_type = "URI"; + m_name = ASN1::to_string(obj); + } + else if(obj.is_a(4, ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED))) + { + m_type = "DN"; + X509_DN dn; + BER_Decoder dec(obj); + std::stringstream ss; + + dn.decode_from(dec); + ss << dn; + + m_name = ss.str(); + } + else if(obj.is_a(7, CONTEXT_SPECIFIC)) + { + if(obj.length() == 8) + { + m_type = "IP"; + m_name = ipv4_to_string(load_be<uint32_t>(obj.bits(), 0)) + "/" + + ipv4_to_string(load_be<uint32_t>(obj.bits(), 1)); + } + else if(obj.length() == 32) + { + throw Decoding_Error("Unsupported IPv6 name constraint"); + } + else + { + throw Decoding_Error("Invalid IP name constraint size " + std::to_string(obj.length())); + } + } + else + { + throw Decoding_Error("Found unknown GeneralName type"); + } + } + +GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) const + { + std::vector<std::string> nam; + std::function<bool(const GeneralName*, const std::string&)> match_fn; + + const X509_DN& dn = cert.subject_dn(); + const AlternativeName& alt_name = cert.subject_alt_name(); + + if(type() == "DNS") + { + match_fn = std::mem_fn(&GeneralName::matches_dns); + + nam = alt_name.get_attribute("DNS"); + + if(nam.empty()) + { + nam = dn.get_attribute("CN"); + } + } + else if(type() == "DN") + { + match_fn = std::mem_fn(&GeneralName::matches_dn); + + nam.push_back(dn.to_string()); + + const auto alt_dn = alt_name.dn(); + if(alt_dn.empty() == false) + { + nam.push_back(alt_dn.to_string()); + } + } + else if(type() == "IP") + { + match_fn = std::mem_fn(&GeneralName::matches_ip); + nam = alt_name.get_attribute("IP"); + } + else + { + return MatchResult::UnknownType; + } + + if(nam.empty()) + { + return MatchResult::NotFound; + } + + bool some = false; + bool all = true; + + for(const std::string& n: nam) + { + bool m = match_fn(this, n); + + some |= m; + all &= m; + } + + if(all) + { + return MatchResult::All; + } + else if(some) + { + return MatchResult::Some; + } + else + { + return MatchResult::None; + } + } + +bool GeneralName::matches_dns(const std::string& nam) const + { + if(nam.size() == name().size()) + { + return tolower_string(nam) == tolower_string(name()); + } + else if(name().size() > nam.size()) + { + // The constraint is longer than the issued name: not possibly a match + return false; + } + else // name.size() < nam.size() + { + // constr is suffix of nam + const std::string constr = name().front() == '.' ? name() : "." + name(); + const std::string substr = nam.substr(nam.size() - constr.size(), constr.size()); + return tolower_string(constr) == tolower_string(substr); + } + } + +bool GeneralName::matches_dn(const std::string& nam) const + { + std::stringstream ss(nam); + std::stringstream tt(name()); + X509_DN nam_dn, my_dn; + + ss >> nam_dn; + tt >> my_dn; + + auto attr = nam_dn.get_attributes(); + bool ret = true; + size_t trys = 0; + + for(const auto& c: my_dn.dn_info()) + { + auto i = attr.equal_range(c.first); + + if(i.first != i.second) + { + trys += 1; + ret = ret && (i.first->second == c.second.value()); + } + } + + return trys > 0 && ret; + } + +bool GeneralName::matches_ip(const std::string& nam) const + { + uint32_t ip = string_to_ipv4(nam); + std::vector<std::string> p = split_on(name(), '/'); + + if(p.size() != 2) + throw Decoding_Error("failed to parse IPv4 address"); + + uint32_t net = string_to_ipv4(p.at(0)); + uint32_t mask = string_to_ipv4(p.at(1)); + + return (ip & mask) == net; + } + +std::ostream& operator<<(std::ostream& os, const GeneralName& gn) + { + os << gn.type() << ":" << gn.name(); + return os; + } + +GeneralSubtree::GeneralSubtree(const std::string& str) : GeneralSubtree() + { + size_t p0, p1; + const auto min = std::stoull(str, &p0, 10); + const auto max = std::stoull(str.substr(p0 + 1), &p1, 10); + GeneralName gn(str.substr(p0 + p1 + 2)); + + if(p0 > 0 && p1 > 0) + { + m_minimum = static_cast<size_t>(min); + m_maximum = static_cast<size_t>(max); + m_base = gn; + } + else + { + throw Invalid_Argument("Failed to decode Name Constraint"); + } + } + +void GeneralSubtree::encode_into(DER_Encoder&) const + { + throw Not_Implemented("General Subtree encoding"); + } + +void GeneralSubtree::decode_from(class BER_Decoder& ber) + { + ber.start_cons(SEQUENCE) + .decode(m_base) + .decode_optional(m_minimum,ASN1_Tag(0), CONTEXT_SPECIFIC,size_t(0)) + .end_cons(); + + if(m_minimum != 0) + throw Decoding_Error("GeneralSubtree minimum must be 0"); + + m_maximum = std::numeric_limits<std::size_t>::max(); + } + +std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) + { + os << gs.minimum() << "," << gs.maximum() << "," << gs.base(); + return os; + } +} diff --git a/comm/third_party/botan/src/lib/x509/name_constraint.h b/comm/third_party/botan/src/lib/x509/name_constraint.h new file mode 100644 index 0000000000..4f56f89eda --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/name_constraint.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_NAME_CONSTRAINT_H_ +#define BOTAN_NAME_CONSTRAINT_H_ + +#include <botan/pkix_types.h> +BOTAN_DEPRECATED_HEADER(name_constraint.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/ocsp.cpp b/comm/third_party/botan/src/lib/x509/ocsp.cpp new file mode 100644 index 0000000000..1ca8232634 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/ocsp.cpp @@ -0,0 +1,363 @@ +/* +* OCSP +* (C) 2012,2013 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/ocsp.h> +#include <botan/certstor.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/x509_ext.h> +#include <botan/oids.h> +#include <botan/base64.h> +#include <botan/pubkey.h> +#include <botan/parsing.h> + +#if defined(BOTAN_HAS_HTTP_UTIL) + #include <botan/http_util.h> +#endif + +namespace Botan { + +namespace OCSP { + +namespace { + +// TODO: should this be in a header somewhere? +void decode_optional_list(BER_Decoder& ber, + ASN1_Tag tag, + std::vector<X509_Certificate>& output) + { + BER_Object obj = ber.get_next_object(); + + if(obj.is_a(tag, ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED)) == false) + { + ber.push_back(obj); + return; + } + + BER_Decoder list(obj); + + while(list.more_items()) + { + BER_Object certbits = list.get_next_object(); + X509_Certificate cert(certbits.bits(), certbits.length()); + output.push_back(std::move(cert)); + } + } + +} + +Request::Request(const X509_Certificate& issuer_cert, + const X509_Certificate& subject_cert) : + m_issuer(issuer_cert), + m_certid(m_issuer, BigInt::decode(subject_cert.serial_number())) + { + if(subject_cert.issuer_dn() != issuer_cert.subject_dn()) + throw Invalid_Argument("Invalid cert pair to OCSP::Request (mismatched issuer,subject args?)"); + } + +Request::Request(const X509_Certificate& issuer_cert, + const BigInt& subject_serial) : + m_issuer(issuer_cert), + m_certid(m_issuer, subject_serial) + { + } + +std::vector<uint8_t> Request::BER_encode() const + { + std::vector<uint8_t> output; + DER_Encoder(output).start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .start_explicit(0) + .encode(static_cast<size_t>(0)) // version # + .end_explicit() + .start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .encode(m_certid) + .end_cons() + .end_cons() + .end_cons() + .end_cons(); + + return output; + } + +std::string Request::base64_encode() const + { + return Botan::base64_encode(BER_encode()); + } + +Response::Response(Certificate_Status_Code status) + { + m_status = Response_Status_Code::Successful; + m_dummy_response_status = status; + } + +Response::Response(const uint8_t response_bits[], size_t response_bits_len) : + m_response_bits(response_bits, response_bits + response_bits_len) + { + m_dummy_response_status = Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + BER_Decoder response_outer = BER_Decoder(m_response_bits).start_cons(SEQUENCE); + + size_t resp_status = 0; + + response_outer.decode(resp_status, ENUMERATED, UNIVERSAL); + + m_status = static_cast<Response_Status_Code>(resp_status); + + if(m_status != Response_Status_Code::Successful) + { return; } + + if(response_outer.more_items()) + { + BER_Decoder response_bytes = + response_outer.start_cons(ASN1_Tag(0), CONTEXT_SPECIFIC).start_cons(SEQUENCE); + + response_bytes.decode_and_check(OID("1.3.6.1.5.5.7.48.1.1"), + "Unknown response type in OCSP response"); + + BER_Decoder basicresponse = + BER_Decoder(response_bytes.get_next_octet_string()).start_cons(SEQUENCE); + + basicresponse.start_cons(SEQUENCE) + .raw_bytes(m_tbs_bits) + .end_cons() + .decode(m_sig_algo) + .decode(m_signature, BIT_STRING); + decode_optional_list(basicresponse, ASN1_Tag(0), m_certs); + + size_t responsedata_version = 0; + Extensions extensions; + + BER_Decoder(m_tbs_bits) + .decode_optional(responsedata_version, ASN1_Tag(0), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode_optional(m_signer_name, ASN1_Tag(1), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode_optional_string(m_key_hash, OCTET_STRING, 2, + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode(m_produced_at) + + .decode_list(m_responses) + + .decode_optional(extensions, ASN1_Tag(1), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + } + + response_outer.end_cons(); + } + +Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer) const + { + if (m_responses.empty()) + return m_dummy_response_status; + + try + { + std::unique_ptr<Public_Key> pub_key(issuer.subject_public_key()); + + const std::vector<std::string> sig_info = + split_on(m_sig_algo.get_oid().to_formatted_string(), '/'); + + if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name()) + return Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + std::string padding = sig_info[1]; + const Signature_Format format = pub_key->default_x509_signature_format(); + + PK_Verifier verifier(*pub_key, padding, format); + + if(verifier.verify_message(ASN1::put_in_sequence(m_tbs_bits), m_signature)) + return Certificate_Status_Code::OCSP_SIGNATURE_OK; + else + return Certificate_Status_Code::OCSP_SIGNATURE_ERROR; + } + catch(Exception&) + { + return Certificate_Status_Code::OCSP_SIGNATURE_ERROR; + } + } + +Certificate_Status_Code Response::check_signature(const std::vector<Certificate_Store*>& trusted_roots, + const std::vector<std::shared_ptr<const X509_Certificate>>& ee_cert_path) const + { + if (m_responses.empty()) + return m_dummy_response_status; + + std::shared_ptr<const X509_Certificate> signing_cert; + + for(size_t i = 0; i != trusted_roots.size(); ++i) + { + if(m_signer_name.empty() && m_key_hash.empty()) + return Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + if(!m_signer_name.empty()) + { + signing_cert = trusted_roots[i]->find_cert(m_signer_name, std::vector<uint8_t>()); + if(signing_cert) + { + break; + } + } + + if(m_key_hash.size() > 0) + { + signing_cert = trusted_roots[i]->find_cert_by_pubkey_sha1(m_key_hash); + if(signing_cert) + { + break; + } + } + } + + if(!signing_cert && ee_cert_path.size() > 1) + { + // End entity cert is not allowed to sign their own OCSP request :) + for(size_t i = 1; i < ee_cert_path.size(); ++i) + { + // Check all CA certificates in the (assumed validated) EE cert path + if(!m_signer_name.empty() && ee_cert_path[i]->subject_dn() == m_signer_name) + { + signing_cert = ee_cert_path[i]; + break; + } + + if(m_key_hash.size() > 0 && ee_cert_path[i]->subject_public_key_bitstring_sha1() == m_key_hash) + { + signing_cert = ee_cert_path[i]; + break; + } + } + } + + if(!signing_cert && m_certs.size() > 0) + { + for(size_t i = 0; i < m_certs.size(); ++i) + { + // Check all CA certificates in the (assumed validated) EE cert path + if(!m_signer_name.empty() && m_certs[i].subject_dn() == m_signer_name) + { + signing_cert = std::make_shared<const X509_Certificate>(m_certs[i]); + break; + } + + if(m_key_hash.size() > 0 && m_certs[i].subject_public_key_bitstring_sha1() == m_key_hash) + { + signing_cert = std::make_shared<const X509_Certificate>(m_certs[i]); + break; + } + } + } + + if(!signing_cert) + return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND; + + if(!signing_cert->allowed_usage(CRL_SIGN) && + !signing_cert->allowed_extended_usage("PKIX.OCSPSigning")) + { + return Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE; + } + + return this->verify_signature(*signing_cert); + } + +Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, + const X509_Certificate& subject, + std::chrono::system_clock::time_point ref_time, + std::chrono::seconds max_age) const + { + if(m_responses.empty()) + { return m_dummy_response_status; } + + for(const auto& response : m_responses) + { + if(response.certid().is_id_for(issuer, subject)) + { + X509_Time x509_ref_time(ref_time); + + if(response.cert_status() == 1) + { return Certificate_Status_Code::CERT_IS_REVOKED; } + + if(response.this_update() > x509_ref_time) + { return Certificate_Status_Code::OCSP_NOT_YET_VALID; } + + if(response.next_update().time_is_set()) + { + if(x509_ref_time > response.next_update()) + { return Certificate_Status_Code::OCSP_HAS_EXPIRED; } + } + else if(max_age > std::chrono::seconds::zero() && ref_time - response.this_update().to_std_timepoint() > max_age) + { return Certificate_Status_Code::OCSP_IS_TOO_OLD; } + + if(response.cert_status() == 0) + { return Certificate_Status_Code::OCSP_RESPONSE_GOOD; } + else + { return Certificate_Status_Code::OCSP_BAD_STATUS; } + } + } + + return Certificate_Status_Code::OCSP_CERT_NOT_LISTED; + } + +#if defined(BOTAN_HAS_HTTP_UTIL) + +Response online_check(const X509_Certificate& issuer, + const BigInt& subject_serial, + const std::string& ocsp_responder, + Certificate_Store* trusted_roots, + std::chrono::milliseconds timeout) + { + if(ocsp_responder.empty()) + throw Invalid_Argument("No OCSP responder specified"); + + OCSP::Request req(issuer, subject_serial); + + auto http = HTTP::POST_sync(ocsp_responder, + "application/ocsp-request", + req.BER_encode(), + 1, + timeout); + + http.throw_unless_ok(); + + // Check the MIME type? + + OCSP::Response response(http.body()); + + std::vector<Certificate_Store*> trusted_roots_vec; + trusted_roots_vec.push_back(trusted_roots); + + if(trusted_roots) + response.check_signature(trusted_roots_vec); + + return response; + } + + +Response online_check(const X509_Certificate& issuer, + const X509_Certificate& subject, + Certificate_Store* trusted_roots, + std::chrono::milliseconds timeout) + { + if(subject.issuer_dn() != issuer.subject_dn()) + throw Invalid_Argument("Invalid cert pair to OCSP::online_check (mismatched issuer,subject args?)"); + + return online_check(issuer, + BigInt::decode(subject.serial_number()), + subject.ocsp_responder(), + trusted_roots, + timeout); + } + +#endif + +} + +} diff --git a/comm/third_party/botan/src/lib/x509/ocsp.h b/comm/third_party/botan/src/lib/x509/ocsp.h new file mode 100644 index 0000000000..5522456442 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/ocsp.h @@ -0,0 +1,282 @@ +/* +* OCSP +* (C) 2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_OCSP_H_ +#define BOTAN_OCSP_H_ + +#include <botan/asn1_obj.h> +#include <botan/pkix_types.h> +#include <botan/x509cert.h> +#include <botan/bigint.h> +#include <chrono> + +namespace Botan { + +class Certificate_Store; + +namespace OCSP { + +class BOTAN_PUBLIC_API(2,0) CertID final : public ASN1_Object + { + public: + CertID() = default; + + CertID(const X509_Certificate& issuer, + const BigInt& subject_serial); + + bool is_id_for(const X509_Certificate& issuer, + const X509_Certificate& subject) const; + + void encode_into(class DER_Encoder& to) const override; + + void decode_from(class BER_Decoder& from) override; + + const std::vector<uint8_t>& issuer_key_hash() const { return m_issuer_key_hash; } + + private: + AlgorithmIdentifier m_hash_id; + std::vector<uint8_t> m_issuer_dn_hash; + std::vector<uint8_t> m_issuer_key_hash; + BigInt m_subject_serial; + }; + +class BOTAN_PUBLIC_API(2,0) SingleResponse final : public ASN1_Object + { + public: + const CertID& certid() const { return m_certid; } + + size_t cert_status() const { return m_cert_status; } + + X509_Time this_update() const { return m_thisupdate; } + + X509_Time next_update() const { return m_nextupdate; } + + void encode_into(class DER_Encoder& to) const override; + + void decode_from(class BER_Decoder& from) override; + private: + CertID m_certid; + size_t m_cert_status = 2; // unknown + X509_Time m_thisupdate; + X509_Time m_nextupdate; + }; + +/** +* An OCSP request. +*/ +class BOTAN_PUBLIC_API(2,0) Request final + { + public: + /** + * Create an OCSP request. + * @param issuer_cert issuer certificate + * @param subject_cert subject certificate + */ + Request(const X509_Certificate& issuer_cert, + const X509_Certificate& subject_cert); + + Request(const X509_Certificate& issuer_cert, + const BigInt& subject_serial); + + /** + * @return BER-encoded OCSP request + */ + std::vector<uint8_t> BER_encode() const; + + /** + * @return Base64-encoded OCSP request + */ + std::string base64_encode() const; + + /** + * @return issuer certificate + */ + const X509_Certificate& issuer() const { return m_issuer; } + + /** + * @return subject certificate + */ + const X509_Certificate& subject() const { throw Not_Implemented("Method have been deprecated"); } + + const std::vector<uint8_t>& issuer_key_hash() const + { return m_certid.issuer_key_hash(); } + private: + X509_Certificate m_issuer; + CertID m_certid; + }; + +/** +* OCSP response status. +* +* see https://tools.ietf.org/html/rfc6960#section-4.2.1 +*/ +enum class Response_Status_Code { + Successful = 0, + Malformed_Request = 1, + Internal_Error = 2, + Try_Later = 3, + Sig_Required = 5, + Unauthorized = 6 +}; + +/** +* OCSP response. +* +* Note this class is only usable as an OCSP client +*/ +class BOTAN_PUBLIC_API(2,0) Response final + { + public: + /** + * Creates an empty OCSP response. + */ + Response() = default; + + /** + * Create a fake OCSP response from a given status code. + * @param status the status code the check functions will return + */ + Response(Certificate_Status_Code status); + + /** + * Parses an OCSP response. + * @param response_bits response bits received + */ + Response(const std::vector<uint8_t>& response_bits) : + Response(response_bits.data(), response_bits.size()) + {} + + /** + * Parses an OCSP response. + * @param response_bits response bits received + * @param response_bits_len length of response in bytes + */ + Response(const uint8_t response_bits[], + size_t response_bits_len); + + /** + * Check signature and return status + * The optional cert_path is the (already validated!) certificate path of + * the end entity which is being inquired about + * @param trust_roots list of certstores containing trusted roots + * @param cert_path optionally, the (already verified!) certificate path for the certificate + * this is an OCSP response for. This is necessary to find the correct intermediate CA in + * some cases. + */ + Certificate_Status_Code check_signature(const std::vector<Certificate_Store*>& trust_roots, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path = {}) const; + + /** + * Verify that issuer's key signed this response + * @param issuer certificate of issuer + * @return if signature valid OCSP_SIGNATURE_OK else an error code + */ + Certificate_Status_Code verify_signature(const X509_Certificate& issuer) const; + + /** + * @return the status of the response + */ + Response_Status_Code status() const { return m_status; } + + /** + * @return the time this OCSP response was supposedly produced at + */ + const X509_Time& produced_at() const { return m_produced_at; } + + /** + * @return DN of signer, if provided in response (may be empty) + */ + const X509_DN& signer_name() const { return m_signer_name; } + + /** + * @return key hash, if provided in response (may be empty) + */ + const std::vector<uint8_t>& signer_key_hash() const { return m_key_hash; } + + const std::vector<uint8_t>& raw_bits() const { return m_response_bits; } + + /** + * Searches the OCSP response for issuer and subject certificate. + * @param issuer issuer certificate + * @param subject subject certificate + * @param ref_time the reference time + * @param max_age the maximum age the response should be considered valid + * if next_update is not set + * @return OCSP status code, possible values: + * CERT_IS_REVOKED, + * OCSP_NOT_YET_VALID, + * OCSP_HAS_EXPIRED, + * OCSP_IS_TOO_OLD, + * OCSP_RESPONSE_GOOD, + * OCSP_BAD_STATUS, + * OCSP_CERT_NOT_LISTED + */ + Certificate_Status_Code status_for(const X509_Certificate& issuer, + const X509_Certificate& subject, + std::chrono::system_clock::time_point ref_time = std::chrono::system_clock::now(), + std::chrono::seconds max_age = std::chrono::seconds::zero()) const; + + /** + * @return the certificate chain, if provided in response + */ + const std::vector<X509_Certificate> &certificates() const { return m_certs; } + + private: + Response_Status_Code m_status; + std::vector<uint8_t> m_response_bits; + X509_Time m_produced_at; + X509_DN m_signer_name; + std::vector<uint8_t> m_key_hash; + std::vector<uint8_t> m_tbs_bits; + AlgorithmIdentifier m_sig_algo; + std::vector<uint8_t> m_signature; + std::vector<X509_Certificate> m_certs; + + std::vector<SingleResponse> m_responses; + + Certificate_Status_Code m_dummy_response_status; + }; + +#if defined(BOTAN_HAS_HTTP_UTIL) + +/** +* Makes an online OCSP request via HTTP and returns the OCSP response. +* @param issuer issuer certificate +* @param subject_serial the subject's serial number +* @param ocsp_responder the OCSP responder to query +* @param trusted_roots trusted roots for the OCSP response +* @param timeout a timeout on the HTTP request +* @return OCSP response +*/ +BOTAN_PUBLIC_API(2,1) +Response online_check(const X509_Certificate& issuer, + const BigInt& subject_serial, + const std::string& ocsp_responder, + Certificate_Store* trusted_roots, + std::chrono::milliseconds timeout = std::chrono::milliseconds(3000)); + +/** +* Makes an online OCSP request via HTTP and returns the OCSP response. +* @param issuer issuer certificate +* @param subject subject certificate +* @param trusted_roots trusted roots for the OCSP response +* @param timeout a timeout on the HTTP request +* @return OCSP response +*/ +BOTAN_PUBLIC_API(2,0) +Response online_check(const X509_Certificate& issuer, + const X509_Certificate& subject, + Certificate_Store* trusted_roots, + std::chrono::milliseconds timeout = std::chrono::milliseconds(3000)); + +#endif + +} + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/ocsp_types.cpp b/comm/third_party/botan/src/lib/x509/ocsp_types.cpp new file mode 100644 index 0000000000..c4b78112eb --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/ocsp_types.cpp @@ -0,0 +1,105 @@ +/* +* OCSP subtypes +* (C) 2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/ocsp.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/x509_ext.h> +#include <botan/hash.h> + +namespace Botan { + +namespace OCSP { + +CertID::CertID(const X509_Certificate& issuer, + const BigInt& subject_serial) + { + /* + In practice it seems some responders, including, notably, + ocsp.verisign.com, will reject anything but SHA-1 here + */ + std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw("SHA-160")); + + m_hash_id = AlgorithmIdentifier(hash->name(), AlgorithmIdentifier::USE_NULL_PARAM); + m_issuer_key_hash = unlock(hash->process(issuer.subject_public_key_bitstring())); + m_issuer_dn_hash = unlock(hash->process(issuer.raw_subject_dn())); + m_subject_serial = subject_serial; + } + +bool CertID::is_id_for(const X509_Certificate& issuer, + const X509_Certificate& subject) const + { + try + { + if(BigInt::decode(subject.serial_number()) != m_subject_serial) + return false; + + const std::string hash_algo = m_hash_id.get_oid().to_formatted_string(); + std::unique_ptr<HashFunction> hash = HashFunction::create_or_throw(hash_algo); + + if(m_issuer_dn_hash != unlock(hash->process(subject.raw_issuer_dn()))) + return false; + + if(m_issuer_key_hash != unlock(hash->process(issuer.subject_public_key_bitstring()))) + return false; + } + catch(...) + { + return false; + } + + return true; + } + +void CertID::encode_into(class DER_Encoder& to) const + { + to.start_cons(SEQUENCE) + .encode(m_hash_id) + .encode(m_issuer_dn_hash, OCTET_STRING) + .encode(m_issuer_key_hash, OCTET_STRING) + .encode(m_subject_serial) + .end_cons(); + } + +void CertID::decode_from(class BER_Decoder& from) + { + from.start_cons(SEQUENCE) + .decode(m_hash_id) + .decode(m_issuer_dn_hash, OCTET_STRING) + .decode(m_issuer_key_hash, OCTET_STRING) + .decode(m_subject_serial) + .end_cons(); + + } + +void SingleResponse::encode_into(class DER_Encoder&) const + { + throw Not_Implemented("SingleResponse::encode_into"); + } + +void SingleResponse::decode_from(class BER_Decoder& from) + { + BER_Object cert_status; + Extensions extensions; + + from.start_cons(SEQUENCE) + .decode(m_certid) + .get_next(cert_status) + .decode(m_thisupdate) + .decode_optional(m_nextupdate, ASN1_Tag(0), + ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED)) + .decode_optional(extensions, + ASN1_Tag(1), + ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED)) + .end_cons(); + + m_cert_status = cert_status.type(); + } + +} + +} diff --git a/comm/third_party/botan/src/lib/x509/ocsp_types.h b/comm/third_party/botan/src/lib/x509/ocsp_types.h new file mode 100644 index 0000000000..aa628d8d60 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/ocsp_types.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_OCSP_TYPES_H_ +#define BOTAN_OCSP_TYPES_H_ + +#include <botan/ocsp.h> +BOTAN_DEPRECATED_HEADER(ocsp_types.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/pkcs10.cpp b/comm/third_party/botan/src/lib/x509/pkcs10.cpp new file mode 100644 index 0000000000..1da5ecca48 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/pkcs10.cpp @@ -0,0 +1,304 @@ +/* +* PKCS #10 +* (C) 1999-2007,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkcs10.h> +#include <botan/x509_key.h> +#include <botan/x509_ext.h> +#include <botan/x509cert.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> +#include <botan/pubkey.h> +#include <botan/oids.h> +#include <botan/pem.h> + +namespace Botan { + +struct PKCS10_Data + { + X509_DN m_subject_dn; + std::vector<uint8_t> m_public_key_bits; + AlternativeName m_alt_name; + std::string m_challenge; + Extensions m_extensions; + }; + +std::string PKCS10_Request::PEM_label() const + { + return "CERTIFICATE REQUEST"; + } + +std::vector<std::string> PKCS10_Request::alternate_PEM_labels() const + { + return { "NEW CERTIFICATE REQUEST" }; + } + +PKCS10_Request::PKCS10_Request(DataSource& src) + { + load_data(src); + } + +PKCS10_Request::PKCS10_Request(const std::vector<uint8_t>& vec) + { + DataSource_Memory src(vec.data(), vec.size()); + load_data(src); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +PKCS10_Request::PKCS10_Request(const std::string& fsname) + { + DataSource_Stream src(fsname, true); + load_data(src); + } +#endif + +//static +PKCS10_Request PKCS10_Request::create(const Private_Key& key, + const X509_DN& subject_dn, + const Extensions& extensions, + const std::string& hash_fn, + RandomNumberGenerator& rng, + const std::string& padding_scheme, + const std::string& challenge) + { + AlgorithmIdentifier sig_algo; + std::unique_ptr<PK_Signer> signer = choose_sig_format(sig_algo, key, rng, hash_fn, padding_scheme); + + const size_t PKCS10_VERSION = 0; + + DER_Encoder tbs_req; + + tbs_req.start_cons(SEQUENCE) + .encode(PKCS10_VERSION) + .encode(subject_dn) + .raw_bytes(key.subject_public_key()) + .start_explicit(0); + + if(challenge.empty() == false) + { + std::vector<uint8_t> value; + DER_Encoder(value).encode(ASN1_String(challenge, DIRECTORY_STRING)); + tbs_req.encode(Attribute("PKCS9.ChallengePassword", value)); + } + + std::vector<uint8_t> extension_req; + DER_Encoder(extension_req).start_cons(SEQUENCE).encode(extensions).end_cons(); + tbs_req.encode(Attribute("PKCS9.ExtensionRequest", extension_req)); + + // end the start_explicit above + tbs_req.end_explicit().end_cons(); + + const std::vector<uint8_t> req = + X509_Object::make_signed(signer.get(), rng, sig_algo, + tbs_req.get_contents()); + + return PKCS10_Request(req); + } + +/* +* Decode the CertificateRequestInfo +*/ +namespace { + +std::unique_ptr<PKCS10_Data> decode_pkcs10(const std::vector<uint8_t>& body) + { + std::unique_ptr<PKCS10_Data> data(new PKCS10_Data); + + BER_Decoder cert_req_info(body); + + size_t version; + cert_req_info.decode(version); + if(version != 0) + throw Decoding_Error("Unknown version code in PKCS #10 request: " + + std::to_string(version)); + + cert_req_info.decode(data->m_subject_dn); + + BER_Object public_key = cert_req_info.get_next_object(); + if(public_key.is_a(SEQUENCE, CONSTRUCTED) == false) + throw BER_Bad_Tag("PKCS10_Request: Unexpected tag for public key", public_key.tagging()); + + data->m_public_key_bits = ASN1::put_in_sequence(public_key.bits(), public_key.length()); + + BER_Object attr_bits = cert_req_info.get_next_object(); + + std::set<std::string> pkcs9_email; + + if(attr_bits.is_a(0, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) + { + BER_Decoder attributes(attr_bits); + while(attributes.more_items()) + { + Attribute attr; + attributes.decode(attr); + + const OID& oid = attr.get_oid(); + BER_Decoder value(attr.get_parameters()); + + if(oid == OID::from_string("PKCS9.EmailAddress")) + { + ASN1_String email; + value.decode(email); + pkcs9_email.insert(email.value()); + } + else if(oid == OID::from_string("PKCS9.ChallengePassword")) + { + ASN1_String challenge_password; + value.decode(challenge_password); + data->m_challenge = challenge_password.value(); + } + else if(oid == OID::from_string("PKCS9.ExtensionRequest")) + { + value.decode(data->m_extensions).verify_end(); + } + } + attributes.verify_end(); + } + else if(attr_bits.is_set()) + throw BER_Bad_Tag("PKCS10_Request: Unexpected tag for attributes", attr_bits.tagging()); + + cert_req_info.verify_end(); + + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::Subject_Alternative_Name>()) + { + data->m_alt_name = ext->get_alt_name(); + } + + for(std::string email : pkcs9_email) + { + data->m_alt_name.add_attribute("RFC882", email); + } + + return data; + } + +} + +void PKCS10_Request::force_decode() + { + m_data.reset(); + + std::unique_ptr<PKCS10_Data> data = decode_pkcs10(signed_body()); + + m_data.reset(data.release()); + + if(!this->check_signature(subject_public_key())) + throw Decoding_Error("PKCS #10 request: Bad signature detected"); + } + +const PKCS10_Data& PKCS10_Request::data() const + { + if(m_data == nullptr) + throw Decoding_Error("PKCS10_Request decoding failed"); + return *m_data.get(); + } + +/* +* Return the challenge password (if any) +*/ +std::string PKCS10_Request::challenge_password() const + { + return data().m_challenge; + } + +/* +* Return the name of the requestor +*/ +const X509_DN& PKCS10_Request::subject_dn() const + { + return data().m_subject_dn; + } + +/* +* Return the public key of the requestor +*/ +const std::vector<uint8_t>& PKCS10_Request::raw_public_key() const + { + return data().m_public_key_bits; + } + +/* +* Return the public key of the requestor +*/ +Public_Key* PKCS10_Request::subject_public_key() const + { + DataSource_Memory source(raw_public_key()); + return X509::load_key(source); + } + +/* +* Return the alternative names of the requestor +*/ +const AlternativeName& PKCS10_Request::subject_alt_name() const + { + return data().m_alt_name; + } + +/* +* Return the X509v3 extensions +*/ +const Extensions& PKCS10_Request::extensions() const + { + return data().m_extensions; + } + +/* +* Return the key constraints (if any) +*/ +Key_Constraints PKCS10_Request::constraints() const + { + if(auto ext = extensions().get(OID::from_string("X509v3.KeyUsage"))) + { + return dynamic_cast<Cert_Extension::Key_Usage&>(*ext).get_constraints(); + } + + return NO_CONSTRAINTS; + } + +/* +* Return the extendend key constraints (if any) +*/ +std::vector<OID> PKCS10_Request::ex_constraints() const + { + if(auto ext = extensions().get(OID::from_string("X509v3.ExtendedKeyUsage"))) + { + return dynamic_cast<Cert_Extension::Extended_Key_Usage&>(*ext).get_oids(); + } + + return {}; + } + +/* +* Return is a CA certificate is requested +*/ +bool PKCS10_Request::is_CA() const + { + if(auto ext = extensions().get(OID::from_string("X509v3.BasicConstraints"))) + { + return dynamic_cast<Cert_Extension::Basic_Constraints&>(*ext).get_is_ca(); + } + + return false; + } + +/* +* Return the desired path limit (if any) +*/ +size_t PKCS10_Request::path_limit() const + { + if(auto ext = extensions().get(OID::from_string("X509v3.BasicConstraints"))) + { + Cert_Extension::Basic_Constraints& basic_constraints = dynamic_cast<Cert_Extension::Basic_Constraints&>(*ext); + if(basic_constraints.get_is_ca()) + { + return basic_constraints.get_path_limit(); + } + } + + return 0; + } + +} diff --git a/comm/third_party/botan/src/lib/x509/pkcs10.h b/comm/third_party/botan/src/lib/x509/pkcs10.h new file mode 100644 index 0000000000..3f3f87357d --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/pkcs10.h @@ -0,0 +1,148 @@ +/* +* PKCS #10 +* (C) 1999-2007 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PKCS10_H_ +#define BOTAN_PKCS10_H_ + +#include <botan/x509_obj.h> +#include <botan/pkix_enums.h> +#include <vector> + +namespace Botan { + +struct PKCS10_Data; + +class Private_Key; +class Extensions; +class X509_DN; +class AlternativeName; + +/** +* PKCS #10 Certificate Request. +*/ +class BOTAN_PUBLIC_API(2,0) PKCS10_Request final : public X509_Object + { + public: + /** + * Get the subject public key. + * @return subject public key + */ + Public_Key* subject_public_key() const; + + /** + * Get the raw DER encoded public key. + * @return raw DER encoded public key + */ + const std::vector<uint8_t>& raw_public_key() const; + + /** + * Get the subject DN. + * @return subject DN + */ + const X509_DN& subject_dn() const; + + /** + * Get the subject alternative name. + * @return subject alternative name. + */ + const AlternativeName& subject_alt_name() const; + + /** + * Get the key constraints for the key associated with this + * PKCS#10 object. + * @return key constraints + */ + Key_Constraints constraints() const; + + /** + * Get the extendend key constraints (if any). + * @return extended key constraints + */ + std::vector<OID> ex_constraints() const; + + /** + * Find out whether this is a CA request. + * @result true if it is a CA request, false otherwise. + */ + bool is_CA() const; + + /** + * Return the constraint on the path length defined + * in the BasicConstraints extension. + * @return path limit + */ + size_t path_limit() const; + + /** + * Get the challenge password for this request + * @return challenge password for this request + */ + std::string challenge_password() const; + + /** + * Get the X509v3 extensions. + * @return X509v3 extensions + */ + const Extensions& extensions() const; + + /** + * Create a PKCS#10 Request from a data source. + * @param source the data source providing the DER encoded request + */ + explicit PKCS10_Request(DataSource& source); + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + /** + * Create a PKCS#10 Request from a file. + * @param filename the name of the file containing the DER or PEM + * encoded request file + */ + explicit PKCS10_Request(const std::string& filename); +#endif + + /** + * Create a PKCS#10 Request from binary data. + * @param vec a std::vector containing the DER value + */ + explicit PKCS10_Request(const std::vector<uint8_t>& vec); + + /** + * Create a new PKCS10 certificate request + * @param key the key that will be included in the certificate request + * @param subject_dn the DN to be placed in the request + * @param extensions extensions to include in the request + * @param hash_fn the hash function to use to create the signature + * @param rng a random number generator + * @param padding_scheme if set specifies the padding scheme, otherwise an + * algorithm-specific default is used. + * @param challenge a challenge string to be included in the PKCS10 request, + * sometimes used for revocation purposes. + */ + static PKCS10_Request create(const Private_Key& key, + const X509_DN& subject_dn, + const Extensions& extensions, + const std::string& hash_fn, + RandomNumberGenerator& rng, + const std::string& padding_scheme = "", + const std::string& challenge = ""); + + private: + std::string PEM_label() const override; + + std::vector<std::string> alternate_PEM_labels() const override; + + void force_decode() override; + + const PKCS10_Data& data() const; + + std::shared_ptr<PKCS10_Data> m_data; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/pkix_enums.h b/comm/third_party/botan/src/lib/x509/pkix_enums.h new file mode 100644 index 0000000000..a1c85293b8 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/pkix_enums.h @@ -0,0 +1,143 @@ +/* +* (C) 2013 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_PKIX_ENUMS_H_ +#define BOTAN_X509_PKIX_ENUMS_H_ + +#include <botan/types.h> + +namespace Botan { + +/** +* Certificate validation status code +*/ +enum class Certificate_Status_Code { + OK = 0, + VERIFIED = 0, + + // Revocation status + OCSP_RESPONSE_GOOD = 1, + OCSP_SIGNATURE_OK = 2, + VALID_CRL_CHECKED = 3, + OCSP_NO_HTTP = 4, + + // Warnings + FIRST_WARNING_STATUS = 500, + CERT_SERIAL_NEGATIVE = 500, + DN_TOO_LONG = 501, + OCSP_NO_REVOCATION_URL = 502, + OCSP_SERVER_NOT_AVAILABLE = 503, + + // Typo versions of above - will be removed in future major release + OSCP_NO_REVOCATION_URL = 502, + OSCP_SERVER_NOT_AVAILABLE = 503, + + // Errors + FIRST_ERROR_STATUS = 1000, + + SIGNATURE_METHOD_TOO_WEAK = 1000, + UNTRUSTED_HASH = 1001, + NO_REVOCATION_DATA = 1002, + NO_MATCHING_CRLDP = 1003, + + // Time problems + CERT_NOT_YET_VALID = 2000, + CERT_HAS_EXPIRED = 2001, + OCSP_NOT_YET_VALID = 2002, + OCSP_HAS_EXPIRED = 2003, + CRL_NOT_YET_VALID = 2004, + CRL_HAS_EXPIRED = 2005, + OCSP_IS_TOO_OLD = 2006, + + // Chain generation problems + CERT_ISSUER_NOT_FOUND = 3000, + CANNOT_ESTABLISH_TRUST = 3001, + CERT_CHAIN_LOOP = 3002, + CHAIN_LACKS_TRUST_ROOT = 3003, + CHAIN_NAME_MISMATCH = 3004, + + // Validation errors + POLICY_ERROR = 4000, + INVALID_USAGE = 4001, + CERT_CHAIN_TOO_LONG = 4002, + CA_CERT_NOT_FOR_CERT_ISSUER = 4003, + NAME_CONSTRAINT_ERROR = 4004, + + // Revocation errors + CA_CERT_NOT_FOR_CRL_ISSUER = 4005, + OCSP_CERT_NOT_LISTED = 4006, + OCSP_BAD_STATUS = 4007, + + // Other problems + CERT_NAME_NOMATCH = 4008, + UNKNOWN_CRITICAL_EXTENSION = 4009, + DUPLICATE_CERT_EXTENSION = 4010, + OCSP_SIGNATURE_ERROR = 4501, + OCSP_ISSUER_NOT_FOUND = 4502, + OCSP_RESPONSE_MISSING_KEYUSAGE = 4503, + OCSP_RESPONSE_INVALID = 4504, + EXT_IN_V1_V2_CERT = 4505, + DUPLICATE_CERT_POLICY = 4506, + V2_IDENTIFIERS_IN_V1_CERT = 4507, + + // Hard failures + CERT_IS_REVOKED = 5000, + CRL_BAD_SIGNATURE = 5001, + SIGNATURE_ERROR = 5002, + CERT_PUBKEY_INVALID = 5003, + SIGNATURE_ALGO_UNKNOWN = 5004, + SIGNATURE_ALGO_BAD_PARAMS = 5005 +}; + +/** +* Convert a status code to a human readable diagnostic message +* @param code the certifcate status +* @return string literal constant, or nullptr if code unknown +*/ +BOTAN_PUBLIC_API(2,0) const char* to_string(Certificate_Status_Code code); + +/** +* X.509v3 Key Constraints. +* If updating update copy in ffi.h +*/ +enum Key_Constraints { + NO_CONSTRAINTS = 0, + DIGITAL_SIGNATURE = 1 << 15, + NON_REPUDIATION = 1 << 14, + KEY_ENCIPHERMENT = 1 << 13, + DATA_ENCIPHERMENT = 1 << 12, + KEY_AGREEMENT = 1 << 11, + KEY_CERT_SIGN = 1 << 10, + CRL_SIGN = 1 << 9, + ENCIPHER_ONLY = 1 << 8, + DECIPHER_ONLY = 1 << 7 +}; + +/** +* X.509v2 CRL Reason Code. +* This will become an enum class in a future major release +*/ +enum CRL_Code : uint32_t { + UNSPECIFIED = 0, + KEY_COMPROMISE = 1, + CA_COMPROMISE = 2, + AFFILIATION_CHANGED = 3, + SUPERSEDED = 4, + CESSATION_OF_OPERATION = 5, + CERTIFICATE_HOLD = 6, + REMOVE_FROM_CRL = 8, + PRIVLEDGE_WITHDRAWN = 9, + PRIVILEGE_WITHDRAWN = 9, + AA_COMPROMISE = 10, + + DELETE_CRL_ENTRY = 0xFF00, + OCSP_GOOD = 0xFF01, + OCSP_UNKNOWN = 0xFF02 +}; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/pkix_types.h b/comm/third_party/botan/src/lib/x509/pkix_types.h new file mode 100644 index 0000000000..983121955f --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/pkix_types.h @@ -0,0 +1,613 @@ +/* +* (C) 1999-2010,2012,2018,2020 Jack Lloyd +* (C) 2007 Yves Jerschow +* (C) 2015 Kai Michaelis +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PKIX_TYPES_H_ +#define BOTAN_PKIX_TYPES_H_ + +#include <botan/asn1_obj.h> +#include <botan/pkix_enums.h> +#include <vector> +#include <string> +#include <iosfwd> +#include <map> +#include <set> + +namespace Botan { + +class X509_Certificate; +class Data_Store; +class Public_Key; + +/** +* 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 constraints the constraints that shall be enforced on the key +* @throw Invalid_Argument if the given constraints are not permitted for this key +*/ +BOTAN_PUBLIC_API(2,0) void verify_cert_constraints_valid_for_key_type(const Public_Key& pub_key, + Key_Constraints constraints); + +std::string BOTAN_PUBLIC_API(2,0) key_constraints_to_string(Key_Constraints); + +/** +* Distinguished Name +*/ +class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object + { + public: + X509_DN() = default; + + explicit X509_DN(const std::multimap<OID, std::string>& args) + { + for(auto i : args) + add_attribute(i.first, i.second); + } + + explicit X509_DN(const std::multimap<std::string, std::string>& args) + { + for(auto i : args) + add_attribute(i.first, i.second); + } + + void encode_into(DER_Encoder&) const override; + void decode_from(BER_Decoder&) override; + + bool has_field(const OID& oid) const; + ASN1_String get_first_attribute(const OID& oid) const; + + /* + * Return the BER encoded data, if any + */ + const std::vector<uint8_t>& get_bits() const { return m_dn_bits; } + + bool empty() const { return m_rdn.empty(); } + + std::string to_string() const; + + const std::vector<std::pair<OID,ASN1_String>>& dn_info() const { return m_rdn; } + + std::multimap<OID, std::string> get_attributes() const; + std::multimap<std::string, std::string> contents() const; + + bool has_field(const std::string& attr) const; + std::vector<std::string> get_attribute(const std::string& attr) const; + std::string get_first_attribute(const std::string& attr) const; + + void add_attribute(const std::string& key, const std::string& val); + + void add_attribute(const OID& oid, const std::string& val) + { + add_attribute(oid, ASN1_String(val)); + } + + void add_attribute(const OID& oid, const ASN1_String& val); + + static std::string deref_info_field(const std::string& key); + + /** + * Lookup upper bounds in characters for the length of distinguished name fields + * as given in RFC 5280, Appendix A. + * + * @param oid the oid of the DN to lookup + * @return the upper bound, or zero if no ub is known to Botan + */ + static size_t lookup_ub(const OID& oid); + + private: + std::vector<std::pair<OID,ASN1_String>> m_rdn; + std::vector<uint8_t> m_dn_bits; + }; + +bool BOTAN_PUBLIC_API(2,0) operator==(const X509_DN& dn1, const X509_DN& dn2); +bool BOTAN_PUBLIC_API(2,0) operator!=(const X509_DN& dn1, const X509_DN& dn2); + +/* +The ordering here is arbitrary and may change from release to release. +It is intended for allowing DNs as keys in std::map and similiar containers +*/ +bool BOTAN_PUBLIC_API(2,0) operator<(const X509_DN& dn1, const X509_DN& dn2); + +BOTAN_PUBLIC_API(2,0) std::ostream& operator<<(std::ostream& out, const X509_DN& dn); +BOTAN_PUBLIC_API(2,0) std::istream& operator>>(std::istream& in, X509_DN& dn); + +/** +* Alternative Name +*/ +class BOTAN_PUBLIC_API(2,0) AlternativeName final : public ASN1_Object + { + public: + void encode_into(DER_Encoder&) const override; + void decode_from(BER_Decoder&) override; + + std::multimap<std::string, std::string> contents() const; + + bool has_field(const std::string& attr) const; + std::vector<std::string> get_attribute(const std::string& attr) const; + + std::string get_first_attribute(const std::string& attr) const; + + void add_attribute(const std::string& type, const std::string& value); + void add_othername(const OID& oid, const std::string& value, ASN1_Tag type); + + const std::multimap<std::string, std::string>& get_attributes() const + { + return m_alt_info; + } + + const std::multimap<OID, ASN1_String>& get_othernames() const + { + return m_othernames; + } + + X509_DN dn() const; + + bool has_items() const; + + AlternativeName(const std::string& email_addr = "", + const std::string& uri = "", + const std::string& dns = "", + const std::string& ip_address = ""); + private: + std::multimap<std::string, std::string> m_alt_info; + std::multimap<OID, ASN1_String> m_othernames; + }; + +/** +* Attribute +*/ +class BOTAN_PUBLIC_API(2,0) Attribute final : public ASN1_Object + { + public: + void encode_into(DER_Encoder& to) const override; + void decode_from(BER_Decoder& from) override; + + Attribute() = default; + Attribute(const OID&, const std::vector<uint8_t>&); + Attribute(const std::string&, const std::vector<uint8_t>&); + + const OID& get_oid() const { return oid; } + + const std::vector<uint8_t>& get_parameters() const { return parameters; } + + BOTAN_DEPRECATED_PUBLIC_MEMBER_VARIABLES: + /* + * These values are public for historical reasons, but in a future release + * they will be made private. Do not access them. + */ + OID oid; + std::vector<uint8_t> parameters; + }; + +/** +* @brief X.509 GeneralName Type +* +* Handles parsing GeneralName types in their BER and canonical string +* encoding. Allows matching GeneralNames against each other using +* the rules laid out in the RFC 5280, sec. 4.2.1.10 (Name Contraints). +*/ +class BOTAN_PUBLIC_API(2,0) GeneralName final : public ASN1_Object + { + public: + enum MatchResult : int + { + All, + Some, + None, + NotFound, + UnknownType, + }; + + /** + * Creates an empty GeneralName. + */ + GeneralName() = default; + + /** + * Creates a new GeneralName for its string format. + * @param str type and name, colon-separated, e.g., "DNS:google.com" + */ + GeneralName(const std::string& str); + + void encode_into(DER_Encoder&) const override; + + void decode_from(BER_Decoder&) override; + + /** + * @return Type of the name. Can be DN, DNS, IP, RFC822 or URI. + */ + const std::string& type() const { return m_type; } + + /** + * @return The name as string. Format depends on type. + */ + const std::string& name() const { return m_name; } + + /** + * Checks whether a given certificate (partially) matches this name. + * @param cert certificate to be matched + * @return the match result + */ + MatchResult matches(const X509_Certificate& cert) const; + + private: + std::string m_type; + std::string m_name; + + bool matches_dns(const std::string&) const; + bool matches_dn(const std::string&) const; + bool matches_ip(const std::string&) const; + }; + +std::ostream& operator<<(std::ostream& os, const GeneralName& gn); + +/** +* @brief A single Name Constraint +* +* The Name Constraint extension adds a minimum and maximum path +* length to a GeneralName to form a constraint. The length limits +* are currently unused. +*/ +class BOTAN_PUBLIC_API(2,0) GeneralSubtree final : public ASN1_Object + { + public: + /** + * Creates an empty name constraint. + */ + GeneralSubtree() : m_base(), m_minimum(0), m_maximum(std::numeric_limits<std::size_t>::max()) + {} + + /*** + * Creates a new name constraint. + * @param base name + * @param min minimum path length + * @param max maximum path length + */ + GeneralSubtree(const GeneralName& base, size_t min, size_t max) + : m_base(base), m_minimum(min), m_maximum(max) + {} + + /** + * Creates a new name constraint for its string format. + * @param str name constraint + */ + GeneralSubtree(const std::string& str); + + void encode_into(DER_Encoder&) const override; + + void decode_from(BER_Decoder&) override; + + /** + * @return name + */ + const GeneralName& base() const { return m_base; } + + /** + * @return minimum path length + */ + size_t minimum() const { return m_minimum; } + + /** + * @return maximum path length + */ + size_t maximum() const { return m_maximum; } + + private: + GeneralName m_base; + size_t m_minimum; + size_t m_maximum; + }; + +std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs); + +/** +* @brief Name Constraints +* +* Wraps the Name Constraints associated with a certificate. +*/ +class BOTAN_PUBLIC_API(2,0) NameConstraints final + { + public: + /** + * Creates an empty name NameConstraints. + */ + NameConstraints() : m_permitted_subtrees(), m_excluded_subtrees() {} + + /** + * Creates NameConstraints from a list of permitted and excluded subtrees. + * @param permitted_subtrees names for which the certificate is permitted + * @param excluded_subtrees names for which the certificate is not permitted + */ + NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees, + std::vector<GeneralSubtree>&& excluded_subtrees) + : m_permitted_subtrees(permitted_subtrees), m_excluded_subtrees(excluded_subtrees) + {} + + /** + * @return permitted names + */ + const std::vector<GeneralSubtree>& permitted() const { return m_permitted_subtrees; } + + /** + * @return excluded names + */ + const std::vector<GeneralSubtree>& excluded() const { return m_excluded_subtrees; } + + private: + std::vector<GeneralSubtree> m_permitted_subtrees; + std::vector<GeneralSubtree> m_excluded_subtrees; + }; + +/** +* X.509 Certificate Extension +*/ +class BOTAN_PUBLIC_API(2,0) Certificate_Extension + { + public: + /** + * @return OID representing this extension + */ + virtual OID oid_of() const = 0; + + /* + * @return specific OID name + * If possible OIDS table should match oid_name to OIDS, ie + * OID::from_string(ext->oid_name()) == ext->oid_of() + * Should return empty string if OID is not known + */ + virtual std::string oid_name() const = 0; + + /** + * Make a copy of this extension + * @return copy of this + */ + virtual Certificate_Extension* copy() const = 0; + + /* + * Add the contents of this extension into the information + * for the subject and/or issuer, as necessary. + * @param subject the subject info + * @param issuer the issuer info + */ + virtual void contents_to(Data_Store& subject, + Data_Store& issuer) const = 0; + + /* + * Callback visited during path validation. + * + * An extension can implement this callback to inspect + * the path during path validation. + * + * If an error occurs during validation of this extension, + * an appropriate status code shall be added to cert_status. + * + * @param subject Subject certificate that contains this extension + * @param issuer Issuer certificate + * @param status Certificate validation status codes for subject certificate + * @param cert_path Certificate path which is currently validated + * @param pos Position of subject certificate in cert_path + */ + virtual void validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos); + + virtual ~Certificate_Extension() = default; + protected: + friend class Extensions; + virtual bool should_encode() const { return true; } + virtual std::vector<uint8_t> encode_inner() const = 0; + virtual void decode_inner(const std::vector<uint8_t>&) = 0; + }; + +/** +* X.509 Certificate Extension List +*/ +class BOTAN_PUBLIC_API(2,0) Extensions final : public ASN1_Object + { + public: + /** + * Look up an object in the extensions, based on OID Returns + * nullptr if not set, if the extension was either absent or not + * handled. The pointer returned is owned by the Extensions + * object. + * This would be better with an optional<T> return value + */ + const Certificate_Extension* get_extension_object(const OID& oid) const; + + template<typename T> + const T* get_extension_object_as(const OID& oid = T::static_oid()) const + { + if(const Certificate_Extension* extn = get_extension_object(oid)) + { + // Unknown_Extension oid_name is empty + if(extn->oid_name().empty()) + { + return nullptr; + } + else if(const T* extn_as_T = dynamic_cast<const T*>(extn)) + { + return extn_as_T; + } + else + { + throw Decoding_Error("Exception::get_extension_object_as dynamic_cast failed"); + } + } + + return nullptr; + } + + /** + * Return the set of extensions in the order they appeared in the certificate + * (or as they were added, if constructed) + */ + const std::vector<OID>& get_extension_oids() const + { + return m_extension_oids; + } + + /** + * Return true if an extension was set + */ + bool extension_set(const OID& oid) const; + + /** + * Return true if an extesion was set and marked critical + */ + bool critical_extension_set(const OID& oid) const; + + /** + * Return the raw bytes of the extension + * Will throw if OID was not set as an extension. + */ + std::vector<uint8_t> get_extension_bits(const OID& oid) const; + + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + void contents_to(Data_Store&, Data_Store&) const; + + /** + * Adds a new extension to the list. + * @param extn pointer to the certificate extension (Extensions takes ownership) + * @param critical whether this extension should be marked as critical + * @throw Invalid_Argument if the extension is already present in the list + */ + void add(Certificate_Extension* extn, bool critical = false); + + /** + * Adds a new extension to the list unless it already exists. If the extension + * already exists within the Extensions object, the extn pointer will be deleted. + * + * @param extn pointer to the certificate extension (Extensions takes ownership) + * @param critical whether this extension should be marked as critical + * @return true if the object was added false if the extension was already used + */ + bool add_new(Certificate_Extension* extn, bool critical = false); + + /** + * Adds an extension to the list or replaces it. + * @param extn the certificate extension + * @param critical whether this extension should be marked as critical + */ + void replace(Certificate_Extension* extn, bool critical = false); + + /** + * Remove an extension from the list. Returns true if the + * extension had been set, false otherwise. + */ + bool remove(const OID& oid); + + /** + * Searches for an extension by OID and returns the result. + * Only the known extensions types declared in this header + * are searched for by this function. + * @return Copy of extension with oid, nullptr if not found. + * Can avoid creating a copy by using get_extension_object function + */ + std::unique_ptr<Certificate_Extension> get(const OID& oid) const; + + /** + * Searches for an extension by OID and returns the result decoding + * it to some arbitrary extension type chosen by the application. + * + * Only the unknown extensions, that is, extensions types that + * are not declared in this header, are searched for by this + * function. + * + * @return Pointer to new extension with oid, nullptr if not found. + */ + template<typename T> + std::unique_ptr<T> get_raw(const OID& oid) const + { + auto extn_info = m_extension_info.find(oid); + + if(extn_info != m_extension_info.end()) + { + // Unknown_Extension oid_name is empty + if(extn_info->second.obj().oid_name() == "") + { + std::unique_ptr<T> ext(new T); + ext->decode_inner(extn_info->second.bits()); + return ext; + } + } + return nullptr; + } + + /** + * Returns a copy of the list of extensions together with the corresponding + * criticality flag. All extensions are encoded as some object, falling back + * to Unknown_Extension class which simply allows reading the bytes as well + * as the criticality flag. + */ + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const; + + /** + * Returns the list of extensions as raw, encoded bytes + * together with the corresponding criticality flag. + * Contains all extensions, including any extensions encoded as Unknown_Extension + */ + std::map<OID, std::pair<std::vector<uint8_t>, bool>> extensions_raw() const; + + Extensions() {} + + Extensions(const Extensions&) = default; + Extensions& operator=(const Extensions&) = default; + + Extensions(Extensions&&) = default; + Extensions& operator=(Extensions&&) = default; + + private: + static std::unique_ptr<Certificate_Extension> + create_extn_obj(const OID& oid, + bool critical, + const std::vector<uint8_t>& body); + + class Extensions_Info + { + public: + Extensions_Info(bool critical, + Certificate_Extension* ext) : + m_obj(ext), + m_bits(m_obj->encode_inner()), + m_critical(critical) + { + } + + Extensions_Info(bool critical, + const std::vector<uint8_t>& encoding, + Certificate_Extension* ext) : + m_obj(ext), + m_bits(encoding), + m_critical(critical) + { + } + + bool is_critical() const { return m_critical; } + const std::vector<uint8_t>& bits() const { return m_bits; } + const Certificate_Extension& obj() const + { + BOTAN_ASSERT_NONNULL(m_obj.get()); + return *m_obj.get(); + } + + private: + std::shared_ptr<Certificate_Extension> m_obj; + std::vector<uint8_t> m_bits; + bool m_critical = false; + }; + + std::vector<OID> m_extension_oids; + std::map<OID, Extensions_Info> m_extension_info; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509_attribute.cpp b/comm/third_party/botan/src/lib/x509/x509_attribute.cpp new file mode 100644 index 0000000000..035f254c88 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_attribute.cpp @@ -0,0 +1,58 @@ +/* +* Attribute +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_types.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/oids.h> + +namespace Botan { + +/* +* Create an Attribute +*/ +Attribute::Attribute(const OID& attr_oid, const std::vector<uint8_t>& attr_value) : + oid(attr_oid), + parameters(attr_value) + {} + +/* +* Create an Attribute +*/ +Attribute::Attribute(const std::string& attr_oid, + const std::vector<uint8_t>& attr_value) : + oid(OID::from_string(attr_oid)), + parameters(attr_value) + {} + +/* +* DER encode a Attribute +*/ +void Attribute::encode_into(DER_Encoder& codec) const + { + codec.start_cons(SEQUENCE) + .encode(oid) + .start_cons(SET) + .raw_bytes(parameters) + .end_cons() + .end_cons(); + } + +/* +* Decode a BER encoded Attribute +*/ +void Attribute::decode_from(BER_Decoder& codec) + { + codec.start_cons(SEQUENCE) + .decode(oid) + .start_cons(SET) + .raw_bytes(parameters) + .end_cons() + .end_cons(); + } + +} diff --git a/comm/third_party/botan/src/lib/x509/x509_ca.cpp b/comm/third_party/botan/src/lib/x509/x509_ca.cpp new file mode 100644 index 0000000000..542a3a9191 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_ca.cpp @@ -0,0 +1,338 @@ +/* +* X.509 Certificate Authority +* (C) 1999-2010 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_ca.h> +#include <botan/x509_key.h> +#include <botan/x509self.h> +#include <botan/x509_ext.h> +#include <botan/pkix_types.h> +#include <botan/pubkey.h> +#include <botan/der_enc.h> +#include <botan/bigint.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <botan/hash.h> +#include <botan/emsa.h> +#include <botan/scan_name.h> +#include <algorithm> +#include <iterator> + +namespace Botan { + +/* +* Load the certificate and private key +*/ +X509_CA::X509_CA(const X509_Certificate& c, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng) : + m_ca_cert(c), + m_hash_fn(hash_fn) + { + if(!m_ca_cert.is_CA_cert()) + throw Invalid_Argument("X509_CA: This certificate is not for a CA"); + + 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::oid2str_or_throw(c.signature_algorithm().get_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)); + } + +/* +* X509_CA Destructor +*/ +X509_CA::~X509_CA() + { + /* for unique_ptr */ + } + +namespace { + +Extensions choose_extensions(const PKCS10_Request& req, + const X509_Certificate& ca_cert, + const std::string& hash_fn) + { + 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()); + verify_cert_constraints_valid_for_key_type(*key, req.constraints()); + constraints = req.constraints(); + } + + Extensions extensions = req.extensions(); + + extensions.replace( + new Cert_Extension::Basic_Constraints(req.is_CA(), req.path_limit()), + true); + + if(constraints != NO_CONSTRAINTS) + { + extensions.replace(new Cert_Extension::Key_Usage(constraints), true); + } + + extensions.replace(new Cert_Extension::Authority_Key_ID(ca_cert.subject_key_id())); + extensions.replace(new Cert_Extension::Subject_Key_ID(req.raw_public_key(), hash_fn)); + + extensions.replace( + new Cert_Extension::Subject_Alternative_Name(req.subject_alt_name())); + + extensions.replace( + new Cert_Extension::Extended_Key_Usage(req.ex_constraints())); + + return extensions; + } + +} + +X509_Certificate X509_CA::sign_request(const PKCS10_Request& req, + RandomNumberGenerator& rng, + const BigInt& serial_number, + const X509_Time& not_before, + const X509_Time& not_after) const + { + auto extensions = choose_extensions(req, m_ca_cert, m_hash_fn); + + return make_cert(m_signer.get(), rng, serial_number, + m_ca_sig_algo, req.raw_public_key(), + not_before, not_after, + m_ca_cert.subject_dn(), req.subject_dn(), + extensions); + } + +/* +* Sign a PKCS #10 certificate request +*/ +X509_Certificate X509_CA::sign_request(const PKCS10_Request& req, + RandomNumberGenerator& rng, + const X509_Time& not_before, + const X509_Time& not_after) const + { + auto extensions = choose_extensions(req, m_ca_cert, m_hash_fn); + + return make_cert(m_signer.get(), rng, m_ca_sig_algo, + req.raw_public_key(), + not_before, not_after, + m_ca_cert.subject_dn(), req.subject_dn(), + extensions); + } + +X509_Certificate X509_CA::make_cert(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& sig_algo, + const std::vector<uint8_t>& pub_key, + const X509_Time& not_before, + const X509_Time& not_after, + const X509_DN& issuer_dn, + const X509_DN& subject_dn, + const Extensions& extensions) + { + const size_t SERIAL_BITS = 128; + BigInt serial_no(rng, SERIAL_BITS); + + return make_cert(signer, rng, serial_no, sig_algo, pub_key, + not_before, not_after, issuer_dn, subject_dn, extensions); + } + +/* +* Create a new certificate +*/ +X509_Certificate X509_CA::make_cert(PK_Signer* signer, + RandomNumberGenerator& rng, + const BigInt& serial_no, + const AlgorithmIdentifier& sig_algo, + const std::vector<uint8_t>& pub_key, + const X509_Time& not_before, + const X509_Time& not_after, + const X509_DN& issuer_dn, + const X509_DN& subject_dn, + const Extensions& extensions) + { + const size_t X509_CERT_VERSION = 3; + + // clang-format off + return X509_Certificate(X509_Object::make_signed( + signer, rng, sig_algo, + DER_Encoder().start_cons(SEQUENCE) + .start_explicit(0) + .encode(X509_CERT_VERSION-1) + .end_explicit() + + .encode(serial_no) + + .encode(sig_algo) + .encode(issuer_dn) + + .start_cons(SEQUENCE) + .encode(not_before) + .encode(not_after) + .end_cons() + + .encode(subject_dn) + .raw_bytes(pub_key) + + .start_explicit(3) + .start_cons(SEQUENCE) + .encode(extensions) + .end_cons() + .end_explicit() + .end_cons() + .get_contents() + )); + // clang-format on + } + +/* +* Create a new, empty CRL +*/ +X509_CRL X509_CA::new_crl(RandomNumberGenerator& rng, + uint32_t next_update) const + { + return new_crl(rng, + std::chrono::system_clock::now(), + std::chrono::seconds(next_update)); + } + +/* +* Update a CRL with new entries +*/ +X509_CRL X509_CA::update_crl(const X509_CRL& crl, + const std::vector<CRL_Entry>& new_revoked, + RandomNumberGenerator& rng, + uint32_t next_update) const + { + return update_crl(crl, new_revoked, rng, + std::chrono::system_clock::now(), + std::chrono::seconds(next_update)); + } + + +X509_CRL X509_CA::new_crl(RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const + { + std::vector<CRL_Entry> empty; + return make_crl(empty, 1, rng, issue_time, next_update); + } + +X509_CRL X509_CA::update_crl(const X509_CRL& last_crl, + const std::vector<CRL_Entry>& new_revoked, + RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const + { + std::vector<CRL_Entry> revoked = last_crl.get_revoked(); + + std::copy(new_revoked.begin(), new_revoked.end(), + std::back_inserter(revoked)); + + return make_crl(revoked, last_crl.crl_number() + 1, rng, issue_time, next_update); + } + +/* +* Create a CRL +*/ +X509_CRL X509_CA::make_crl(const std::vector<CRL_Entry>& revoked, + uint32_t crl_number, + RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const + { + const size_t X509_CRL_VERSION = 2; + + auto expire_time = issue_time + next_update; + + Extensions extensions; + extensions.add(new Cert_Extension::Authority_Key_ID(m_ca_cert.subject_key_id())); + extensions.add(new Cert_Extension::CRL_Number(crl_number)); + + // clang-format off + const std::vector<uint8_t> crl = X509_Object::make_signed( + m_signer.get(), rng, m_ca_sig_algo, + DER_Encoder().start_cons(SEQUENCE) + .encode(X509_CRL_VERSION-1) + .encode(m_ca_sig_algo) + .encode(m_ca_cert.subject_dn()) + .encode(X509_Time(issue_time)) + .encode(X509_Time(expire_time)) + .encode_if(revoked.size() > 0, + DER_Encoder() + .start_cons(SEQUENCE) + .encode_list(revoked) + .end_cons() + ) + .start_explicit(0) + .start_cons(SEQUENCE) + .encode(extensions) + .end_cons() + .end_explicit() + .end_cons() + .get_contents()); + // clang-format on + + return X509_CRL(crl); + } + +/* +* Return the CA's certificate +*/ +X509_Certificate X509_CA::ca_certificate() const + { + return m_ca_cert; + } + +/* +* 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 X509_Object::choose_sig_format(sig_algo, key, rng, hash_fn, "").release(); + } + +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) + { + std::string padding; + if(opts.count("padding")) + padding = opts.at("padding"); + return X509_Object::choose_sig_format(sig_algo, key, rng, hash_fn, padding).release(); + } + +} diff --git a/comm/third_party/botan/src/lib/x509/x509_ca.h b/comm/third_party/botan/src/lib/x509/x509_ca.h new file mode 100644 index 0000000000..20e9b1bccf --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_ca.h @@ -0,0 +1,261 @@ +/* +* X.509 Certificate Authority +* (C) 1999-2008 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CA_H_ +#define BOTAN_X509_CA_H_ + +#include <botan/x509cert.h> +#include <botan/x509_crl.h> +#include <chrono> +#include <map> + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +namespace Botan { + +class BigInt; +class Private_Key; +class PKCS10_Request; +class PK_Signer; + +/** +* This class represents X.509 Certificate Authorities (CAs). +*/ +class BOTAN_PUBLIC_API(2,0) X509_CA final + { + public: + /** + * Sign a PKCS#10 Request. + * @param req the request to sign + * @param rng the rng to use + * @param not_before the starting time for the certificate + * @param not_after the expiration time for the certificate + * @return resulting certificate + */ + X509_Certificate sign_request(const PKCS10_Request& req, + RandomNumberGenerator& rng, + const X509_Time& not_before, + const X509_Time& not_after) const; + + /** + * Sign a PKCS#10 Request. + * @param req the request to sign + * @param rng the rng to use + * @param serial_number the serial number the cert will be assigned. + * @param not_before the starting time for the certificate + * @param not_after the expiration time for the certificate + * @return resulting certificate + */ + X509_Certificate sign_request(const PKCS10_Request& req, + RandomNumberGenerator& rng, + const BigInt& serial_number, + const X509_Time& not_before, + const X509_Time& not_after) const; + + /** + * Get the certificate of this CA. + * @return CA certificate + */ + X509_Certificate ca_certificate() const; + + /** + * Create a new and empty CRL for this CA. + * @param rng the random number generator to use + * @param issue_time the issue time (typically system_clock::now) + * @param next_update the time interval after issue_data within which + * a new CRL will be produced. + * @return new CRL + */ + X509_CRL new_crl(RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const; + + /** + * Create a new CRL by with additional entries. + * @param last_crl the last CRL of this CA to add the new entries to + * @param new_entries contains the new CRL entries to be added to the CRL + * @param rng the random number generator to use + * @param issue_time the issue time (typically system_clock::now) + * @param next_update the time interval after issue_data within which + * a new CRL will be produced. + */ + X509_CRL update_crl(const X509_CRL& last_crl, + const std::vector<CRL_Entry>& new_entries, + RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const; + + /** + * Create a new and empty CRL for this CA. + * @param rng the random number generator to use + * @param next_update the time to set in next update in seconds + * as the offset from the current time + * @return new CRL + */ + X509_CRL new_crl(RandomNumberGenerator& rng, + uint32_t next_update = 604800) const; + + /** + * Create a new CRL by with additional entries. + * @param last_crl the last CRL of this CA to add the new entries to + * @param new_entries contains the new CRL entries to be added to the CRL + * @param rng the random number generator to use + * @param next_update the time to set in next update in seconds + * as the offset from the current time + */ + X509_CRL update_crl(const X509_CRL& last_crl, + const std::vector<CRL_Entry>& new_entries, + RandomNumberGenerator& rng, + uint32_t next_update = 604800) const; + + /** + * Interface for creating new certificates + * @param signer a signing object + * @param rng a random number generator + * @param sig_algo the signature algorithm identifier + * @param pub_key the serialized public key + * @param not_before the start time of the certificate + * @param not_after the end time of the certificate + * @param issuer_dn the DN of the issuer + * @param subject_dn the DN of the subject + * @param extensions an optional list of certificate extensions + * @returns newly minted certificate + */ + static X509_Certificate make_cert(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& sig_algo, + const std::vector<uint8_t>& pub_key, + const X509_Time& not_before, + const X509_Time& not_after, + const X509_DN& issuer_dn, + const X509_DN& subject_dn, + const Extensions& extensions); + + /** + * Interface for creating new certificates + * @param signer a signing object + * @param rng a random number generator + * @param serial_number the serial number the cert will be assigned + * @param sig_algo the signature algorithm identifier + * @param pub_key the serialized public key + * @param not_before the start time of the certificate + * @param not_after the end time of the certificate + * @param issuer_dn the DN of the issuer + * @param subject_dn the DN of the subject + * @param extensions an optional list of certificate extensions + * @returns newly minted certificate + */ + static X509_Certificate make_cert(PK_Signer* signer, + RandomNumberGenerator& rng, + const BigInt& serial_number, + const AlgorithmIdentifier& sig_algo, + const std::vector<uint8_t>& pub_key, + const X509_Time& not_before, + const X509_Time& not_after, + const X509_DN& issuer_dn, + const X509_DN& subject_dn, + const Extensions& extensions); + + /** + * Create a new CA object. + * @param ca_certificate the certificate of the CA + * @param key the private key of the CA + * @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::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, + const Private_Key& key, + const std::string& hash_fn) : + X509_CA(ca_certificate, key, hash_fn, system_rng()) + {} +#endif + + X509_CA(const X509_CA&) = delete; + X509_CA& operator=(const X509_CA&) = delete; + + X509_CA(X509_CA&&) = default; + X509_CA& operator=(X509_CA&&) = default; + + ~X509_CA(); + + private: + X509_CRL make_crl(const std::vector<CRL_Entry>& entries, + uint32_t crl_number, + RandomNumberGenerator& rng, + std::chrono::system_clock::time_point issue_time, + std::chrono::seconds next_update) const; + + AlgorithmIdentifier m_ca_sig_algo; + X509_Certificate m_ca_cert; + std::string m_hash_fn; + std::unique_ptr<PK_Signer> m_signer; + }; + +/** +* Choose the default signature format for a certain public key signature +* scheme. +* @param key will be the key to choose a padding scheme for +* @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 +*/ +BOTAN_PUBLIC_API(2,0) PK_Signer* choose_sig_format(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& hash_fn, + AlgorithmIdentifier& alg_id); + +/** +* @verbatim +* 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. +* @endverbatim +* +* @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/comm/third_party/botan/src/lib/x509/x509_crl.cpp b/comm/third_party/botan/src/lib/x509/x509_crl.cpp new file mode 100644 index 0000000000..1b5df1a083 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_crl.cpp @@ -0,0 +1,268 @@ +/* +* X.509 CRL +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_crl.h> +#include <botan/x509_ext.h> +#include <botan/x509cert.h> +#include <botan/ber_dec.h> + +#include <sstream> + +namespace Botan { + +struct CRL_Data + { + X509_DN m_issuer; + X509_Time m_this_update; + X509_Time m_next_update; + std::vector<CRL_Entry> m_entries; + Extensions m_extensions; + + // cached values from extensions + size_t m_crl_number = 0; + std::vector<uint8_t> m_auth_key_id; + std::string m_issuing_distribution_point; + }; + +std::string X509_CRL::PEM_label() const + { + return "X509 CRL"; + } + +std::vector<std::string> X509_CRL::alternate_PEM_labels() const + { + return { "CRL" }; + } + +X509_CRL::X509_CRL(DataSource& src) + { + load_data(src); + } + +X509_CRL::X509_CRL(const std::vector<uint8_t>& vec) + { + DataSource_Memory src(vec.data(), vec.size()); + load_data(src); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +X509_CRL::X509_CRL(const std::string& fsname) + { + DataSource_Stream src(fsname, true); + load_data(src); + } +#endif + +X509_CRL::X509_CRL(const X509_DN& issuer, + const X509_Time& this_update, + const X509_Time& next_update, + const std::vector<CRL_Entry>& revoked) : + X509_Object() + { + m_data.reset(new CRL_Data); + m_data->m_issuer = issuer; + m_data->m_this_update = this_update; + m_data->m_next_update = next_update; + m_data->m_entries = revoked; + } + +/** +* Check if this particular certificate is listed in the CRL +*/ +bool X509_CRL::is_revoked(const X509_Certificate& cert) const + { + /* + If the cert wasn't issued by the CRL issuer, it's possible the cert + is revoked, but not by this CRL. Maybe throw an exception instead? + */ + if(cert.issuer_dn() != issuer_dn()) + return false; + + std::vector<uint8_t> crl_akid = authority_key_id(); + std::vector<uint8_t> cert_akid = cert.authority_key_id(); + + if(!crl_akid.empty() && !cert_akid.empty()) + { + if(crl_akid != cert_akid) + return false; + } + + std::vector<uint8_t> cert_serial = cert.serial_number(); + + bool is_revoked = false; + + // FIXME would be nice to avoid a linear scan here - maybe sort the entries? + for(const CRL_Entry& entry : get_revoked()) + { + if(cert_serial == entry.serial_number()) + { + if(entry.reason_code() == REMOVE_FROM_CRL) + is_revoked = false; + else + is_revoked = true; + } + } + + return is_revoked; + } + +/* +* Decode the TBSCertList data +*/ +namespace { + +std::unique_ptr<CRL_Data> decode_crl_body(const std::vector<uint8_t>& body, + const AlgorithmIdentifier& sig_algo) + { + std::unique_ptr<CRL_Data> data(new CRL_Data); + + BER_Decoder tbs_crl(body); + + size_t version; + tbs_crl.decode_optional(version, INTEGER, UNIVERSAL); + + if(version != 0 && version != 1) + throw X509_CRL::X509_CRL_Error("Unknown X.509 CRL version " + + std::to_string(version+1)); + + AlgorithmIdentifier sig_algo_inner; + tbs_crl.decode(sig_algo_inner); + + if(sig_algo != sig_algo_inner) + throw X509_CRL::X509_CRL_Error("Algorithm identifier mismatch"); + + tbs_crl.decode(data->m_issuer) + .decode(data->m_this_update) + .decode(data->m_next_update); + + BER_Object next = tbs_crl.get_next_object(); + + if(next.is_a(SEQUENCE, CONSTRUCTED)) + { + BER_Decoder cert_list(std::move(next)); + + while(cert_list.more_items()) + { + CRL_Entry entry; + cert_list.decode(entry); + data->m_entries.push_back(entry); + } + next = tbs_crl.get_next_object(); + } + + if(next.is_a(0, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) + { + BER_Decoder crl_options(std::move(next)); + crl_options.decode(data->m_extensions).verify_end(); + next = tbs_crl.get_next_object(); + } + + if(next.is_set()) + throw X509_CRL::X509_CRL_Error("Unknown tag in CRL"); + + tbs_crl.verify_end(); + + // Now cache some fields from the extensions + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Number>()) + { + data->m_crl_number = ext->get_crl_number(); + } + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>()) + { + data->m_auth_key_id = ext->get_key_id(); + } + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Issuing_Distribution_Point>()) + { + std::stringstream ss; + + for(const auto& pair : ext->get_point().contents()) + { + ss << pair.first << ": " << pair.second << " "; + } + data->m_issuing_distribution_point = ss.str(); + } + + return data; + } + +} + +void X509_CRL::force_decode() + { + m_data.reset(decode_crl_body(signed_body(), signature_algorithm()).release()); + } + +const CRL_Data& X509_CRL::data() const + { + if(!m_data) + { + throw Invalid_State("X509_CRL uninitialized"); + } + return *m_data.get(); + } + +const Extensions& X509_CRL::extensions() const + { + return data().m_extensions; + } + +/* +* Return the list of revoked certificates +*/ +const std::vector<CRL_Entry>& X509_CRL::get_revoked() const + { + return data().m_entries; + } + +/* +* Return the distinguished name of the issuer +*/ +const X509_DN& X509_CRL::issuer_dn() const + { + return data().m_issuer; + } + +/* +* Return the key identifier of the issuer +*/ +const std::vector<uint8_t>& X509_CRL::authority_key_id() const + { + return data().m_auth_key_id; + } + +/* +* Return the CRL number of this CRL +*/ +uint32_t X509_CRL::crl_number() const + { + return static_cast<uint32_t>(data().m_crl_number); + } + +/* +* Return the issue data of the CRL +*/ +const X509_Time& X509_CRL::this_update() const + { + return data().m_this_update; + } + +/* +* Return the date when a new CRL will be issued +*/ +const X509_Time& X509_CRL::next_update() const + { + return data().m_next_update; + } + +/* +* Return the CRL's distribution point +*/ +std::string X509_CRL::crl_issuing_distribution_point() const + { + return data().m_issuing_distribution_point; + } +} diff --git a/comm/third_party/botan/src/lib/x509/x509_crl.h b/comm/third_party/botan/src/lib/x509/x509_crl.h new file mode 100644 index 0000000000..6d4a301abd --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_crl.h @@ -0,0 +1,209 @@ +/* +* X.509 CRL +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CRL_H_ +#define BOTAN_X509_CRL_H_ + +#include <botan/x509_obj.h> +#include <botan/asn1_obj.h> +#include <botan/pkix_enums.h> +#include <vector> + +namespace Botan { + +class Extensions; +class X509_Certificate; +class X509_DN; + +struct CRL_Entry_Data; +struct CRL_Data; + +/** +* This class represents CRL entries +*/ +class BOTAN_PUBLIC_API(2,0) CRL_Entry final : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + /** + * Get the serial number of the certificate associated with this entry. + * @return certificate's serial number + */ + const std::vector<uint8_t>& serial_number() const; + + /** + * Get the revocation date of the certificate associated with this entry + * @return certificate's revocation date + */ + const X509_Time& expire_time() const; + + /** + * Get the entries reason code + * @return reason code + */ + CRL_Code reason_code() const; + + /** + * Get the extensions on this CRL entry + */ + const Extensions& extensions() const; + + /** + * Create uninitialized CRL_Entry object + */ + CRL_Entry() = default; + + /** + * Construct an CRL entry. + * @param cert the certificate to revoke + * @param reason the reason code to set in the entry + */ + CRL_Entry(const X509_Certificate& cert, + CRL_Code reason = UNSPECIFIED); + + private: + friend class X509_CRL; + + const CRL_Entry_Data& data() const; + + std::shared_ptr<CRL_Entry_Data> m_data; + }; + +/** +* Test two CRL entries for equality in all fields. +*/ +BOTAN_PUBLIC_API(2,0) bool operator==(const CRL_Entry&, const CRL_Entry&); + +/** +* Test two CRL entries for inequality in at least one field. +*/ +BOTAN_PUBLIC_API(2,0) bool operator!=(const CRL_Entry&, const CRL_Entry&); + +/** +* This class represents X.509 Certificate Revocation Lists (CRLs). +*/ +class BOTAN_PUBLIC_API(2,0) X509_CRL final : public X509_Object + { + public: + /** + * This class represents CRL related errors. + * + * In a future major release this exception type will be removed and + * replaced with Decoding_Error + */ + class BOTAN_PUBLIC_API(2,0) X509_CRL_Error final : public Decoding_Error + { + public: + explicit X509_CRL_Error(const std::string& error) : + Decoding_Error("X509_CRL: " + error) {} + }; + + /** + * Check if this particular certificate is listed in the CRL + */ + bool is_revoked(const X509_Certificate& cert) const; + + /** + * Get the entries of this CRL in the form of a vector. + * @return vector containing the entries of this CRL. + */ + const std::vector<CRL_Entry>& get_revoked() const; + + /** + * Get the issuer DN of this CRL. + * @return CRLs issuer DN + */ + const X509_DN& issuer_dn() const; + + /** + * @return extension data for this CRL + */ + const Extensions& extensions() const; + + /** + * Get the AuthorityKeyIdentifier of this CRL. + * @return this CRLs AuthorityKeyIdentifier + */ + const std::vector<uint8_t>& authority_key_id() const; + + /** + * Get the serial number of this CRL. + * @return CRLs serial number + */ + uint32_t crl_number() const; + + /** + * Get the CRL's thisUpdate value. + * @return CRLs thisUpdate + */ + const X509_Time& this_update() const; + + /** + * Get the CRL's nextUpdate value. + * @return CRLs nextdUpdate + */ + const X509_Time& next_update() const; + + /** + * Get the CRL's distribution point + * @return CRL.IssuingDistributionPoint from the CRL's Data_Store + */ + std::string crl_issuing_distribution_point() const; + + /** + * Create an uninitialized CRL object. Any attempts to access + * this object will throw an exception. + */ + X509_CRL() = default; + + /** + * Construct a CRL from a data source. + * @param source the data source providing the DER or PEM encoded CRL. + */ + X509_CRL(DataSource& source); + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + /** + * Construct a CRL from a file containing the DER or PEM encoded CRL. + * @param filename the name of the CRL file + */ + X509_CRL(const std::string& filename); +#endif + + /** + * Construct a CRL from a binary vector + * @param vec the binary (DER) representation of the CRL + */ + X509_CRL(const std::vector<uint8_t>& vec); + + /** + * Construct a CRL + * @param issuer issuer of this CRL + * @param thisUpdate valid from + * @param nextUpdate valid until + * @param revoked entries to be included in the CRL + */ + X509_CRL(const X509_DN& issuer, const X509_Time& thisUpdate, + const X509_Time& nextUpdate, const std::vector<CRL_Entry>& revoked); + + private: + std::string PEM_label() const override; + + std::vector<std::string> alternate_PEM_labels() const override; + + void force_decode() override; + + const CRL_Data& data() const; + + std::shared_ptr<CRL_Data> m_data; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509_dn.cpp b/comm/third_party/botan/src/lib/x509/x509_dn.cpp new file mode 100644 index 0000000000..99aad475a9 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_dn.cpp @@ -0,0 +1,428 @@ +/* +* X509_DN +* (C) 1999-2007,2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkix_types.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/internal/stl_util.h> +#include <botan/oids.h> +#include <ostream> +#include <sstream> +#include <cctype> + +namespace Botan { + +/* +* Add an attribute to a X509_DN +*/ +void X509_DN::add_attribute(const std::string& type, + const std::string& str) + { + add_attribute(OID::from_string(type), str); + } + +/* +* Add an attribute to a X509_DN +*/ +void X509_DN::add_attribute(const OID& oid, const ASN1_String& str) + { + if(str.empty()) + return; + + m_rdn.push_back(std::make_pair(oid, str)); + m_dn_bits.clear(); + } + +/* +* Get the attributes of this X509_DN +*/ +std::multimap<OID, std::string> X509_DN::get_attributes() const + { + std::multimap<OID, std::string> retval; + + for(auto& i : m_rdn) + multimap_insert(retval, i.first, i.second.value()); + return retval; + } + +/* +* Get the contents of this X.500 Name +*/ +std::multimap<std::string, std::string> X509_DN::contents() const + { + std::multimap<std::string, std::string> retval; + + for(auto& i : m_rdn) + { + multimap_insert(retval, i.first.to_formatted_string(), i.second.value()); + } + return retval; + } + +bool X509_DN::has_field(const std::string& attr) const + { + const OID o = OIDS::str2oid_or_empty(deref_info_field(attr)); + if(o.has_value()) + return has_field(o); + else + return false; + } + +bool X509_DN::has_field(const OID& oid) const + { + for(auto& i : m_rdn) + { + if(i.first == oid) + return true; + } + + return false; + } + +std::string X509_DN::get_first_attribute(const std::string& attr) const + { + const OID oid = OID::from_string(deref_info_field(attr)); + return get_first_attribute(oid).value(); + } + +ASN1_String X509_DN::get_first_attribute(const OID& oid) const + { + for(auto& i : m_rdn) + { + if(i.first == oid) + { + return i.second; + } + } + + return ASN1_String(); + } + +/* +* Get a single attribute type +*/ +std::vector<std::string> X509_DN::get_attribute(const std::string& attr) const + { + const OID oid = OID::from_string(deref_info_field(attr)); + + std::vector<std::string> values; + + for(auto& i : m_rdn) + { + if(i.first == oid) + { + values.push_back(i.second.value()); + } + } + + return values; + } + +/* +* Deref aliases in a subject/issuer info request +*/ +std::string X509_DN::deref_info_field(const std::string& info) + { + if(info == "Name" || info == "CommonName" || info == "CN") return "X520.CommonName"; + if(info == "SerialNumber" || info == "SN") return "X520.SerialNumber"; + if(info == "Country" || info == "C") return "X520.Country"; + if(info == "Organization" || info == "O") return "X520.Organization"; + if(info == "Organizational Unit" || info == "OrgUnit" || info == "OU") + return "X520.OrganizationalUnit"; + if(info == "Locality" || info == "L") return "X520.Locality"; + if(info == "State" || info == "Province" || info == "ST") return "X520.State"; + if(info == "Email") return "RFC822"; + return info; + } + +/* +* Compare two X509_DNs for equality +*/ +bool operator==(const X509_DN& dn1, const X509_DN& dn2) + { + auto attr1 = dn1.get_attributes(); + auto attr2 = dn2.get_attributes(); + + if(attr1.size() != attr2.size()) return false; + + auto p1 = attr1.begin(); + auto p2 = attr2.begin(); + + while(true) + { + if(p1 == attr1.end() && p2 == attr2.end()) + break; + if(p1 == attr1.end()) return false; + if(p2 == attr2.end()) return false; + if(p1->first != p2->first) return false; + if(!x500_name_cmp(p1->second, p2->second)) + return false; + ++p1; + ++p2; + } + return true; + } + +/* +* Compare two X509_DNs for inequality +*/ +bool operator!=(const X509_DN& dn1, const X509_DN& dn2) + { + return !(dn1 == dn2); + } + +/* +* Induce an arbitrary ordering on DNs +*/ +bool operator<(const X509_DN& dn1, const X509_DN& dn2) + { + auto attr1 = dn1.get_attributes(); + auto attr2 = dn2.get_attributes(); + + // If they are not the same size, choose the smaller as the "lessor" + if(attr1.size() < attr2.size()) + return true; + if(attr1.size() > attr2.size()) + return false; + + // We know they are the same # of elements, now compare the OIDs: + auto p1 = attr1.begin(); + auto p2 = attr2.begin(); + + while(p1 != attr1.end() && p2 != attr2.end()) + { + if(p1->first != p2->first) + { + return (p1->first < p2->first); + } + + ++p1; + ++p2; + } + + // We know this is true because maps have the same size + BOTAN_ASSERT_NOMSG(p1 == attr1.end()); + BOTAN_ASSERT_NOMSG(p2 == attr2.end()); + + // Now we know all elements have the same OIDs, compare + // their string values: + + p1 = attr1.begin(); + p2 = attr2.begin(); + while(p1 != attr1.end() && p2 != attr2.end()) + { + BOTAN_DEBUG_ASSERT(p1->first == p2->first); + + // They may be binary different but same by X.500 rules, check this + if(!x500_name_cmp(p1->second, p2->second)) + { + // If they are not (by X.500) the same string, pick the + // lexicographic first as the lessor + return (p1->second < p2->second); + } + + ++p1; + ++p2; + } + + // if we reach here, then the DNs should be identical + BOTAN_DEBUG_ASSERT(dn1 == dn2); + return false; + } + +/* +* DER encode a DistinguishedName +*/ +void X509_DN::encode_into(DER_Encoder& der) const + { + der.start_cons(SEQUENCE); + + if(!m_dn_bits.empty()) + { + /* + If we decoded this from somewhere, encode it back exactly as + we received it + */ + der.raw_bytes(m_dn_bits); + } + else + { + for(const auto& dn : m_rdn) + { + der.start_cons(SET) + .start_cons(SEQUENCE) + .encode(dn.first) + .encode(dn.second) + .end_cons() + .end_cons(); + } + } + + der.end_cons(); + } + +/* +* Decode a BER encoded DistinguishedName +*/ +void X509_DN::decode_from(BER_Decoder& source) + { + std::vector<uint8_t> bits; + + source.start_cons(SEQUENCE) + .raw_bytes(bits) + .end_cons(); + + BER_Decoder sequence(bits); + + while(sequence.more_items()) + { + BER_Decoder rdn = sequence.start_cons(SET); + + while(rdn.more_items()) + { + OID oid; + ASN1_String str; + + rdn.start_cons(SEQUENCE) + .decode(oid) + .decode(str) // TODO support Any + .end_cons(); + + add_attribute(oid, str); + } + } + + m_dn_bits = bits; + } + +namespace { + +std::string to_short_form(const OID& oid) + { + const std::string long_id = oid.to_formatted_string(); + + if(long_id == "X520.CommonName") + return "CN"; + + if(long_id == "X520.Country") + return "C"; + + if(long_id == "X520.Organization") + return "O"; + + if(long_id == "X520.OrganizationalUnit") + return "OU"; + + return long_id; + } + +} + +std::string X509_DN::to_string() const + { + std::ostringstream out; + out << *this; + return out.str(); + } + +std::ostream& operator<<(std::ostream& out, const X509_DN& dn) + { + auto info = dn.dn_info(); + + for(size_t i = 0; i != info.size(); ++i) + { + out << to_short_form(info[i].first) << "=\""; + for(char c : info[i].second.value()) + { + if(c == '\\' || c == '\"') + { + out << "\\"; + } + out << c; + } + out << "\""; + + if(i + 1 < info.size()) + { + out << ","; + } + } + return out; + } + +std::istream& operator>>(std::istream& in, X509_DN& dn) + { + in >> std::noskipws; + do + { + std::string key; + std::string val; + char c; + + while(in.good()) + { + in >> c; + + if(std::isspace(c) && key.empty()) + continue; + else if(!std::isspace(c)) + { + key.push_back(c); + break; + } + else + break; + } + + while(in.good()) + { + in >> c; + + if(!std::isspace(c) && c != '=') + key.push_back(c); + else if(c == '=') + break; + else + throw Invalid_Argument("Ill-formed X.509 DN"); + } + + bool in_quotes = false; + while(in.good()) + { + in >> c; + + if(std::isspace(c)) + { + if(!in_quotes && !val.empty()) + break; + else if(in_quotes) + val.push_back(' '); + } + else if(c == '"') + in_quotes = !in_quotes; + else if(c == '\\') + { + if(in.good()) + in >> c; + val.push_back(c); + } + else if(c == ',' && !in_quotes) + break; + else + val.push_back(c); + } + + if(!key.empty() && !val.empty()) + dn.add_attribute(X509_DN::deref_info_field(key),val); + else + break; + } + while(in.good()); + return in; + } +} diff --git a/comm/third_party/botan/src/lib/x509/x509_dn.h b/comm/third_party/botan/src/lib/x509/x509_dn.h new file mode 100644 index 0000000000..cd7beebe9c --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_dn.h @@ -0,0 +1,11 @@ +/* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_DN_H_ +#define BOTAN_X509_DN_H_ + +#include <botan/pkix_types.h> +BOTAN_DEPRECATED_HEADER(x509_dn.h) + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509_dn_ub.cpp b/comm/third_party/botan/src/lib/x509/x509_dn_ub.cpp new file mode 100644 index 0000000000..b5c7645a13 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_dn_ub.cpp @@ -0,0 +1,60 @@ +/* +* DN_UB maps: Upper bounds on the length of DN strings +* +* This file was automatically generated by ./src/scripts/oids.py on 2019-10-21 +* +* 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/pkix_types.h> +#include <botan/asn1_obj.h> +#include <map> + +namespace { + +/** + * Upper bounds for the length of distinguished name fields as given in RFC 5280, Appendix A. + * Only OIDS recognized by botan are considered, so far. + * Maps OID string representations instead of human readable strings in order + * to avoid an additional lookup. + */ +static const std::map<Botan::OID, size_t> DN_UB = + { + { Botan::OID({2,5,4,10}), 64 }, // X520.Organization + { Botan::OID({2,5,4,11}), 64 }, // X520.OrganizationalUnit + { Botan::OID({2,5,4,12}), 64 }, // X520.Title + { Botan::OID({2,5,4,3}), 64 }, // X520.CommonName + { Botan::OID({2,5,4,4}), 40 }, // X520.Surname + { Botan::OID({2,5,4,42}), 32768 }, // X520.GivenName + { Botan::OID({2,5,4,43}), 32768 }, // X520.Initials + { Botan::OID({2,5,4,44}), 32768 }, // X520.GenerationalQualifier + { Botan::OID({2,5,4,46}), 64 }, // X520.DNQualifier + { Botan::OID({2,5,4,5}), 64 }, // X520.SerialNumber + { Botan::OID({2,5,4,6}), 3 }, // X520.Country + { Botan::OID({2,5,4,65}), 128 }, // X520.Pseudonym + { Botan::OID({2,5,4,7}), 128 }, // X520.Locality + { Botan::OID({2,5,4,8}), 128 }, // X520.State + { Botan::OID({2,5,4,9}), 128 } // X520.StreetAddress + }; +} + +namespace Botan { + +//static +size_t X509_DN::lookup_ub(const OID& oid) + { + auto ub_entry = DN_UB.find(oid); + if(ub_entry != DN_UB.end()) + { + return ub_entry->second; + } + else + { + return 0; + } + } +} + diff --git a/comm/third_party/botan/src/lib/x509/x509_ext.cpp b/comm/third_party/botan/src/lib/x509/x509_ext.cpp new file mode 100644 index 0000000000..e81e15c187 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_ext.cpp @@ -0,0 +1,1023 @@ +/* +* X.509 Certificate Extensions +* (C) 1999-2010,2012 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_ext.h> +#include <botan/x509cert.h> +#include <botan/datastor.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/hash.h> +#include <botan/loadstor.h> +#include <botan/internal/bit_ops.h> +#include <algorithm> +#include <set> +#include <sstream> + +namespace Botan { + +/* +* Create a Certificate_Extension object of some kind to handle +*/ +std::unique_ptr<Certificate_Extension> +Extensions::create_extn_obj(const OID& oid, + bool critical, + const std::vector<uint8_t>& body) + { + const std::string oid_str = oid.to_string(); + + std::unique_ptr<Certificate_Extension> extn; + + if(oid == Cert_Extension::Subject_Key_ID::static_oid()) + { + extn.reset(new Cert_Extension::Subject_Key_ID); + } + else if(oid == Cert_Extension::Key_Usage::static_oid()) + { + extn.reset(new Cert_Extension::Key_Usage); + } + else if(oid == Cert_Extension::Subject_Alternative_Name::static_oid()) + { + extn.reset(new Cert_Extension::Subject_Alternative_Name); + } + else if(oid == Cert_Extension::Issuer_Alternative_Name::static_oid()) + { + extn.reset(new Cert_Extension::Issuer_Alternative_Name); + } + else if(oid == Cert_Extension::Basic_Constraints::static_oid()) + { + extn.reset(new Cert_Extension::Basic_Constraints); + } + else if(oid == Cert_Extension::CRL_Number::static_oid()) + { + extn.reset(new Cert_Extension::CRL_Number); + } + else if(oid == Cert_Extension::CRL_ReasonCode::static_oid()) + { + extn.reset(new Cert_Extension::CRL_ReasonCode); + } + else if(oid == Cert_Extension::Authority_Key_ID::static_oid()) + { + extn.reset(new Cert_Extension::Authority_Key_ID); + } + else if(oid == Cert_Extension::Name_Constraints::static_oid()) + { + extn.reset(new Cert_Extension::Name_Constraints); + } + else if(oid == Cert_Extension::CRL_Distribution_Points::static_oid()) + { + extn.reset(new Cert_Extension::CRL_Distribution_Points); + } + else if(oid == Cert_Extension::CRL_Issuing_Distribution_Point::static_oid()) + { + extn.reset(new Cert_Extension::CRL_Issuing_Distribution_Point); + } + else if(oid == Cert_Extension::Certificate_Policies::static_oid()) + { + extn.reset(new Cert_Extension::Certificate_Policies); + } + else if(oid == Cert_Extension::Extended_Key_Usage::static_oid()) + { + extn.reset(new Cert_Extension::Extended_Key_Usage); + } + else if(oid == Cert_Extension::Authority_Information_Access::static_oid()) + { + extn.reset(new Cert_Extension::Authority_Information_Access); + } + else + { + // some other unknown extension type + extn.reset(new Cert_Extension::Unknown_Extension(oid, critical)); + } + + try + { + extn->decode_inner(body); + } + catch(Decoding_Error&) + { + extn.reset(new Cert_Extension::Unknown_Extension(oid, critical)); + extn->decode_inner(body); + } + return extn; + } + +/* +* Validate the extension (the default implementation is a NOP) +*/ +void Certificate_Extension::validate(const X509_Certificate&, const X509_Certificate&, + const std::vector<std::shared_ptr<const X509_Certificate>>&, + std::vector<std::set<Certificate_Status_Code>>&, + size_t) + { + } + +/* +* Add a new cert +*/ +void Extensions::add(Certificate_Extension* extn, bool critical) + { + // sanity check: we don't want to have the same extension more than once + if(m_extension_info.count(extn->oid_of()) > 0) + { + const std::string name = extn->oid_name(); + delete extn; + throw Invalid_Argument("Extension " + name + " already present in Extensions::add"); + } + + const OID oid = extn->oid_of(); + Extensions_Info info(critical, extn); + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); + } + +bool Extensions::add_new(Certificate_Extension* extn, bool critical) + { + if(m_extension_info.count(extn->oid_of()) > 0) + { + delete extn; + return false; // already exists + } + + const OID oid = extn->oid_of(); + Extensions_Info info(critical, extn); + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); + return true; + } + +bool Extensions::remove(const OID& oid) + { + const bool erased = m_extension_info.erase(oid) > 0; + + if(erased) + { + m_extension_oids.erase(std::find(m_extension_oids.begin(), m_extension_oids.end(), oid)); + } + + return erased; + } + +void Extensions::replace(Certificate_Extension* extn, bool critical) + { + // Remove it if it existed + remove(extn->oid_of()); + + const OID oid = extn->oid_of(); + Extensions_Info info(critical, extn); + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); + } + +bool Extensions::extension_set(const OID& oid) const + { + return (m_extension_info.find(oid) != m_extension_info.end()); + } + +bool Extensions::critical_extension_set(const OID& oid) const + { + auto i = m_extension_info.find(oid); + if(i != m_extension_info.end()) + return i->second.is_critical(); + return false; + } + +std::vector<uint8_t> Extensions::get_extension_bits(const OID& oid) const + { + auto i = m_extension_info.find(oid); + if(i == m_extension_info.end()) + throw Invalid_Argument("Extensions::get_extension_bits no such extension set"); + + return i->second.bits(); + } + +const Certificate_Extension* Extensions::get_extension_object(const OID& oid) const + { + auto extn = m_extension_info.find(oid); + if(extn == m_extension_info.end()) + return nullptr; + + return &extn->second.obj(); + } + +std::unique_ptr<Certificate_Extension> Extensions::get(const OID& oid) const + { + if(const Certificate_Extension* ext = this->get_extension_object(oid)) + { + return std::unique_ptr<Certificate_Extension>(ext->copy()); + } + return nullptr; + } + +std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> Extensions::extensions() const + { + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> exts; + for(auto&& ext : m_extension_info) + { + exts.push_back( + std::make_pair( + std::unique_ptr<Certificate_Extension>(ext.second.obj().copy()), + ext.second.is_critical()) + ); + } + return exts; + } + +std::map<OID, std::pair<std::vector<uint8_t>, bool>> Extensions::extensions_raw() const + { + std::map<OID, std::pair<std::vector<uint8_t>, bool>> out; + for(auto&& ext : m_extension_info) + { + out.emplace(ext.first, + std::make_pair(ext.second.bits(), + ext.second.is_critical())); + } + return out; + } + +/* +* Encode an Extensions list +*/ +void Extensions::encode_into(DER_Encoder& to_object) const + { + for(auto ext_info : m_extension_info) + { + const OID& oid = ext_info.first; + const bool should_encode = ext_info.second.obj().should_encode(); + + if(should_encode) + { + const bool is_critical = ext_info.second.is_critical(); + const std::vector<uint8_t>& ext_value = ext_info.second.bits(); + + to_object.start_cons(SEQUENCE) + .encode(oid) + .encode_optional(is_critical, false) + .encode(ext_value, OCTET_STRING) + .end_cons(); + } + } + } + +/* +* Decode a list of Extensions +*/ +void Extensions::decode_from(BER_Decoder& from_source) + { + m_extension_oids.clear(); + m_extension_info.clear(); + + BER_Decoder sequence = from_source.start_cons(SEQUENCE); + + while(sequence.more_items()) + { + OID oid; + bool critical; + std::vector<uint8_t> bits; + + sequence.start_cons(SEQUENCE) + .decode(oid) + .decode_optional(critical, BOOLEAN, UNIVERSAL, false) + .decode(bits, OCTET_STRING) + .end_cons(); + + std::unique_ptr<Certificate_Extension> obj = create_extn_obj(oid, critical, bits); + Extensions_Info info(critical, bits, obj.release()); + + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); + } + sequence.verify_end(); + } + +/* +* Write the extensions to an info store +*/ +void Extensions::contents_to(Data_Store& subject_info, + Data_Store& issuer_info) const + { + for(auto&& m_extn_info : m_extension_info) + { + m_extn_info.second.obj().contents_to(subject_info, issuer_info); + subject_info.add(m_extn_info.second.obj().oid_name() + ".is_critical", + m_extn_info.second.is_critical()); + } + } + +namespace Cert_Extension { + +/* +* Checked accessor for the path_limit member +*/ +size_t Basic_Constraints::get_path_limit() const + { + if(!m_is_ca) + throw Invalid_State("Basic_Constraints::get_path_limit: Not a CA"); + return m_path_limit; + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Basic_Constraints::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .encode_if(m_is_ca, + DER_Encoder() + .encode(m_is_ca) + .encode_optional(m_path_limit, NO_CERT_PATH_LIMIT) + ) + .end_cons(); + return output; + } + +/* +* Decode the extension +*/ +void Basic_Constraints::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_optional(m_is_ca, BOOLEAN, UNIVERSAL, false) + .decode_optional(m_path_limit, INTEGER, UNIVERSAL, NO_CERT_PATH_LIMIT) + .end_cons(); + + if(m_is_ca == false) + m_path_limit = 0; + } + +/* +* Return a textual representation +*/ +void Basic_Constraints::contents_to(Data_Store& subject, Data_Store&) const + { + subject.add("X509v3.BasicConstraints.is_ca", (m_is_ca ? 1 : 0)); + subject.add("X509v3.BasicConstraints.path_constraint", static_cast<uint32_t>(m_path_limit)); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Key_Usage::encode_inner() const + { + if(m_constraints == NO_CONSTRAINTS) + throw Encoding_Error("Cannot encode zero usage constraints"); + + const size_t unused_bits = ctz(static_cast<uint32_t>(m_constraints)); + + std::vector<uint8_t> der; + der.push_back(BIT_STRING); + der.push_back(2 + ((unused_bits < 8) ? 1 : 0)); + der.push_back(unused_bits % 8); + der.push_back((m_constraints >> 8) & 0xFF); + if(m_constraints & 0xFF) + der.push_back(m_constraints & 0xFF); + + return der; + } + +/* +* Decode the extension +*/ +void Key_Usage::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder ber(in); + + BER_Object obj = ber.get_next_object(); + + obj.assert_is_a(BIT_STRING, UNIVERSAL, "usage constraint"); + + if(obj.length() != 2 && obj.length() != 3) + throw BER_Decoding_Error("Bad size for BITSTRING in usage constraint"); + + uint16_t usage = 0; + + const uint8_t* bits = obj.bits(); + + if(bits[0] >= 8) + throw BER_Decoding_Error("Invalid unused bits in usage constraint"); + + const uint8_t mask = static_cast<uint8_t>(0xFF << bits[0]); + + if(obj.length() == 2) + { + usage = make_uint16(bits[1] & mask, 0); + } + else if(obj.length() == 3) + { + usage = make_uint16(bits[1], bits[2] & mask); + } + + m_constraints = Key_Constraints(usage); + } + +/* +* Return a textual representation +*/ +void Key_Usage::contents_to(Data_Store& subject, Data_Store&) const + { + subject.add("X509v3.KeyUsage", m_constraints); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Subject_Key_ID::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output).encode(m_key_id, OCTET_STRING); + return output; + } + +/* +* Decode the extension +*/ +void Subject_Key_ID::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode(m_key_id, OCTET_STRING).verify_end(); + } + +/* +* Return a textual representation +*/ +void Subject_Key_ID::contents_to(Data_Store& subject, Data_Store&) const + { + subject.add("X509v3.SubjectKeyIdentifier", m_key_id); + } + +/* +* Subject_Key_ID Constructor +*/ +Subject_Key_ID::Subject_Key_ID(const std::vector<uint8_t>& pub_key, const std::string& hash_name) + { + std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_name)); + + m_key_id.resize(hash->output_length()); + + hash->update(pub_key); + hash->final(m_key_id.data()); + + // Truncate longer hashes, 192 bits here seems plenty + const size_t max_skid_len = (192 / 8); + if(m_key_id.size() > max_skid_len) + m_key_id.resize(max_skid_len); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Authority_Key_ID::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .encode(m_key_id, OCTET_STRING, ASN1_Tag(0), CONTEXT_SPECIFIC) + .end_cons(); + return output; + } + +/* +* Decode the extension +*/ +void Authority_Key_ID::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_optional_string(m_key_id, OCTET_STRING, 0); + } + +/* +* Return a textual representation +*/ +void Authority_Key_ID::contents_to(Data_Store&, Data_Store& issuer) const + { + if(m_key_id.size()) + issuer.add("X509v3.AuthorityKeyIdentifier", m_key_id); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Subject_Alternative_Name::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output).encode(m_alt_name); + return output; + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Issuer_Alternative_Name::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output).encode(m_alt_name); + return output; + } + +/* +* Decode the extension +*/ +void Subject_Alternative_Name::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode(m_alt_name); + } + +/* +* Decode the extension +*/ +void Issuer_Alternative_Name::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode(m_alt_name); + } + +/* +* Return a textual representation +*/ +void Subject_Alternative_Name::contents_to(Data_Store& subject_info, + Data_Store&) const + { + subject_info.add(get_alt_name().contents()); + } + +/* +* Return a textual representation +*/ +void Issuer_Alternative_Name::contents_to(Data_Store&, Data_Store& issuer_info) const + { + issuer_info.add(get_alt_name().contents()); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Extended_Key_Usage::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .encode_list(m_oids) + .end_cons(); + return output; + } + +/* +* Decode the extension +*/ +void Extended_Key_Usage::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode_list(m_oids); + } + +/* +* Return a textual representation +*/ +void Extended_Key_Usage::contents_to(Data_Store& subject, Data_Store&) const + { + for(size_t i = 0; i != m_oids.size(); ++i) + subject.add("X509v3.ExtendedKeyUsage", m_oids[i].to_string()); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> Name_Constraints::encode_inner() const + { + throw Not_Implemented("Name_Constraints encoding"); + } + + +/* +* Decode the extension +*/ +void Name_Constraints::decode_inner(const std::vector<uint8_t>& in) + { + std::vector<GeneralSubtree> permit, exclude; + BER_Decoder ber(in); + BER_Decoder ext = ber.start_cons(SEQUENCE); + BER_Object per = ext.get_next_object(); + + ext.push_back(per); + if(per.is_a(0, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) + { + ext.decode_list(permit,ASN1_Tag(0),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + if(permit.empty()) + throw Encoding_Error("Empty Name Contraint list"); + } + + BER_Object exc = ext.get_next_object(); + ext.push_back(exc); + if(per.is_a(1, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) + { + ext.decode_list(exclude,ASN1_Tag(1),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + if(exclude.empty()) + throw Encoding_Error("Empty Name Contraint list"); + } + + ext.end_cons(); + + if(permit.empty() && exclude.empty()) + throw Encoding_Error("Empty Name Contraint extension"); + + m_name_constraints = NameConstraints(std::move(permit),std::move(exclude)); + } + +/* +* Return a textual representation +*/ +void Name_Constraints::contents_to(Data_Store& subject, Data_Store&) const + { + std::stringstream ss; + + for(const GeneralSubtree& gs: m_name_constraints.permitted()) + { + ss << gs; + subject.add("X509v3.NameConstraints.permitted", ss.str()); + ss.str(std::string()); + } + for(const GeneralSubtree& gs: m_name_constraints.excluded()) + { + ss << gs; + subject.add("X509v3.NameConstraints.excluded", ss.str()); + ss.str(std::string()); + } + } + +void Name_Constraints::validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) + { + if(!m_name_constraints.permitted().empty() || !m_name_constraints.excluded().empty()) + { + if(!subject.is_CA_cert()) + { + cert_status.at(pos).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + } + + const bool issuer_name_constraint_critical = + issuer.is_critical("X509v3.NameConstraints"); + + // Check that all subordinate certs pass the name constraint + for(size_t j = 0; j < pos; ++j) + { + bool permitted = m_name_constraints.permitted().empty(); + bool failed = false; + + for(auto c: m_name_constraints.permitted()) + { + switch(c.base().matches(*cert_path.at(j))) + { + case GeneralName::MatchResult::NotFound: + case GeneralName::MatchResult::All: + permitted = true; + break; + case GeneralName::MatchResult::UnknownType: + failed = issuer_name_constraint_critical; + permitted = true; + break; + default: + break; + } + } + + for(auto c: m_name_constraints.excluded()) + { + switch(c.base().matches(*cert_path.at(j))) + { + case GeneralName::MatchResult::All: + case GeneralName::MatchResult::Some: + failed = true; + break; + case GeneralName::MatchResult::UnknownType: + failed = issuer_name_constraint_critical; + break; + default: + break; + } + } + + if(failed || !permitted) + { + cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + } + } + } + } + +namespace { + +/* +* A policy specifier +*/ +class Policy_Information final : public ASN1_Object + { + public: + Policy_Information() = default; + explicit Policy_Information(const OID& oid) : m_oid(oid) {} + + const OID& oid() const { return m_oid; } + + void encode_into(DER_Encoder& codec) const override + { + codec.start_cons(SEQUENCE) + .encode(m_oid) + .end_cons(); + } + + void decode_from(BER_Decoder& codec) override + { + codec.start_cons(SEQUENCE) + .decode(m_oid) + .discard_remaining() + .end_cons(); + } + + private: + OID m_oid; + }; + +} + +/* +* Encode the extension +*/ +std::vector<uint8_t> Certificate_Policies::encode_inner() const + { + std::vector<Policy_Information> policies; + + for(size_t i = 0; i != m_oids.size(); ++i) + policies.push_back(Policy_Information(m_oids[i])); + + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .encode_list(policies) + .end_cons(); + return output; + } + +/* +* Decode the extension +*/ +void Certificate_Policies::decode_inner(const std::vector<uint8_t>& in) + { + std::vector<Policy_Information> policies; + + BER_Decoder(in).decode_list(policies); + m_oids.clear(); + for(size_t i = 0; i != policies.size(); ++i) + m_oids.push_back(policies[i].oid()); + } + +/* +* Return a textual representation +*/ +void Certificate_Policies::contents_to(Data_Store& info, Data_Store&) const + { + for(size_t i = 0; i != m_oids.size(); ++i) + info.add("X509v3.CertificatePolicies", m_oids[i].to_string()); + } + +void Certificate_Policies::validate( + const X509_Certificate& /*subject*/, + const X509_Certificate& /*issuer*/, + const std::vector<std::shared_ptr<const X509_Certificate>>& /*cert_path*/, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) + { + std::set<OID> oid_set(m_oids.begin(), m_oids.end()); + if(oid_set.size() != m_oids.size()) + { + cert_status.at(pos).insert(Certificate_Status_Code::DUPLICATE_CERT_POLICY); + } + } + +std::vector<uint8_t> Authority_Information_Access::encode_inner() const + { + ASN1_String url(m_ocsp_responder, IA5_STRING); + + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .encode(OID::from_string("PKIX.OCSP")) + .add_object(ASN1_Tag(6), CONTEXT_SPECIFIC, url.value()) + .end_cons() + .end_cons(); + return output; + } + +void Authority_Information_Access::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder ber = BER_Decoder(in).start_cons(SEQUENCE); + + while(ber.more_items()) + { + OID oid; + + BER_Decoder info = ber.start_cons(SEQUENCE); + + info.decode(oid); + + if(oid == OID::from_string("PKIX.OCSP")) + { + BER_Object name = info.get_next_object(); + + if(name.is_a(6, CONTEXT_SPECIFIC)) + { + m_ocsp_responder = ASN1::to_string(name); + } + + } + if(oid == OID::from_string("PKIX.CertificateAuthorityIssuers")) + { + BER_Object name = info.get_next_object(); + + if(name.is_a(6, CONTEXT_SPECIFIC)) + { + m_ca_issuers.push_back(ASN1::to_string(name)); + } + } + } + } + +void Authority_Information_Access::contents_to(Data_Store& subject, Data_Store&) const + { + if(!m_ocsp_responder.empty()) + subject.add("OCSP.responder", m_ocsp_responder); + for(const std::string& ca_issuer : m_ca_issuers) + subject.add("PKIX.CertificateAuthorityIssuers", ca_issuer); + } + +/* +* Checked accessor for the crl_number member +*/ +size_t CRL_Number::get_crl_number() const + { + if(!m_has_value) + throw Invalid_State("CRL_Number::get_crl_number: Not set"); + return m_crl_number; + } + +/* +* Copy a CRL_Number extension +*/ +CRL_Number* CRL_Number::copy() const + { + if(!m_has_value) + throw Invalid_State("CRL_Number::copy: Not set"); + return new CRL_Number(m_crl_number); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> CRL_Number::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output).encode(m_crl_number); + return output; + } + +/* +* Decode the extension +*/ +void CRL_Number::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode(m_crl_number); + m_has_value = true; + } + +/* +* Return a textual representation +*/ +void CRL_Number::contents_to(Data_Store& info, Data_Store&) const + { + info.add("X509v3.CRLNumber", static_cast<uint32_t>(m_crl_number)); + } + +/* +* Encode the extension +*/ +std::vector<uint8_t> CRL_ReasonCode::encode_inner() const + { + std::vector<uint8_t> output; + DER_Encoder(output).encode(static_cast<size_t>(m_reason), ENUMERATED, UNIVERSAL); + return output; + } + +/* +* Decode the extension +*/ +void CRL_ReasonCode::decode_inner(const std::vector<uint8_t>& in) + { + size_t reason_code = 0; + BER_Decoder(in).decode(reason_code, ENUMERATED, UNIVERSAL); + m_reason = static_cast<CRL_Code>(reason_code); + } + +/* +* Return a textual representation +*/ +void CRL_ReasonCode::contents_to(Data_Store& info, Data_Store&) const + { + info.add("X509v3.CRLReasonCode", m_reason); + } + +std::vector<uint8_t> CRL_Distribution_Points::encode_inner() const + { + throw Not_Implemented("CRL_Distribution_Points encoding"); + } + +void CRL_Distribution_Points::decode_inner(const std::vector<uint8_t>& buf) + { + BER_Decoder(buf) + .decode_list(m_distribution_points) + .verify_end(); + + std::stringstream ss; + + for(size_t i = 0; i != m_distribution_points.size(); ++i) + { + auto contents = m_distribution_points[i].point().contents(); + + for(const auto& pair : contents) + { + ss << pair.first << ": " << pair.second << " "; + } + } + + m_crl_distribution_urls.push_back(ss.str()); + } + +void CRL_Distribution_Points::contents_to(Data_Store& subject, Data_Store&) const + { + for(const std::string& crl_url : m_crl_distribution_urls) + subject.add("CRL.DistributionPoint", crl_url); + } + +void CRL_Distribution_Points::Distribution_Point::encode_into(class DER_Encoder&) const + { + throw Not_Implemented("CRL_Distribution_Points encoding"); + } + +void CRL_Distribution_Points::Distribution_Point::decode_from(class BER_Decoder& ber) + { + ber.start_cons(SEQUENCE) + .start_cons(ASN1_Tag(0), CONTEXT_SPECIFIC) + .decode_optional_implicit(m_point, ASN1_Tag(0), + ASN1_Tag(CONTEXT_SPECIFIC | CONSTRUCTED), + SEQUENCE, CONSTRUCTED) + .end_cons().end_cons(); + } + +std::vector<uint8_t> CRL_Issuing_Distribution_Point::encode_inner() const + { + throw Not_Implemented("CRL_Issuing_Distribution_Point encoding"); + } + +void CRL_Issuing_Distribution_Point::decode_inner(const std::vector<uint8_t>& buf) + { + BER_Decoder(buf).decode(m_distribution_point).verify_end(); + } + +void CRL_Issuing_Distribution_Point::contents_to(Data_Store& info, Data_Store&) const + { + auto contents = m_distribution_point.point().contents(); + std::stringstream ss; + + for(const auto& pair : contents) + { + ss << pair.first << ": " << pair.second << " "; + } + + info.add("X509v3.CRLIssuingDistributionPoint", ss.str()); + } + +std::vector<uint8_t> Unknown_Extension::encode_inner() const + { + return m_bytes; + } + +void Unknown_Extension::decode_inner(const std::vector<uint8_t>& bytes) + { + // Just treat as an opaque blob at this level + m_bytes = bytes; + } + +void Unknown_Extension::contents_to(Data_Store&, Data_Store&) const + { + // No information store + } + +} + +} diff --git a/comm/third_party/botan/src/lib/x509/x509_ext.h b/comm/third_party/botan/src/lib/x509/x509_ext.h new file mode 100644 index 0000000000..cb6e064c9d --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_ext.h @@ -0,0 +1,529 @@ +/* +* X.509 Certificate Extensions +* (C) 1999-2007,2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_EXTENSIONS_H_ +#define BOTAN_X509_EXTENSIONS_H_ + +#include <botan/pkix_types.h> +#include <set> + +namespace Botan { + +class Data_Store; +class X509_Certificate; + +namespace Cert_Extension { + +static const size_t NO_CERT_PATH_LIMIT = 0xFFFFFFF0; + +/** +* Basic Constraints Extension +*/ +class BOTAN_PUBLIC_API(2,0) Basic_Constraints final : public Certificate_Extension + { + public: + Basic_Constraints* copy() const override + { return new Basic_Constraints(m_is_ca, m_path_limit); } + + Basic_Constraints(bool ca = false, size_t limit = 0) : + m_is_ca(ca), m_path_limit(limit) {} + + bool get_is_ca() const { return m_is_ca; } + size_t get_path_limit() const; + + static OID static_oid() { return OID("2.5.29.19"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "X509v3.BasicConstraints"; } + + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + bool m_is_ca; + size_t m_path_limit; + }; + +/** +* Key Usage Constraints Extension +*/ +class BOTAN_PUBLIC_API(2,0) Key_Usage final : public Certificate_Extension + { + public: + Key_Usage* copy() const override { return new Key_Usage(m_constraints); } + + explicit Key_Usage(Key_Constraints c = NO_CONSTRAINTS) : m_constraints(c) {} + + Key_Constraints get_constraints() const { return m_constraints; } + + static OID static_oid() { return OID("2.5.29.15"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override { return "X509v3.KeyUsage"; } + + bool should_encode() const override + { return (m_constraints != NO_CONSTRAINTS); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + Key_Constraints m_constraints; + }; + +/** +* Subject Key Identifier Extension +*/ +class BOTAN_PUBLIC_API(2,0) Subject_Key_ID final : public Certificate_Extension + { + public: + Subject_Key_ID() = default; + + explicit Subject_Key_ID(const std::vector<uint8_t>& k) : m_key_id(k) {} + + Subject_Key_ID(const std::vector<uint8_t>& public_key, + const std::string& hash_fn); + + Subject_Key_ID* copy() const override + { return new Subject_Key_ID(m_key_id); } + + const std::vector<uint8_t>& get_key_id() const { return m_key_id; } + + static OID static_oid() { return OID("2.5.29.14"); } + OID oid_of() const override { return static_oid(); } + + private: + + std::string oid_name() const override + { return "X509v3.SubjectKeyIdentifier"; } + + bool should_encode() const override { return (m_key_id.size() > 0); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<uint8_t> m_key_id; + }; + +/** +* Authority Key Identifier Extension +*/ +class BOTAN_PUBLIC_API(2,0) Authority_Key_ID final : public Certificate_Extension + { + public: + Authority_Key_ID* copy() const override + { return new Authority_Key_ID(m_key_id); } + + Authority_Key_ID() = default; + explicit Authority_Key_ID(const std::vector<uint8_t>& k) : m_key_id(k) {} + + const std::vector<uint8_t>& get_key_id() const { return m_key_id; } + + static OID static_oid() { return OID("2.5.29.35"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "X509v3.AuthorityKeyIdentifier"; } + + bool should_encode() const override { return (m_key_id.size() > 0); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<uint8_t> m_key_id; + }; + +/** +* Subject Alternative Name Extension +*/ +class BOTAN_PUBLIC_API(2,4) Subject_Alternative_Name final : public Certificate_Extension + { + public: + const AlternativeName& get_alt_name() const { return m_alt_name; } + + static OID static_oid() { return OID("2.5.29.17"); } + OID oid_of() const override { return static_oid(); } + + Subject_Alternative_Name* copy() const override + { return new Subject_Alternative_Name(get_alt_name()); } + + explicit Subject_Alternative_Name(const AlternativeName& name = AlternativeName()) : + m_alt_name(name) {} + + private: + std::string oid_name() const override { return "X509v3.SubjectAlternativeName"; } + + bool should_encode() const override { return m_alt_name.has_items(); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + AlternativeName m_alt_name; + }; + +/** +* Issuer Alternative Name Extension +*/ +class BOTAN_PUBLIC_API(2,0) Issuer_Alternative_Name final : public Certificate_Extension + { + public: + const AlternativeName& get_alt_name() const { return m_alt_name; } + + static OID static_oid() { return OID("2.5.29.18"); } + OID oid_of() const override { return static_oid(); } + + Issuer_Alternative_Name* copy() const override + { return new Issuer_Alternative_Name(get_alt_name()); } + + explicit Issuer_Alternative_Name(const AlternativeName& name = AlternativeName()) : + m_alt_name(name) {} + + private: + std::string oid_name() const override { return "X509v3.IssuerAlternativeName"; } + + bool should_encode() const override { return m_alt_name.has_items(); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + AlternativeName m_alt_name; + }; + +/** +* Extended Key Usage Extension +*/ +class BOTAN_PUBLIC_API(2,0) Extended_Key_Usage final : public Certificate_Extension + { + public: + Extended_Key_Usage* copy() const override + { return new Extended_Key_Usage(m_oids); } + + Extended_Key_Usage() = default; + explicit Extended_Key_Usage(const std::vector<OID>& o) : m_oids(o) {} + + const std::vector<OID>& get_oids() const { return m_oids; } + + static OID static_oid() { return OID("2.5.29.37"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override { return "X509v3.ExtendedKeyUsage"; } + + bool should_encode() const override { return (m_oids.size() > 0); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<OID> m_oids; + }; + +/** +* Name Constraints +*/ +class BOTAN_PUBLIC_API(2,0) Name_Constraints final : public Certificate_Extension + { + public: + Name_Constraints* copy() const override + { return new Name_Constraints(m_name_constraints); } + + Name_Constraints() = default; + Name_Constraints(const NameConstraints &nc) : m_name_constraints(nc) {} + + void validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) override; + + const NameConstraints& get_name_constraints() const { return m_name_constraints; } + + static OID static_oid() { return OID("2.5.29.30"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "X509v3.NameConstraints"; } + + bool should_encode() const override { return true; } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + NameConstraints m_name_constraints; + }; + +/** +* Certificate Policies Extension +*/ +class BOTAN_PUBLIC_API(2,0) Certificate_Policies final : public Certificate_Extension + { + public: + Certificate_Policies* copy() const override + { return new Certificate_Policies(m_oids); } + + Certificate_Policies() = default; + explicit Certificate_Policies(const std::vector<OID>& o) : m_oids(o) {} + + BOTAN_DEPRECATED("Use get_policy_oids") + std::vector<OID> get_oids() const { return m_oids; } + + const std::vector<OID>& get_policy_oids() const { return m_oids; } + + static OID static_oid() { return OID("2.5.29.32"); } + OID oid_of() const override { return static_oid(); } + + void validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) override; + private: + std::string oid_name() const override + { return "X509v3.CertificatePolicies"; } + + bool should_encode() const override { return (m_oids.size() > 0); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<OID> m_oids; + }; + +/** +* Authority Information Access Extension +*/ +class BOTAN_PUBLIC_API(2,0) Authority_Information_Access final : public Certificate_Extension + { + public: + Authority_Information_Access* copy() const override + { return new Authority_Information_Access(m_ocsp_responder, m_ca_issuers); } + + Authority_Information_Access() = default; + + explicit Authority_Information_Access(const std::string& ocsp, const std::vector<std::string>& ca_issuers = std::vector<std::string>()) : + m_ocsp_responder(ocsp), m_ca_issuers(ca_issuers) {} + + std::string ocsp_responder() const { return m_ocsp_responder; } + + static OID static_oid() { return OID("1.3.6.1.5.5.7.1.1"); } + OID oid_of() const override { return static_oid(); } + const std::vector<std::string> ca_issuers() const { return m_ca_issuers; } + + private: + std::string oid_name() const override + { return "PKIX.AuthorityInformationAccess"; } + + bool should_encode() const override { return (!m_ocsp_responder.empty()); } + + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + + void contents_to(Data_Store&, Data_Store&) const override; + + std::string m_ocsp_responder; + std::vector<std::string> m_ca_issuers; + }; + +/** +* CRL Number Extension +*/ +class BOTAN_PUBLIC_API(2,0) CRL_Number final : public Certificate_Extension + { + public: + CRL_Number* copy() const override; + + CRL_Number() : m_has_value(false), m_crl_number(0) {} + CRL_Number(size_t n) : m_has_value(true), m_crl_number(n) {} + + size_t get_crl_number() const; + + static OID static_oid() { return OID("2.5.29.20"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override { return "X509v3.CRLNumber"; } + + bool should_encode() const override { return m_has_value; } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + bool m_has_value; + size_t m_crl_number; + }; + +/** +* CRL Entry Reason Code Extension +*/ +class BOTAN_PUBLIC_API(2,0) CRL_ReasonCode final : public Certificate_Extension + { + public: + CRL_ReasonCode* copy() const override + { return new CRL_ReasonCode(m_reason); } + + explicit CRL_ReasonCode(CRL_Code r = UNSPECIFIED) : m_reason(r) {} + + CRL_Code get_reason() const { return m_reason; } + + static OID static_oid() { return OID("2.5.29.21"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override { return "X509v3.ReasonCode"; } + + bool should_encode() const override { return (m_reason != UNSPECIFIED); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + CRL_Code m_reason; + }; + +/** +* CRL Distribution Points Extension +* todo enforce restrictions from RFC 5280 4.2.1.13 +*/ +class BOTAN_PUBLIC_API(2,0) CRL_Distribution_Points final : public Certificate_Extension + { + public: + class BOTAN_PUBLIC_API(2,0) Distribution_Point final : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + const AlternativeName& point() const { return m_point; } + private: + AlternativeName m_point; + }; + + CRL_Distribution_Points* copy() const override + { return new CRL_Distribution_Points(m_distribution_points); } + + CRL_Distribution_Points() = default; + + explicit CRL_Distribution_Points(const std::vector<Distribution_Point>& points) : + m_distribution_points(points) {} + + const std::vector<Distribution_Point>& distribution_points() const + { return m_distribution_points; } + + const std::vector<std::string>& crl_distribution_urls() const + { return m_crl_distribution_urls; } + + static OID static_oid() { return OID("2.5.29.31"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "X509v3.CRLDistributionPoints"; } + + bool should_encode() const override + { return !m_distribution_points.empty(); } + + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<Distribution_Point> m_distribution_points; + std::vector<std::string> m_crl_distribution_urls; + }; + +/** +* CRL Issuing Distribution Point Extension +* todo enforce restrictions from RFC 5280 5.2.5 +*/ +class CRL_Issuing_Distribution_Point final : public Certificate_Extension + { + public: + CRL_Issuing_Distribution_Point() = default; + + explicit CRL_Issuing_Distribution_Point(const CRL_Distribution_Points::Distribution_Point& distribution_point) : + m_distribution_point(distribution_point) {} + + CRL_Issuing_Distribution_Point* copy() const override + { return new CRL_Issuing_Distribution_Point(m_distribution_point); } + + const AlternativeName& get_point() const + { return m_distribution_point.point(); } + + static OID static_oid() { return OID("2.5.29.28"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "X509v3.CRLIssuingDistributionPoint"; } + + bool should_encode() const override { return true; } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + CRL_Distribution_Points::Distribution_Point m_distribution_point; + }; + +/** +* An unknown X.509 extension +* Will add a failure to the path validation result, if critical +*/ +class BOTAN_PUBLIC_API(2,4) Unknown_Extension final : public Certificate_Extension + { + public: + Unknown_Extension(const OID& oid, bool critical) : + m_oid(oid), m_critical(critical) {} + + Unknown_Extension* copy() const override + { return new Unknown_Extension(m_oid, m_critical); } + + /** + * Return the OID of this unknown extension + */ + OID oid_of() const override + { return m_oid; } + + //static_oid not defined for Unknown_Extension + + /** + * Return the extension contents + */ + const std::vector<uint8_t>& extension_contents() const { return m_bytes; } + + /** + * Return if this extension was marked critical + */ + bool is_critical_extension() const { return m_critical; } + + void validate(const X509_Certificate&, const X509_Certificate&, + const std::vector<std::shared_ptr<const X509_Certificate>>&, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) override + { + if(m_critical) + { + cert_status.at(pos).insert(Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION); + } + } + + private: + std::string oid_name() const override { return ""; } + + bool should_encode() const override { return true; } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + OID m_oid; + bool m_critical; + std::vector<uint8_t> m_bytes; + }; + + } + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509_obj.cpp b/comm/third_party/botan/src/lib/x509/x509_obj.cpp new file mode 100644 index 0000000000..2bb67629a6 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_obj.cpp @@ -0,0 +1,424 @@ +/* +* X.509 SIGNED Object +* (C) 1999-2007,2020 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_obj.h> +#include <botan/pubkey.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/pem.h> +#include <botan/emsa.h> +#include <algorithm> + +namespace Botan { + +namespace { +struct Pss_params + { + AlgorithmIdentifier hash_algo; + AlgorithmIdentifier mask_gen_algo; + AlgorithmIdentifier mask_gen_hash; // redundant: decoded mask_gen_algo.parameters + size_t salt_len; + size_t trailer_field; + }; + +Pss_params decode_pss_params(const std::vector<uint8_t>& encoded_pss_params) + { + const AlgorithmIdentifier default_hash("SHA-160", AlgorithmIdentifier::USE_NULL_PARAM); + const AlgorithmIdentifier default_mgf("MGF1", default_hash.BER_encode()); + + Pss_params pss_parameter; + BER_Decoder(encoded_pss_params) + .start_cons(SEQUENCE) + .decode_optional(pss_parameter.hash_algo, ASN1_Tag(0), PRIVATE, default_hash) + .decode_optional(pss_parameter.mask_gen_algo, ASN1_Tag(1), PRIVATE, default_mgf) + .decode_optional(pss_parameter.salt_len, ASN1_Tag(2), PRIVATE, size_t(20)) + .decode_optional(pss_parameter.trailer_field, ASN1_Tag(3), PRIVATE, size_t(1)) + .end_cons(); + + BER_Decoder(pss_parameter.mask_gen_algo.get_parameters()).decode(pss_parameter.mask_gen_hash); + + return pss_parameter; + } +} + +/* +* Read a PEM or BER X.509 object +*/ +void X509_Object::load_data(DataSource& in) + { + try { + if(ASN1::maybe_BER(in) && !PEM_Code::matches(in)) + { + BER_Decoder dec(in); + decode_from(dec); + } + else + { + std::string got_label; + DataSource_Memory ber(PEM_Code::decode(in, got_label)); + + if(got_label != PEM_label()) + { + bool is_alternate = false; + for(std::string alt_label : alternate_PEM_labels()) + { + if(got_label == alt_label) + { + is_alternate = true; + break; + } + } + + if(!is_alternate) + throw Decoding_Error("Unexpected PEM label for " + PEM_label() + " of " + got_label); + } + + BER_Decoder dec(ber); + decode_from(dec); + } + } + catch(Decoding_Error& e) + { + throw Decoding_Error(PEM_label() + " decoding", e); + } + } + + +void X509_Object::encode_into(DER_Encoder& to) const + { + to.start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .raw_bytes(signed_body()) + .end_cons() + .encode(signature_algorithm()) + .encode(signature(), BIT_STRING) + .end_cons(); + } + +/* +* Read a BER encoded X.509 object +*/ +void X509_Object::decode_from(BER_Decoder& from) + { + from.start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .raw_bytes(m_tbs_bits) + .end_cons() + .decode(m_sig_algo) + .decode(m_sig, BIT_STRING) + .end_cons(); + + force_decode(); + } + +/* +* Return a PEM encoded X.509 object +*/ +std::string X509_Object::PEM_encode() const + { + return PEM_Code::encode(BER_encode(), PEM_label()); + } + +/* +* Return the TBS data +*/ +std::vector<uint8_t> X509_Object::tbs_data() const + { + return ASN1::put_in_sequence(m_tbs_bits); + } + +/* +* Return the hash used in generating the signature +*/ +std::string X509_Object::hash_used_for_signature() const + { + const OID& oid = m_sig_algo.get_oid(); + const std::vector<std::string> sig_info = split_on(oid.to_formatted_string(), '/'); + + if(sig_info.size() == 1 && sig_info[0] == "Ed25519") + return "SHA-512"; + else if(sig_info.size() != 2) + throw Internal_Error("Invalid name format found for " + oid.to_string()); + + if(sig_info[1] == "EMSA4") + { + const OID hash_oid = decode_pss_params(signature_algorithm().get_parameters()).hash_algo.get_oid(); + return hash_oid.to_formatted_string(); + } + else + { + const std::vector<std::string> pad_and_hash = + parse_algorithm_name(sig_info[1]); + + if(pad_and_hash.size() != 2) + { + throw Internal_Error("Invalid name format " + sig_info[1]); + } + + return pad_and_hash[1]; + } + } + +/* +* Check the signature on an object +*/ +bool X509_Object::check_signature(const Public_Key* pub_key) const + { + if(!pub_key) + throw Invalid_Argument("No key provided for " + PEM_label() + " signature check"); + std::unique_ptr<const Public_Key> key(pub_key); + return check_signature(*key); + } + +bool X509_Object::check_signature(const Public_Key& pub_key) const + { + const Certificate_Status_Code code = verify_signature(pub_key); + return (code == Certificate_Status_Code::VERIFIED); + } + +Certificate_Status_Code X509_Object::verify_signature(const Public_Key& pub_key) const + { + const std::vector<std::string> sig_info = + split_on(m_sig_algo.get_oid().to_formatted_string(), '/'); + + if(sig_info.size() < 1 || sig_info.size() > 2 || sig_info[0] != pub_key.algo_name()) + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + + const std::string pub_key_algo = sig_info[0]; + std::string padding; + if(sig_info.size() == 2) + padding = sig_info[1]; + else if(pub_key_algo == "Ed25519" || pub_key_algo == "XMSS") + padding = "Pure"; + else + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + + const Signature_Format format = pub_key.default_x509_signature_format(); + + if(padding == "EMSA4") + { + // "MUST contain RSASSA-PSS-params" + if(signature_algorithm().get_parameters().empty()) + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + + Pss_params pss_parameter = decode_pss_params(signature_algorithm().get_parameters()); + + // hash_algo must be SHA1, SHA2-224, SHA2-256, SHA2-384 or SHA2-512 + const std::string hash_algo = pss_parameter.hash_algo.get_oid().to_formatted_string(); + if(hash_algo != "SHA-160" && + hash_algo != "SHA-224" && + hash_algo != "SHA-256" && + hash_algo != "SHA-384" && + hash_algo != "SHA-512") + { + return Certificate_Status_Code::UNTRUSTED_HASH; + } + + const std::string mgf_algo = pss_parameter.mask_gen_algo.get_oid().to_formatted_string(); + if(mgf_algo != "MGF1") + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + + // For MGF1, it is strongly RECOMMENDED that the underlying hash function be the same as the one identified by hashAlgorithm + // Must be SHA1, SHA2-224, SHA2-256, SHA2-384 or SHA2-512 + if(pss_parameter.mask_gen_hash.get_oid() != pss_parameter.hash_algo.get_oid()) + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + + if(pss_parameter.trailer_field != 1) + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + + padding += "(" + hash_algo + "," + mgf_algo + "," + std::to_string(pss_parameter.salt_len) + ")"; + } + else + { + /* + * For all other signature types the signature parameters should + * be either NULL or empty. In theory there is some distinction between + * these but in practice they seem to be used somewhat interchangeably. + * + * The various RFCs all have prescriptions of what is allowed: + * RSA - NULL (RFC 3279) + * DSA - empty (RFC 3279) + * ECDSA - empty (RFC 3279) + * GOST - empty (RFC 4491) + * Ed25519 - empty (RFC 8410) + * XMSS - empty (draft-vangeest-x509-hash-sigs) + * + * But in practice we find RSA with empty and ECDSA will NULL all + * over the place so it's not really possible to enforce. For Ed25519 + * and XMSS because they are new we attempt to enforce. + */ + if(pub_key_algo == "Ed25519" || pub_key_algo == "XMSS") + { + if(!signature_algorithm().parameters_are_empty()) + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + } + else + { + if(!signature_algorithm().parameters_are_null_or_empty()) + { + return Certificate_Status_Code::SIGNATURE_ALGO_BAD_PARAMS; + } + } + } + + try + { + PK_Verifier verifier(pub_key, padding, format); + const bool valid = verifier.verify_message(tbs_data(), signature()); + + if(valid) + return Certificate_Status_Code::VERIFIED; + else + return Certificate_Status_Code::SIGNATURE_ERROR; + } + catch(Algorithm_Not_Found&) + { + return Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN; + } + catch(...) + { + // This shouldn't happen, fallback to generic signature error + return Certificate_Status_Code::SIGNATURE_ERROR; + } + } + +/* +* Apply the X.509 SIGNED macro +*/ +std::vector<uint8_t> X509_Object::make_signed(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& algo, + const secure_vector<uint8_t>& tbs_bits) + { + const std::vector<uint8_t> signature = signer->sign_message(tbs_bits, rng); + + std::vector<uint8_t> output; + DER_Encoder(output) + .start_cons(SEQUENCE) + .raw_bytes(tbs_bits) + .encode(algo) + .encode(signature, BIT_STRING) + .end_cons(); + + return output; + } + +namespace { + +std::string choose_sig_algo(AlgorithmIdentifier& sig_algo, + const Private_Key& key, + const std::string& hash_fn, + const std::string& user_specified) + { + const std::string algo_name = key.algo_name(); + std::string padding; + + // check algo_name and set default + if(algo_name == "RSA") + { + // set to EMSA3 for compatibility reasons, originally it was the only option + padding = "EMSA3(" + hash_fn + ")"; + } + else if(algo_name == "DSA" || + algo_name == "ECDSA" || + algo_name == "ECGDSA" || + algo_name == "ECKCDSA" || + algo_name == "GOST-34.10" || + algo_name == "GOST-34.10-2012-256" || + algo_name == "GOST-34.10-2012-512") + { + padding = "EMSA1(" + hash_fn + ")"; + } + else if(algo_name == "Ed25519") + { + padding = "Pure"; + } + else if(algo_name == "XMSS") + { + if(user_specified.empty() == true) + { + throw Invalid_Argument("XMSS requires padding scheme"); + } + padding = user_specified; + sig_algo = AlgorithmIdentifier(OID::from_string("XMSS"), AlgorithmIdentifier::USE_EMPTY_PARAM); + return padding; + } + else + { + throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); + } + + if(user_specified.empty() == false) + { + padding = user_specified; + } + + if(padding != "Pure") + { + // try to construct an EMSA object from the padding options or default + std::unique_ptr<EMSA> emsa; + 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) + { + throw Invalid_Argument("Could not parse padding scheme " + padding); + } + + sig_algo = emsa->config_for_x509(key, hash_fn); + return emsa->name(); + } + else + { + sig_algo = AlgorithmIdentifier(OID::from_string("Ed25519"), AlgorithmIdentifier::USE_EMPTY_PARAM); + return "Pure"; + } + } + +} + +/* +* Choose a signing format for the key +*/ +std::unique_ptr<PK_Signer> X509_Object::choose_sig_format(AlgorithmIdentifier& sig_algo, + const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& hash_fn, + const std::string& padding_algo) + { + const Signature_Format format = key.default_x509_signature_format(); + + const std::string emsa = choose_sig_algo(sig_algo, key, hash_fn, padding_algo); + + return std::unique_ptr<PK_Signer>(new PK_Signer(key, rng, emsa, format)); + } + +} diff --git a/comm/third_party/botan/src/lib/x509/x509_obj.h b/comm/third_party/botan/src/lib/x509/x509_obj.h new file mode 100644 index 0000000000..fd972aed9d --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509_obj.h @@ -0,0 +1,144 @@ +/* +* X.509 SIGNED Object +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_OBJECT_H_ +#define BOTAN_X509_OBJECT_H_ + +#include <botan/asn1_obj.h> +#include <botan/pkix_enums.h> +#include <vector> + +namespace Botan { + +class Public_Key; +class Private_Key; +class RandomNumberGenerator; + +/** +* This class represents abstract X.509 signed objects as in the X.500 +* SIGNED macro +*/ +class BOTAN_PUBLIC_API(2,0) X509_Object : public ASN1_Object + { + public: + /** + * The underlying data that is to be or was signed + * @return data that is or was signed + */ + std::vector<uint8_t> tbs_data() const; + + /** + * @return signature on tbs_data() + */ + const std::vector<uint8_t>& signature() const { return m_sig; } + + /** + * @return signed body + */ + const std::vector<uint8_t>& signed_body() const { return m_tbs_bits; } + + /** + * @return signature algorithm that was used to generate signature + */ + const AlgorithmIdentifier& signature_algorithm() const { return m_sig_algo; } + + /** + * @return hash algorithm that was used to generate signature + */ + std::string hash_used_for_signature() const; + + /** + * Create a signed X509 object. + * @param signer the signer used to sign the object + * @param rng the random number generator to use + * @param alg_id the algorithm identifier of the signature scheme + * @param tbs the tbs bits to be signed + * @return signed X509 object + */ + static std::vector<uint8_t> make_signed(class PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& alg_id, + const secure_vector<uint8_t>& tbs); + + /** + * Check the signature on this data + * @param key the public key purportedly used to sign this data + * @return status of the signature - OK if verified or otherwise an indicator of + * the problem preventing verification. + */ + Certificate_Status_Code verify_signature(const Public_Key& key) const; + + /** + * Check the signature on this data + * @param key the public key purportedly used to sign this data + * @return true if the signature is valid, otherwise false + */ + bool check_signature(const Public_Key& key) const; + + /** + * Check the signature on this data + * @param key the public key purportedly used to sign this data + * the object will be deleted after use (this should have + * been a std::unique_ptr<Public_Key>) + * @return true if the signature is valid, otherwise false + */ + bool check_signature(const Public_Key* key) const; + + /** + * DER encode an X509_Object + * See @ref ASN1_Object::encode_into() + */ + void encode_into(class DER_Encoder& to) const override; + + /** + * Decode a BER encoded X509_Object + * See @ref ASN1_Object::decode_from() + */ + void decode_from(class BER_Decoder& from) override; + + /** + * @return PEM encoding of this + */ + std::string PEM_encode() const; + + X509_Object(const X509_Object&) = default; + X509_Object& operator=(const X509_Object&) = default; + + virtual std::string PEM_label() const = 0; + + virtual std::vector<std::string> alternate_PEM_labels() const + { return std::vector<std::string>(); } + + virtual ~X509_Object() = default; + + static std::unique_ptr<PK_Signer> + choose_sig_format(AlgorithmIdentifier& sig_algo, + const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& hash_fn, + const std::string& padding_algo); + + protected: + + X509_Object() = default; + + /** + * Decodes from src as either DER or PEM data, then calls force_decode() + */ + void load_data(DataSource& src); + + private: + virtual void force_decode() = 0; + + AlgorithmIdentifier m_sig_algo; + std::vector<uint8_t> m_tbs_bits; + std::vector<uint8_t> m_sig; + }; + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509cert.cpp b/comm/third_party/botan/src/lib/x509/x509cert.cpp new file mode 100644 index 0000000000..55f279c58c --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509cert.cpp @@ -0,0 +1,956 @@ +/* +* X.509 Certificates +* (C) 1999-2010,2015,2017 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509cert.h> +#include <botan/x509_key.h> +#include <botan/datastor.h> +#include <botan/pk_keys.h> +#include <botan/x509_ext.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/bigint.h> +#include <botan/oids.h> +#include <botan/hash.h> +#include <botan/hex.h> +#include <algorithm> +#include <sstream> + +namespace Botan { + +struct X509_Certificate_Data + { + std::vector<uint8_t> m_serial; + AlgorithmIdentifier m_sig_algo_inner; + X509_DN m_issuer_dn; + X509_DN m_subject_dn; + std::vector<uint8_t> m_issuer_dn_bits; + std::vector<uint8_t> m_subject_dn_bits; + X509_Time m_not_before; + X509_Time m_not_after; + std::vector<uint8_t> m_subject_public_key_bits; + std::vector<uint8_t> m_subject_public_key_bits_seq; + std::vector<uint8_t> m_subject_public_key_bitstring; + std::vector<uint8_t> m_subject_public_key_bitstring_sha1; + AlgorithmIdentifier m_subject_public_key_algid; + + std::vector<uint8_t> m_v2_issuer_key_id; + std::vector<uint8_t> m_v2_subject_key_id; + Extensions m_v3_extensions; + + std::vector<OID> m_extended_key_usage; + std::vector<uint8_t> m_authority_key_id; + std::vector<uint8_t> m_subject_key_id; + std::vector<OID> m_cert_policies; + + std::vector<std::string> m_crl_distribution_points; + std::string m_ocsp_responder; + std::vector<std::string> m_ca_issuers; + + std::vector<uint8_t> m_issuer_dn_bits_sha256; + std::vector<uint8_t> m_subject_dn_bits_sha256; + + std::string m_fingerprint_sha1; + std::string m_fingerprint_sha256; + + AlternativeName m_subject_alt_name; + AlternativeName m_issuer_alt_name; + NameConstraints m_name_constraints; + + Data_Store m_subject_ds; + Data_Store m_issuer_ds; + + size_t m_version = 0; + size_t m_path_len_constraint = 0; + Key_Constraints m_key_constraints = NO_CONSTRAINTS; + bool m_self_signed = false; + bool m_is_ca_certificate = false; + bool m_serial_negative = false; + }; + +std::string X509_Certificate::PEM_label() const + { + return "CERTIFICATE"; + } + +std::vector<std::string> X509_Certificate::alternate_PEM_labels() const + { + return { "X509 CERTIFICATE" }; + } + +X509_Certificate::X509_Certificate(DataSource& src) + { + load_data(src); + } + +X509_Certificate::X509_Certificate(const std::vector<uint8_t>& vec) + { + DataSource_Memory src(vec.data(), vec.size()); + load_data(src); + } + +X509_Certificate::X509_Certificate(const uint8_t data[], size_t len) + { + DataSource_Memory src(data, len); + load_data(src); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +X509_Certificate::X509_Certificate(const std::string& fsname) + { + DataSource_Stream src(fsname, true); + load_data(src); + } +#endif + +namespace { + +std::unique_ptr<X509_Certificate_Data> parse_x509_cert_body(const X509_Object& obj) + { + std::unique_ptr<X509_Certificate_Data> data(new X509_Certificate_Data); + + BigInt serial_bn; + BER_Object public_key; + BER_Object v3_exts_data; + + BER_Decoder(obj.signed_body()) + .decode_optional(data->m_version, ASN1_Tag(0), ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + .decode(serial_bn) + .decode(data->m_sig_algo_inner) + .decode(data->m_issuer_dn) + .start_cons(SEQUENCE) + .decode(data->m_not_before) + .decode(data->m_not_after) + .end_cons() + .decode(data->m_subject_dn) + .get_next(public_key) + .decode_optional_string(data->m_v2_issuer_key_id, BIT_STRING, 1) + .decode_optional_string(data->m_v2_subject_key_id, BIT_STRING, 2) + .get_next(v3_exts_data) + .verify_end("TBSCertificate has extra data after extensions block"); + + if(data->m_version > 2) + throw Decoding_Error("Unknown X.509 cert version " + std::to_string(data->m_version)); + if(obj.signature_algorithm() != data->m_sig_algo_inner) + throw Decoding_Error("X.509 Certificate had differing algorithm identifers in inner and outer ID fields"); + + public_key.assert_is_a(SEQUENCE, CONSTRUCTED, "X.509 certificate public key"); + + // crude method to save the serial's sign; will get lost during decoding, otherwise + data->m_serial_negative = serial_bn.is_negative(); + + // for general sanity convert wire version (0 based) to standards version (v1 .. v3) + data->m_version += 1; + + data->m_serial = BigInt::encode(serial_bn); + data->m_subject_dn_bits = ASN1::put_in_sequence(data->m_subject_dn.get_bits()); + data->m_issuer_dn_bits = ASN1::put_in_sequence(data->m_issuer_dn.get_bits()); + + // validate_public_key_params(public_key.value); + AlgorithmIdentifier public_key_alg_id; + BER_Decoder(public_key).decode(public_key_alg_id).discard_remaining(); + + const std::vector<std::string> public_key_info = + split_on(OIDS::oid2str_or_empty(public_key_alg_id.get_oid()), '/'); + + if(!public_key_info.empty() && public_key_info[0] == "RSA") + { + // RFC4055: If PublicKeyAlgo = PSS or OAEP: limit the use of the public key exclusively to either RSASSA - PSS or RSAES - OAEP + if(public_key_info.size() >= 2) + { + if(public_key_info[1] == "EMSA4") + { + /* + When the RSA private key owner wishes to limit the use of the public + key exclusively to RSASSA-PSS, then the id-RSASSA-PSS object + identifier MUST be used in the algorithm field within the subject + public key information, and, if present, the parameters field MUST + contain RSASSA-PSS-params. + + All parameters in the signature structure algorithm identifier MUST + match the parameters in the key structure algorithm identifier + except the saltLength field. The saltLength field in the signature parameters + MUST be greater or equal to that in the key parameters field. + + ToDo: Allow salt length to be greater + */ + if(public_key_alg_id != obj.signature_algorithm()) + { + throw Decoding_Error("Algorithm identifier mismatch"); + } + } + } + else + { + // oid = rsaEncryption -> parameters field MUST contain NULL + if(public_key_alg_id != AlgorithmIdentifier(public_key_alg_id.get_oid(), AlgorithmIdentifier::USE_NULL_PARAM)) + { + throw Decoding_Error("RSA algorithm parameters field MUST contain NULL"); + } + } + } + + data->m_subject_public_key_bits.assign(public_key.bits(), public_key.bits() + public_key.length()); + + data->m_subject_public_key_bits_seq = ASN1::put_in_sequence(data->m_subject_public_key_bits); + + BER_Decoder(data->m_subject_public_key_bits) + .decode(data->m_subject_public_key_algid) + .decode(data->m_subject_public_key_bitstring, BIT_STRING); + + if(v3_exts_data.is_a(3, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) + { + // Path validation will reject a v1/v2 cert with v3 extensions + BER_Decoder(v3_exts_data).decode(data->m_v3_extensions).verify_end(); + } + else if(v3_exts_data.is_set()) + { + throw BER_Bad_Tag("Unknown tag in X.509 cert", v3_exts_data.tagging()); + } + + // Now cache some fields from the extensions + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Key_Usage>()) + { + data->m_key_constraints = ext->get_constraints(); + /* + RFC 5280: When the keyUsage extension appears in a certificate, + at least one of the bits MUST be set to 1. + */ + if(data->m_key_constraints == NO_CONSTRAINTS) + { + throw Decoding_Error("Certificate has invalid encoding for KeyUsage"); + } + } + else + { + data->m_key_constraints = NO_CONSTRAINTS; + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Subject_Key_ID>()) + { + data->m_subject_key_id = ext->get_key_id(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>()) + { + data->m_authority_key_id = ext->get_key_id(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Name_Constraints>()) + { + data->m_name_constraints = ext->get_name_constraints(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Basic_Constraints>()) + { + if(ext->get_is_ca() == true) + { + /* + * RFC 5280 section 4.2.1.3 requires that CAs include KeyUsage in all + * intermediate CA certificates they issue. Currently we accept it being + * missing, as do most other implementations. But it may be worth + * removing this entirely, or alternately adding a warning level + * validation failure for it. + */ + if(data->m_key_constraints == NO_CONSTRAINTS || + (data->m_key_constraints & KEY_CERT_SIGN)) + { + data->m_is_ca_certificate = true; + data->m_path_len_constraint = ext->get_path_limit(); + } + } + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Issuer_Alternative_Name>()) + { + data->m_issuer_alt_name = ext->get_alt_name(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Subject_Alternative_Name>()) + { + data->m_subject_alt_name = ext->get_alt_name(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Extended_Key_Usage>()) + { + data->m_extended_key_usage = ext->get_oids(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Certificate_Policies>()) + { + data->m_cert_policies = ext->get_policy_oids(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Authority_Information_Access>()) + { + data->m_ocsp_responder = ext->ocsp_responder(); + data->m_ca_issuers = ext->ca_issuers(); + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::CRL_Distribution_Points>()) + { + data->m_crl_distribution_points = ext->crl_distribution_urls(); + } + + // Check for self-signed vs self-issued certificates + if(data->m_subject_dn == data->m_issuer_dn) + { + if(data->m_subject_key_id.empty() == false && data->m_authority_key_id.empty() == false) + { + data->m_self_signed = (data->m_subject_key_id == data->m_authority_key_id); + } + else + { + /* + If a parse error or unknown algorithm is encountered, default + to assuming it is self signed. We have no way of being certain but + that is usually the default case (self-issued is rare in practice). + */ + data->m_self_signed = true; + + try + { + std::unique_ptr<Public_Key> pub_key(X509::load_key(data->m_subject_public_key_bits_seq)); + + Certificate_Status_Code sig_status = obj.verify_signature(*pub_key); + + if(sig_status == Certificate_Status_Code::OK || + sig_status == Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN) + { + data->m_self_signed = true; + } + else + { + data->m_self_signed = false; + } + } + catch(...) + { + // ignore errors here to allow parsing to continue + } + } + } + + const std::vector<uint8_t> full_encoding = obj.BER_encode(); + + std::unique_ptr<HashFunction> sha1(HashFunction::create("SHA-1")); + if(sha1) + { + sha1->update(data->m_subject_public_key_bitstring); + data->m_subject_public_key_bitstring_sha1 = sha1->final_stdvec(); + // otherwise left as empty, and we will throw if subject_public_key_bitstring_sha1 is called + + data->m_fingerprint_sha1 = create_hex_fingerprint(full_encoding, "SHA-1"); + } + + std::unique_ptr<HashFunction> sha256(HashFunction::create("SHA-256")); + if(sha256) + { + sha256->update(data->m_issuer_dn_bits); + data->m_issuer_dn_bits_sha256 = sha256->final_stdvec(); + + sha256->update(data->m_subject_dn_bits); + data->m_subject_dn_bits_sha256 = sha256->final_stdvec(); + + data->m_fingerprint_sha256 = create_hex_fingerprint(full_encoding, "SHA-256"); + } + + data->m_subject_ds.add(data->m_subject_dn.contents()); + data->m_issuer_ds.add(data->m_issuer_dn.contents()); + data->m_v3_extensions.contents_to(data->m_subject_ds, data->m_issuer_ds); + + return data; + } + +} + +/* +* Decode the TBSCertificate data +*/ +void X509_Certificate::force_decode() + { + m_data.reset(); + + std::unique_ptr<X509_Certificate_Data> data = parse_x509_cert_body(*this); + + m_data.reset(data.release()); + } + +const X509_Certificate_Data& X509_Certificate::data() const + { + if(m_data == nullptr) + { + throw Invalid_State("X509_Certificate uninitialized"); + } + return *m_data.get(); + } + +uint32_t X509_Certificate::x509_version() const + { + return static_cast<uint32_t>(data().m_version); + } + +bool X509_Certificate::is_self_signed() const + { + return data().m_self_signed; + } + +const X509_Time& X509_Certificate::not_before() const + { + return data().m_not_before; + } + +const X509_Time& X509_Certificate::not_after() const + { + return data().m_not_after; + } + +const AlgorithmIdentifier& X509_Certificate::subject_public_key_algo() const + { + return data().m_subject_public_key_algid; + } + +const std::vector<uint8_t>& X509_Certificate::v2_issuer_key_id() const + { + return data().m_v2_issuer_key_id; + } + +const std::vector<uint8_t>& X509_Certificate::v2_subject_key_id() const + { + return data().m_v2_subject_key_id; + } + +const std::vector<uint8_t>& X509_Certificate::subject_public_key_bits() const + { + return data().m_subject_public_key_bits; + } + +const std::vector<uint8_t>& X509_Certificate::subject_public_key_info() const + { + return data().m_subject_public_key_bits_seq; + } + +const std::vector<uint8_t>& X509_Certificate::subject_public_key_bitstring() const + { + return data().m_subject_public_key_bitstring; + } + +const std::vector<uint8_t>& X509_Certificate::subject_public_key_bitstring_sha1() const + { + if(data().m_subject_public_key_bitstring_sha1.empty()) + throw Encoding_Error("X509_Certificate::subject_public_key_bitstring_sha1 called but SHA-1 disabled in build"); + + return data().m_subject_public_key_bitstring_sha1; + } + +const std::vector<uint8_t>& X509_Certificate::authority_key_id() const + { + return data().m_authority_key_id; + } + +const std::vector<uint8_t>& X509_Certificate::subject_key_id() const + { + return data().m_subject_key_id; + } + +const std::vector<uint8_t>& X509_Certificate::serial_number() const + { + return data().m_serial; + } + +bool X509_Certificate::is_serial_negative() const + { + return data().m_serial_negative; + } + + +const X509_DN& X509_Certificate::issuer_dn() const + { + return data().m_issuer_dn; + } + +const X509_DN& X509_Certificate::subject_dn() const + { + return data().m_subject_dn; + } + +const std::vector<uint8_t>& X509_Certificate::raw_issuer_dn() const + { + return data().m_issuer_dn_bits; + } + +const std::vector<uint8_t>& X509_Certificate::raw_subject_dn() const + { + return data().m_subject_dn_bits; + } + +bool X509_Certificate::is_CA_cert() const + { + if(data().m_version < 3 && data().m_self_signed) + return true; + + return data().m_is_ca_certificate; + } + +uint32_t X509_Certificate::path_limit() const + { + if(data().m_version < 3 && data().m_self_signed) + return 32; // in theory infinite, but this is more than enough + + return static_cast<uint32_t>(data().m_path_len_constraint); + } + +Key_Constraints X509_Certificate::constraints() const + { + return data().m_key_constraints; + } + +const std::vector<OID>& X509_Certificate::extended_key_usage() const + { + return data().m_extended_key_usage; + } + +const std::vector<OID>& X509_Certificate::certificate_policy_oids() const + { + return data().m_cert_policies; + } + +const NameConstraints& X509_Certificate::name_constraints() const + { + return data().m_name_constraints; + } + +const Extensions& X509_Certificate::v3_extensions() const + { + return data().m_v3_extensions; + } + +bool X509_Certificate::allowed_usage(Key_Constraints usage) const + { + if(constraints() == NO_CONSTRAINTS) + return true; + return ((constraints() & usage) == usage); + } + +bool X509_Certificate::allowed_extended_usage(const std::string& usage) const + { + return allowed_extended_usage(OID::from_string(usage)); + } + +bool X509_Certificate::allowed_extended_usage(const OID& usage) const + { + const std::vector<OID>& ex = extended_key_usage(); + if(ex.empty()) + return true; + + if(std::find(ex.begin(), ex.end(), usage) != ex.end()) + return true; + + return false; + } + +bool X509_Certificate::allowed_usage(Usage_Type usage) const + { + // These follow suggestions in RFC 5280 4.2.1.12 + + switch(usage) + { + case Usage_Type::UNSPECIFIED: + return true; + + case Usage_Type::TLS_SERVER_AUTH: + return (allowed_usage(KEY_AGREEMENT) || allowed_usage(KEY_ENCIPHERMENT) || allowed_usage(DIGITAL_SIGNATURE)) && allowed_extended_usage("PKIX.ServerAuth"); + + case Usage_Type::TLS_CLIENT_AUTH: + return (allowed_usage(DIGITAL_SIGNATURE) || allowed_usage(KEY_AGREEMENT)) && allowed_extended_usage("PKIX.ClientAuth"); + + case Usage_Type::OCSP_RESPONDER: + return (allowed_usage(DIGITAL_SIGNATURE) || allowed_usage(NON_REPUDIATION)) && allowed_extended_usage("PKIX.OCSPSigning"); + + case Usage_Type::CERTIFICATE_AUTHORITY: + return is_CA_cert(); + + case Usage_Type::ENCRYPTION: + return (allowed_usage(KEY_ENCIPHERMENT) || allowed_usage(DATA_ENCIPHERMENT)); + } + + return false; + } + +bool X509_Certificate::has_constraints(Key_Constraints constraints) const + { + if(this->constraints() == NO_CONSTRAINTS) + { + return false; + } + + return ((this->constraints() & constraints) != 0); + } + +bool X509_Certificate::has_ex_constraint(const std::string& ex_constraint) const + { + return has_ex_constraint(OID::from_string(ex_constraint)); + } + +bool X509_Certificate::has_ex_constraint(const OID& usage) const + { + const std::vector<OID>& ex = extended_key_usage(); + return (std::find(ex.begin(), ex.end(), usage) != ex.end()); + } + +/* +* Return if a certificate extension is marked critical +*/ +bool X509_Certificate::is_critical(const std::string& ex_name) const + { + return v3_extensions().critical_extension_set(OID::from_string(ex_name)); + } + +std::string X509_Certificate::ocsp_responder() const + { + return data().m_ocsp_responder; + } + +std::vector<std::string> X509_Certificate::ca_issuers() const + { + return data().m_ca_issuers; + } + +std::string X509_Certificate::crl_distribution_point() const + { + // just returns the first (arbitrarily) + if(data().m_crl_distribution_points.size() > 0) + return data().m_crl_distribution_points[0]; + return ""; + } + +const AlternativeName& X509_Certificate::subject_alt_name() const + { + return data().m_subject_alt_name; + } + +const AlternativeName& X509_Certificate::issuer_alt_name() const + { + return data().m_issuer_alt_name; + } + +/* +* Return information about the subject +*/ +std::vector<std::string> +X509_Certificate::subject_info(const std::string& req) const + { + if(req == "Email") + return this->subject_info("RFC822"); + + if(subject_dn().has_field(req)) + return subject_dn().get_attribute(req); + + if(subject_alt_name().has_field(req)) + return subject_alt_name().get_attribute(req); + + // These will be removed later: + if(req == "X509.Certificate.v2.key_id") + return {hex_encode(this->v2_subject_key_id())}; + if(req == "X509v3.SubjectKeyIdentifier") + return {hex_encode(this->subject_key_id())}; + if(req == "X509.Certificate.dn_bits") + return {hex_encode(this->raw_subject_dn())}; + if(req == "X509.Certificate.start") + return {not_before().to_string()}; + if(req == "X509.Certificate.end") + return {not_after().to_string()}; + + if(req == "X509.Certificate.version") + return {std::to_string(x509_version())}; + if(req == "X509.Certificate.serial") + return {hex_encode(serial_number())}; + + return data().m_subject_ds.get(req); + } + +/* +* Return information about the issuer +*/ +std::vector<std::string> +X509_Certificate::issuer_info(const std::string& req) const + { + if(issuer_dn().has_field(req)) + return issuer_dn().get_attribute(req); + + if(issuer_alt_name().has_field(req)) + return issuer_alt_name().get_attribute(req); + + // These will be removed later: + if(req == "X509.Certificate.v2.key_id") + return {hex_encode(this->v2_issuer_key_id())}; + if(req == "X509v3.AuthorityKeyIdentifier") + return {hex_encode(this->authority_key_id())}; + if(req == "X509.Certificate.dn_bits") + return {hex_encode(this->raw_issuer_dn())}; + + return data().m_issuer_ds.get(req); + } + +/* +* Return the public key in this certificate +*/ +std::unique_ptr<Public_Key> X509_Certificate::load_subject_public_key() const + { + try + { + return std::unique_ptr<Public_Key>(X509::load_key(subject_public_key_info())); + } + catch(std::exception& e) + { + throw Decoding_Error("X509_Certificate::load_subject_public_key", e); + } + } + +Public_Key* X509_Certificate::subject_public_key() const + { + return load_subject_public_key().release(); + } + +std::vector<uint8_t> X509_Certificate::raw_issuer_dn_sha256() const + { + if(data().m_issuer_dn_bits_sha256.empty()) + throw Encoding_Error("X509_Certificate::raw_issuer_dn_sha256 called but SHA-256 disabled in build"); + return data().m_issuer_dn_bits_sha256; + } + +std::vector<uint8_t> X509_Certificate::raw_subject_dn_sha256() const + { + if(data().m_subject_dn_bits_sha256.empty()) + throw Encoding_Error("X509_Certificate::raw_subject_dn_sha256 called but SHA-256 disabled in build"); + return data().m_subject_dn_bits_sha256; + } + +namespace { + +/* +* Lookup each OID in the vector +*/ +std::vector<std::string> lookup_oids(const std::vector<OID>& oids) + { + std::vector<std::string> out; + + for(const OID& oid : oids) + { + out.push_back(oid.to_formatted_string()); + } + return out; + } + +} + +/* +* Return the list of extended key usage OIDs +*/ +std::vector<std::string> X509_Certificate::ex_constraints() const + { + return lookup_oids(extended_key_usage()); + } + +/* +* Return the list of certificate policies +*/ +std::vector<std::string> X509_Certificate::policies() const + { + return lookup_oids(certificate_policy_oids()); + } + +std::string X509_Certificate::fingerprint(const std::string& hash_name) const + { + /* + * The SHA-1 and SHA-256 fingerprints are precomputed since these + * are the most commonly used. Especially, SHA-256 fingerprints are + * used for cycle detection during path construction. + * + * If SHA-1 or SHA-256 was missing at parsing time the vectors are + * left empty in which case we fall back to create_hex_fingerprint + * which will throw if the hash is unavailable. + */ + if(hash_name == "SHA-256" && data().m_fingerprint_sha256.size() > 0) + return data().m_fingerprint_sha256; + else if(hash_name == "SHA-1" && data().m_fingerprint_sha1.size() > 0) + return data().m_fingerprint_sha1; + else + return create_hex_fingerprint(this->BER_encode(), hash_name); + } + +bool X509_Certificate::matches_dns_name(const std::string& name) const + { + if(name.empty()) + return false; + + std::vector<std::string> issued_names = subject_info("DNS"); + + // Fall back to CN only if no DNS names are set (RFC 6125 sec 6.4.4) + if(issued_names.empty()) + issued_names = subject_info("Name"); + + for(size_t i = 0; i != issued_names.size(); ++i) + { + if(host_wildcard_match(issued_names[i], name)) + return true; + } + + return false; + } + +/* +* Compare two certificates for equality +*/ +bool X509_Certificate::operator==(const X509_Certificate& other) const + { + return (this->signature() == other.signature() && + this->signature_algorithm() == other.signature_algorithm() && + this->signed_body() == other.signed_body()); + } + +bool X509_Certificate::operator<(const X509_Certificate& other) const + { + /* If signature values are not equal, sort by lexicographic ordering of that */ + if(this->signature() != other.signature()) + { + return (this->signature() < other.signature()); + } + + // Then compare the signed contents + return this->signed_body() < other.signed_body(); + } + +/* +* X.509 Certificate Comparison +*/ +bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2) + { + return !(cert1 == cert2); + } + +std::string X509_Certificate::to_string() const + { + std::ostringstream out; + + out << "Version: " << this->x509_version() << "\n"; + out << "Subject: " << subject_dn() << "\n"; + out << "Issuer: " << issuer_dn() << "\n"; + out << "Issued: " << this->not_before().readable_string() << "\n"; + out << "Expires: " << this->not_after().readable_string() << "\n"; + + out << "Constraints:\n"; + Key_Constraints constraints = this->constraints(); + if(constraints == NO_CONSTRAINTS) + out << " None\n"; + else + { + if(constraints & DIGITAL_SIGNATURE) + out << " Digital Signature\n"; + if(constraints & NON_REPUDIATION) + out << " Non-Repudiation\n"; + if(constraints & KEY_ENCIPHERMENT) + out << " Key Encipherment\n"; + if(constraints & DATA_ENCIPHERMENT) + out << " Data Encipherment\n"; + if(constraints & KEY_AGREEMENT) + out << " Key Agreement\n"; + if(constraints & KEY_CERT_SIGN) + 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"; + } + + const std::vector<OID>& policies = this->certificate_policy_oids(); + if(!policies.empty()) + { + out << "Policies: " << "\n"; + for(auto oid : policies) + out << " " << oid.to_string() << "\n"; + } + + const std::vector<OID>& ex_constraints = this->extended_key_usage(); + if(!ex_constraints.empty()) + { + out << "Extended Constraints:\n"; + for(auto&& oid : ex_constraints) + { + out << " " << oid.to_formatted_string() << "\n"; + } + } + + const NameConstraints& name_constraints = this->name_constraints(); + + if(!name_constraints.permitted().empty() || !name_constraints.excluded().empty()) + { + out << "Name Constraints:\n"; + + if(!name_constraints.permitted().empty()) + { + out << " Permit"; + for(auto st: name_constraints.permitted()) + { + out << " " << st.base(); + } + out << "\n"; + } + + if(!name_constraints.excluded().empty()) + { + out << " Exclude"; + for(auto st: name_constraints.excluded()) + { + out << " " << st.base(); + } + out << "\n"; + } + } + + if(!ocsp_responder().empty()) + out << "OCSP responder " << ocsp_responder() << "\n"; + + const std::vector<std::string> ca_issuers = this->ca_issuers(); + if(!ca_issuers.empty()) + { + out << "CA Issuers:\n"; + for(size_t i = 0; i != ca_issuers.size(); i++) + out << " URI: " << ca_issuers[i] << "\n"; + } + + if(!crl_distribution_point().empty()) + out << "CRL " << crl_distribution_point() << "\n"; + + out << "Signature algorithm: " << this->signature_algorithm().get_oid().to_formatted_string() << "\n"; + + out << "Serial number: " << hex_encode(this->serial_number()) << "\n"; + + if(this->authority_key_id().size()) + out << "Authority keyid: " << hex_encode(this->authority_key_id()) << "\n"; + + if(this->subject_key_id().size()) + out << "Subject keyid: " << hex_encode(this->subject_key_id()) << "\n"; + + try + { + std::unique_ptr<Public_Key> pubkey(this->subject_public_key()); + out << "Public Key [" << pubkey->algo_name() << "-" << pubkey->key_length() << "]\n\n"; + out << X509::PEM_encode(*pubkey); + } + catch(Decoding_Error&) + { + const AlgorithmIdentifier& alg_id = this->subject_public_key_algo(); + out << "Failed to decode key with oid " << alg_id.get_oid().to_string() << "\n"; + } + + return out.str(); + } + +} diff --git a/comm/third_party/botan/src/lib/x509/x509cert.h b/comm/third_party/botan/src/lib/x509/x509cert.h new file mode 100644 index 0000000000..0355bbb58e --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509cert.h @@ -0,0 +1,461 @@ +/* +* X.509 Certificates +* (C) 1999-2007,2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CERTS_H_ +#define BOTAN_X509_CERTS_H_ + +#include <botan/x509_obj.h> +#include <memory> + +namespace Botan { + +class Public_Key; +class X509_DN; +class Extensions; +class AlternativeName; +class NameConstraints; + +enum class Usage_Type + { + UNSPECIFIED, // no restrictions + TLS_SERVER_AUTH, + TLS_CLIENT_AUTH, + CERTIFICATE_AUTHORITY, + OCSP_RESPONDER, + ENCRYPTION + }; + +struct X509_Certificate_Data; + +/** +* This class represents an X.509 Certificate +*/ +class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object + { + public: + /** + * Return a newly allocated copy of the public key associated + * with the subject of this certificate. This object is owned + * by the caller. + * + * Prefer load_subject_public_key in new code + * + * @return public key + */ + Public_Key* subject_public_key() const; + + /** + * Create a public key object associated with the public key bits in this + * certificate. If the public key bits was valid for X.509 encoding + * purposes but invalid algorithmically (for example, RSA with an even + * modulus) that will be detected at this point, and an exception will be + * thrown. + * + * @return subject public key of this certificate + */ + std::unique_ptr<Public_Key> load_subject_public_key() const; + + /** + * Get the public key associated with this certificate. This includes the + * outer AlgorithmIdentifier + * @return subject public key of this certificate + */ + const std::vector<uint8_t>& subject_public_key_bits() const; + + /** + * Get the SubjectPublicKeyInfo associated with this certificate. + * @return subject public key info of this certificate + */ + const std::vector<uint8_t>& subject_public_key_info() const; + + /** + * Return the algorithm identifier of the public key + */ + const AlgorithmIdentifier& subject_public_key_algo() const; + + /** + * Get the bit string of the public key associated with this certificate + * @return public key bits + */ + const std::vector<uint8_t>& subject_public_key_bitstring() const; + + /** + * Get the SHA-1 bit string of the public key associated with this certificate. + * This is used for OCSP among other protocols. + * This function will throw if SHA-1 is not available. + * @return hash of subject public key of this certificate + */ + const std::vector<uint8_t>& subject_public_key_bitstring_sha1() const; + + /** + * Get the certificate's issuer distinguished name (DN). + * @return issuer DN of this certificate + */ + const X509_DN& issuer_dn() const; + + /** + * Get the certificate's subject distinguished name (DN). + * @return subject DN of this certificate + */ + const X509_DN& subject_dn() const; + + /** + * Get a value for a specific subject_info parameter name. + * @param name the name of the parameter to look up. Possible names include + * "X509.Certificate.version", "X509.Certificate.serial", + * "X509.Certificate.start", "X509.Certificate.end", + * "X509.Certificate.v2.key_id", "X509.Certificate.public_key", + * "X509v3.BasicConstraints.path_constraint", + * "X509v3.BasicConstraints.is_ca", "X509v3.NameConstraints", + * "X509v3.ExtendedKeyUsage", "X509v3.CertificatePolicies", + * "X509v3.SubjectKeyIdentifier", "X509.Certificate.serial", + * "X520.CommonName", "X520.Organization", "X520.Country", + * "RFC822" (Email in SAN) or "PKCS9.EmailAddress" (Email in DN). + * @return value(s) of the specified parameter + */ + std::vector<std::string> subject_info(const std::string& name) const; + + /** + * Get a value for a specific subject_info parameter name. + * @param name the name of the parameter to look up. Possible names are + * "X509.Certificate.v2.key_id" or "X509v3.AuthorityKeyIdentifier". + * @return value(s) of the specified parameter + */ + std::vector<std::string> issuer_info(const std::string& name) const; + + /** + * Raw issuer DN bits + */ + const std::vector<uint8_t>& raw_issuer_dn() const; + + /** + * SHA-256 of Raw issuer DN + */ + std::vector<uint8_t> raw_issuer_dn_sha256() const; + + /** + * Raw subject DN + */ + const std::vector<uint8_t>& raw_subject_dn() const; + + /** + * SHA-256 of Raw subject DN + */ + std::vector<uint8_t> raw_subject_dn_sha256() const; + + /** + * Get the notBefore of the certificate as a string + * @return notBefore of the certificate + */ + std::string BOTAN_DEPRECATED("Use not_before().to_string()") start_time() const + { + return not_before().to_string(); + } + + /** + * Get the notAfter of the certificate as a string + * @return notAfter of the certificate + */ + std::string BOTAN_DEPRECATED("Use not_after().to_string()") end_time() const + { + return not_after().to_string(); + } + + /** + * Get the notBefore of the certificate as X509_Time + * @return notBefore of the certificate + */ + const X509_Time& not_before() const; + + /** + * Get the notAfter of the certificate as X509_Time + * @return notAfter of the certificate + */ + const X509_Time& not_after() const; + + /** + * Get the X509 version of this certificate object. + * @return X509 version + */ + uint32_t x509_version() const; + + /** + * Get the serial number of this certificate. + * @return certificates serial number + */ + const std::vector<uint8_t>& serial_number() const; + + /** + * Get the serial number's sign + * @return 1 iff the serial is negative. + */ + bool is_serial_negative() const; + + /** + * Get the DER encoded AuthorityKeyIdentifier of this certificate. + * @return DER encoded AuthorityKeyIdentifier + */ + const std::vector<uint8_t>& authority_key_id() const; + + /** + * Get the DER encoded SubjectKeyIdentifier of this certificate. + * @return DER encoded SubjectKeyIdentifier + */ + const std::vector<uint8_t>& subject_key_id() const; + + /** + * Check whether this certificate is self signed. + * If the DN issuer and subject agree, + * @return true if this certificate is self signed + */ + bool is_self_signed() const; + + /** + * Check whether this certificate is a CA certificate. + * @return true if this certificate is a CA certificate + */ + bool is_CA_cert() const; + + /** + * Returns true if the specified @param usage is set in the key usage extension + * or if no key usage constraints are set at all. + * To check if a certain key constraint is set in the certificate + * use @see X509_Certificate#has_constraints. + */ + bool allowed_usage(Key_Constraints usage) const; + + /** + * Returns true if the specified @param usage is set in the extended key usage extension + * or if no extended key usage constraints are set at all. + * To check if a certain extended key constraint is set in the certificate + * use @see X509_Certificate#has_ex_constraint. + */ + bool allowed_extended_usage(const std::string& usage) const; + + /** + * Returns true if the specified usage is set in the extended key usage extension, + * or if no extended key usage constraints are set at all. + * To check if a certain extended key constraint is set in the certificate + * use @see X509_Certificate#has_ex_constraint. + */ + bool allowed_extended_usage(const OID& usage) const; + + /** + * Returns true if the required key and extended key constraints are set in the certificate + * for the specified @param usage or if no key constraints are set in both the key usage + * and extended key usage extension. + */ + bool allowed_usage(Usage_Type usage) const; + + /** + * Returns true if the specified @param constraints are included in the key + * usage extension. + */ + bool has_constraints(Key_Constraints constraints) const; + + /** + * Returns true if and only if @param ex_constraint (referring to an + * extended key constraint, eg "PKIX.ServerAuth") is included in the + * extended key extension. + */ + bool BOTAN_DEPRECATED("Use version taking an OID") + has_ex_constraint(const std::string& ex_constraint) const; + + /** + * Returns true if and only if OID @param ex_constraint is + * included in the extended key extension. + */ + bool has_ex_constraint(const OID& ex_constraint) const; + + /** + * Get the path limit as defined in the BasicConstraints extension of + * this certificate. + * @return path limit + */ + uint32_t path_limit() const; + + /** + * Check whenever a given X509 Extension is marked critical in this + * certificate. + */ + bool is_critical(const std::string& ex_name) const; + + /** + * Get the key constraints as defined in the KeyUsage extension of this + * certificate. + * @return key constraints + */ + Key_Constraints constraints() const; + + /** + * Get the key constraints as defined in the ExtendedKeyUsage + * extension of this certificate. + * @return key constraints + */ + std::vector<std::string> + BOTAN_DEPRECATED("Use extended_key_usage") ex_constraints() const; + + /** + * Get the key usage as defined in the ExtendedKeyUsage extension + * of this certificate, or else an empty vector. + * @return key usage + */ + const std::vector<OID>& extended_key_usage() const; + + /** + * Get the name constraints as defined in the NameConstraints + * extension of this certificate. + * @return name constraints + */ + const NameConstraints& name_constraints() const; + + /** + * Get the policies as defined in the CertificatePolicies extension + * of this certificate. + * @return certificate policies + */ + std::vector<std::string> BOTAN_DEPRECATED("Use certificate_policy_oids") policies() const; + + const std::vector<OID>& certificate_policy_oids() const; + + /** + * Get all extensions of this certificate. + * @return certificate extensions + */ + const Extensions& v3_extensions() const; + + /** + * Return the v2 issuer key ID. v2 key IDs are almost never used, + * instead see v3_subject_key_id. + */ + const std::vector<uint8_t>& v2_issuer_key_id() const; + + /** + * Return the v2 subject key ID. v2 key IDs are almost never used, + * instead see v3_subject_key_id. + */ + const std::vector<uint8_t>& v2_subject_key_id() const; + + /** + * Return the subject alternative names (DNS, IP, ...) + */ + const AlternativeName& subject_alt_name() const; + + /** + * Return the issuer alternative names (DNS, IP, ...) + */ + const AlternativeName& issuer_alt_name() const; + + /** + * Return the listed address of an OCSP responder, or empty if not set + */ + std::string ocsp_responder() const; + + /** + * Return the listed addresses of ca issuers, or empty if not set + */ + std::vector<std::string> ca_issuers() const; + + /** + * Return the CRL distribution point, or empty if not set + */ + std::string crl_distribution_point() const; + + /** + * @return a free-form string describing the certificate + */ + std::string to_string() const; + + /** + * @return a fingerprint of the certificate + * @param hash_name hash function used to calculate the fingerprint + */ + std::string fingerprint(const std::string& hash_name = "SHA-1") const; + + /** + * Check if a certain DNS name matches up with the information in + * the cert + * @param name DNS name to match + */ + bool matches_dns_name(const std::string& name) const; + + /** + * Check to certificates for equality. + * @return true both certificates are (binary) equal + */ + bool operator==(const X509_Certificate& other) const; + + /** + * Impose an arbitrary (but consistent) ordering, eg to allow sorting + * a container of certificate objects. + * @return true if this is less than other by some unspecified criteria + */ + bool operator<(const X509_Certificate& other) const; + + /** + * Create a certificate from a data source providing the DER or + * PEM encoded certificate. + * @param source the data source + */ + explicit X509_Certificate(DataSource& source); + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + /** + * Create a certificate from a file containing the DER or PEM + * encoded certificate. + * @param filename the name of the certificate file + */ + explicit X509_Certificate(const std::string& filename); +#endif + + /** + * Create a certificate from a buffer + * @param in the buffer containing the DER-encoded certificate + */ + explicit X509_Certificate(const std::vector<uint8_t>& in); + + /** + * Create a certificate from a buffer + * @param data the buffer containing the DER-encoded certificate + * @param length length of data in bytes + */ + X509_Certificate(const uint8_t data[], size_t length); + + /** + * Create an uninitialized certificate object. Any attempts to + * access this object will throw an exception. + */ + X509_Certificate() = default; + + X509_Certificate(const X509_Certificate& other) = default; + + X509_Certificate& operator=(const X509_Certificate& other) = default; + + private: + std::string PEM_label() const override; + + std::vector<std::string> alternate_PEM_labels() const override; + + void force_decode() override; + + const X509_Certificate_Data& data() const; + + std::shared_ptr<X509_Certificate_Data> m_data; + }; + +/** +* Check two certificates for inequality +* @param cert1 The first certificate +* @param cert2 The second certificate +* @return true if the arguments represent different certificates, +* false if they are binary identical +*/ +BOTAN_PUBLIC_API(2,0) bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2); + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509opt.cpp b/comm/third_party/botan/src/lib/x509/x509opt.cpp new file mode 100644 index 0000000000..f762acd7b7 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509opt.cpp @@ -0,0 +1,100 @@ +/* +* X.509 Certificate Options +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509self.h> +#include <botan/parsing.h> +#include <chrono> + +namespace Botan { + +/* +* Set when the certificate should become valid +*/ +void X509_Cert_Options::not_before(const std::string& time_string) + { + start = X509_Time(time_string, ASN1_Tag::UTC_OR_GENERALIZED_TIME); + } + +/* +* Set when the certificate should expire +*/ +void X509_Cert_Options::not_after(const std::string& time_string) + { + end = X509_Time(time_string, ASN1_Tag::UTC_OR_GENERALIZED_TIME); + } + +/* +* Set key constraint information +*/ +void X509_Cert_Options::add_constraints(Key_Constraints usage) + { + constraints = usage; + } + +/* +* Set key constraint information +*/ +void X509_Cert_Options::add_ex_constraint(const OID& oid) + { + ex_constraints.push_back(oid); + } + +/* +* Set key constraint information +*/ +void X509_Cert_Options::add_ex_constraint(const std::string& oid_str) + { + ex_constraints.push_back(OID::from_string(oid_str)); + } + +/* +* Mark this certificate for CA usage +*/ +void X509_Cert_Options::CA_key(size_t limit) + { + is_CA = true; + path_limit = limit; + } + +void X509_Cert_Options::set_padding_scheme(const std::string& scheme) + { + padding_scheme = scheme; + } + +/* +* Initialize the certificate options +*/ +X509_Cert_Options::X509_Cert_Options(const std::string& initial_opts, + uint32_t expiration_time) + { + is_CA = false; + path_limit = 0; + constraints = NO_CONSTRAINTS; + // use default for chosen algorithm + padding_scheme = ""; + + auto now = std::chrono::system_clock::now(); + + start = X509_Time(now); + end = X509_Time(now + std::chrono::seconds(expiration_time)); + + if(initial_opts.empty()) + return; + + std::vector<std::string> parsed = split_on(initial_opts, '/'); + + if(parsed.size() > 4) + throw Invalid_Argument("X.509 cert options: Too many names: " + + initial_opts); + + if(parsed.size() >= 1) common_name = parsed[0]; + if(parsed.size() >= 2) country = parsed[1]; + if(parsed.size() >= 3) organization = parsed[2]; + if(parsed.size() == 4) org_unit = parsed[3]; + } + +} diff --git a/comm/third_party/botan/src/lib/x509/x509path.cpp b/comm/third_party/botan/src/lib/x509/x509path.cpp new file mode 100644 index 0000000000..b5cdc27c2d --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509path.cpp @@ -0,0 +1,1088 @@ +/* +* X.509 Certificate Path Validation +* (C) 2010,2011,2012,2014,2016 Jack Lloyd +* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509path.h> +#include <botan/x509_ext.h> +#include <botan/pk_keys.h> +#include <botan/ocsp.h> +#include <botan/oids.h> +#include <algorithm> +#include <chrono> +#include <vector> +#include <set> +#include <string> +#include <sstream> + +#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) + #include <future> + #include <botan/http_util.h> +#endif + +namespace Botan { + +/* +* PKIX path validation +*/ +CertificatePathStatusCodes +PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::chrono::system_clock::time_point ref_time, + const std::string& hostname, + Usage_Type usage, + size_t min_signature_algo_strength, + const std::set<std::string>& trusted_hashes) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_chain cert_path empty"); + + const bool self_signed_ee_cert = (cert_path.size() == 1); + + X509_Time validation_time(ref_time); + + CertificatePathStatusCodes cert_status(cert_path.size()); + + if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname)) + cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH); + + if(!cert_path[0]->allowed_usage(usage)) + cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); + + if(cert_path[0]->is_CA_cert() == false && + cert_path[0]->has_constraints(KEY_CERT_SIGN)) + { + /* + "If the keyCertSign bit is asserted, then the cA bit in the + basic constraints extension (Section 4.2.1.9) MUST also be + asserted." - RFC 5280 + + We don't bother doing this check on the rest of the path since they + must have the cA bit asserted or the validation will fail anyway. + */ + cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); + } + + for(size_t i = 0; i != cert_path.size(); ++i) + { + std::set<Certificate_Status_Code>& status = cert_status.at(i); + + const bool at_self_signed_root = (i == cert_path.size() - 1); + + const std::shared_ptr<const X509_Certificate>& subject = cert_path[i]; + + const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)]; + + if(at_self_signed_root && (issuer->is_self_signed() == false)) + { + status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT); + } + + if(subject->issuer_dn() != issuer->subject_dn()) + { + status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH); + } + + // Check the serial number + if(subject->is_serial_negative()) + { + status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE); + } + + // Check the subject's DN components' length + + for(const auto& dn_pair : subject->subject_dn().dn_info()) + { + const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first); + // dn_pair = <OID,str> + if(dn_ub > 0 && dn_pair.second.size() > dn_ub) + { + status.insert(Certificate_Status_Code::DN_TOO_LONG); + } + } + + // Check all certs for valid time range + if(validation_time < subject->not_before()) + status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID); + + if(validation_time > subject->not_after()) + status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED); + + // Check issuer constraints + if(!issuer->is_CA_cert() && !self_signed_ee_cert) + status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER); + + std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key()); + + // Check the signature algorithm is known + if(OIDS::oid2str_or_empty(subject->signature_algorithm().get_oid()).empty()) + { + status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN); + } + else + { + // only perform the following checks if the signature algorithm is known + if(!issuer_key) + { + status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID); + } + else + { + const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key); + + if(sig_status != Certificate_Status_Code::VERIFIED) + status.insert(sig_status); + + if(issuer_key->estimated_strength() < min_signature_algo_strength) + status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK); + } + + // Ignore untrusted hashes on self-signed roots + if(trusted_hashes.size() > 0 && !at_self_signed_root) + { + if(trusted_hashes.count(subject->hash_used_for_signature()) == 0) + status.insert(Certificate_Status_Code::UNTRUSTED_HASH); + } + } + + // Check cert extensions + + if(subject->x509_version() == 1) + { + if(subject->v2_issuer_key_id().empty() == false || + subject->v2_subject_key_id().empty() == false) + { + status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT); + } + } + + Extensions extensions = subject->v3_extensions(); + const auto& extensions_vec = extensions.extensions(); + if(subject->x509_version() < 3 && !extensions_vec.empty()) + { + status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT); + } + for(auto& extension : extensions_vec) + { + extension.first->validate(*subject, *issuer, cert_path, cert_status, i); + } + if(extensions.extensions().size() != extensions.get_extension_oids().size()) + { + status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION); + } + } + + // path len check + size_t max_path_length = cert_path.size(); + for(size_t i = cert_path.size() - 1; i > 0 ; --i) + { + std::set<Certificate_Status_Code>& status = cert_status.at(i); + const std::shared_ptr<const X509_Certificate>& subject = cert_path[i]; + + /* + * If the certificate was not self-issued, verify that max_path_length is + * greater than zero and decrement max_path_length by 1. + */ + if(subject->subject_dn() != subject->issuer_dn()) + { + if(max_path_length > 0) + { + --max_path_length; + } + else + { + status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG); + } + } + + /* + * If pathLenConstraint is present in the certificate and is less than max_path_length, + * set max_path_length to the value of pathLenConstraint. + */ + if(subject->path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject->path_limit() < max_path_length) + { + max_path_length = subject->path_limit(); + } + } + + return cert_status; + } + +CertificatePathStatusCodes +PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, + const std::vector<Certificate_Store*>& trusted_certstores, + std::chrono::system_clock::time_point ref_time, + std::chrono::seconds max_ocsp_age) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_ocsp cert_path empty"); + + CertificatePathStatusCodes cert_status(cert_path.size() - 1); + + for(size_t i = 0; i != cert_path.size() - 1; ++i) + { + std::set<Certificate_Status_Code>& status = cert_status.at(i); + + std::shared_ptr<const X509_Certificate> subject = cert_path.at(i); + std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1); + + if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr) + && (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) + { + try + { + Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, cert_path); + + if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK) + { + // Signature ok, so check the claimed status + Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time, max_ocsp_age); + status.insert(ocsp_status); + } + else + { + // Some signature problem + status.insert(ocsp_signature_status); + } + } + catch(Exception&) + { + status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID); + } + } + } + + while(cert_status.size() > 0 && cert_status.back().empty()) + cert_status.pop_back(); + + return cert_status; + } + +CertificatePathStatusCodes +PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<std::shared_ptr<const X509_CRL>>& crls, + std::chrono::system_clock::time_point ref_time) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_crl cert_path empty"); + + CertificatePathStatusCodes cert_status(cert_path.size()); + const X509_Time validation_time(ref_time); + + for(size_t i = 0; i != cert_path.size() - 1; ++i) + { + std::set<Certificate_Status_Code>& status = cert_status.at(i); + + if(i < crls.size() && crls.at(i)) + { + std::shared_ptr<const X509_Certificate> subject = cert_path.at(i); + std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1); + + if(!ca->allowed_usage(CRL_SIGN)) + status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER); + + if(validation_time < crls[i]->this_update()) + status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID); + + if(validation_time > crls[i]->next_update()) + status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED); + + if(crls[i]->check_signature(ca->subject_public_key()) == false) + status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE); + + status.insert(Certificate_Status_Code::VALID_CRL_CHECKED); + + if(crls[i]->is_revoked(*subject)) + status.insert(Certificate_Status_Code::CERT_IS_REVOKED); + + std::string dp = subject->crl_distribution_point(); + if(!dp.empty()) + { + if(dp != crls[i]->crl_issuing_distribution_point()) + { + status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP); + } + } + + for(const auto& extension : crls[i]->extensions().extensions()) + { + // XXX this is wrong - the OID might be defined but the extention not full parsed + // for example see #1652 + + // is the extension critical and unknown? + if(extension.second && OIDS::oid2str_or_empty(extension.first->oid_of()) == "") + { + /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the + * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid" + */ + status.insert(Certificate_Status_Code::CERT_IS_REVOKED); + } + } + + } + } + + while(cert_status.size() > 0 && cert_status.back().empty()) + cert_status.pop_back(); + + return cert_status; + } + +CertificatePathStatusCodes +PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& certstores, + std::chrono::system_clock::time_point ref_time) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_crl cert_path empty"); + + if(certstores.empty()) + throw Invalid_Argument("PKIX::check_crl certstores empty"); + + std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size()); + + for(size_t i = 0; i != cert_path.size(); ++i) + { + BOTAN_ASSERT_NONNULL(cert_path[i]); + for(size_t c = 0; c != certstores.size(); ++c) + { + crls[i] = certstores[c]->find_crl_for(*cert_path[i]); + if(crls[i]) + break; + } + } + + return PKIX::check_crl(cert_path, crls, ref_time); + } + +#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) + +CertificatePathStatusCodes +PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& trusted_certstores, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds timeout, + bool ocsp_check_intermediate_CAs, + std::chrono::seconds max_ocsp_age) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty"); + + std::vector<std::future<std::shared_ptr<const OCSP::Response>>> ocsp_response_futures; + + size_t to_ocsp = 1; + + if(ocsp_check_intermediate_CAs) + to_ocsp = cert_path.size() - 1; + if(cert_path.size() == 1) + to_ocsp = 0; + + for(size_t i = 0; i < to_ocsp; ++i) + { + const std::shared_ptr<const X509_Certificate>& subject = cert_path.at(i); + const std::shared_ptr<const X509_Certificate>& issuer = cert_path.at(i+1); + + if(subject->ocsp_responder() == "") + { + ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const OCSP::Response> { + return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OCSP_NO_REVOCATION_URL); + })); + } + else + { + ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const OCSP::Response> { + OCSP::Request req(*issuer, BigInt::decode(subject->serial_number())); + + HTTP::Response http; + try + { + http = HTTP::POST_sync(subject->ocsp_responder(), + "application/ocsp-request", + req.BER_encode(), + /*redirects*/1, + timeout); + } + catch(std::exception&) + { + // log e.what() ? + } + if (http.status_code() != 200) + return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE); + // Check the MIME type? + + return std::make_shared<const OCSP::Response>(http.body()); + })); + } + } + + std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses; + + for(size_t i = 0; i < ocsp_response_futures.size(); ++i) + { + ocsp_responses.push_back(ocsp_response_futures[i].get()); + } + + return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, max_ocsp_age); + } + +CertificatePathStatusCodes +PKIX::check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& certstores, + Certificate_Store_In_Memory* crl_store, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds timeout) + { + if(cert_path.empty()) + throw Invalid_Argument("PKIX::check_crl_online cert_path empty"); + if(certstores.empty()) + throw Invalid_Argument("PKIX::check_crl_online certstores empty"); + + std::vector<std::future<std::shared_ptr<const X509_CRL>>> future_crls; + std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size()); + + for(size_t i = 0; i != cert_path.size(); ++i) + { + const std::shared_ptr<const X509_Certificate>& cert = cert_path.at(i); + for(size_t c = 0; c != certstores.size(); ++c) + { + crls[i] = certstores[c]->find_crl_for(*cert); + if(crls[i]) + break; + } + + // TODO: check if CRL is expired and re-request? + + // Only request if we don't already have a CRL + if(crls[i]) + { + /* + We already have a CRL, so just insert this empty one to hold a place in the vector + so that indexes match up + */ + future_crls.emplace_back(std::future<std::shared_ptr<const X509_CRL>>()); + } + else if(cert->crl_distribution_point() == "") + { + // Avoid creating a thread for this case + future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const X509_CRL> { + throw Not_Implemented("No CRL distribution point for this certificate"); + })); + } + else + { + future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const X509_CRL> { + auto http = HTTP::GET_sync(cert->crl_distribution_point(), + /*redirects*/ 1, timeout); + + http.throw_unless_ok(); + // check the mime type? + return std::make_shared<const X509_CRL>(http.body()); + })); + } + } + + for(size_t i = 0; i != future_crls.size(); ++i) + { + if(future_crls[i].valid()) + { + try + { + crls[i] = future_crls[i].get(); + } + catch(std::exception&) + { + // crls[i] left null + // todo: log exception e.what() ? + } + } + } + + const CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, crls, ref_time); + + if(crl_store) + { + for(size_t i = 0; i != crl_status.size(); ++i) + { + if(crl_status[i].count(Certificate_Status_Code::VALID_CRL_CHECKED)) + { + // better be non-null, we supposedly validated it + BOTAN_ASSERT_NONNULL(crls[i]); + crl_store->add_crl(crls[i]); + } + } + } + + return crl_status; + } + +#endif + +Certificate_Status_Code +PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& trusted_certstores, + const std::shared_ptr<const X509_Certificate>& end_entity, + const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra) + { + if(end_entity->is_self_signed()) + { + return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; + } + + /* + * This is an inelegant but functional way of preventing path loops + * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate + * fingerprints in the path. If there is a duplicate, we error out. + * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc. + */ + std::set<std::string> certs_seen; + + cert_path.push_back(end_entity); + certs_seen.insert(end_entity->fingerprint("SHA-256")); + + Certificate_Store_In_Memory ee_extras; + for(size_t i = 0; i != end_entity_extra.size(); ++i) + ee_extras.add_certificate(end_entity_extra[i]); + + // iterate until we reach a root or cannot find the issuer + for(;;) + { + const X509_Certificate& last = *cert_path.back(); + const X509_DN issuer_dn = last.issuer_dn(); + const std::vector<uint8_t> auth_key_id = last.authority_key_id(); + + std::shared_ptr<const X509_Certificate> issuer; + bool trusted_issuer = false; + + for(Certificate_Store* store : trusted_certstores) + { + issuer = store->find_cert(issuer_dn, auth_key_id); + if(issuer) + { + trusted_issuer = true; + break; + } + } + + if(!issuer) + { + // fall back to searching supplemental certs + issuer = ee_extras.find_cert(issuer_dn, auth_key_id); + } + + if(!issuer) + return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND; + + const std::string fprint = issuer->fingerprint("SHA-256"); + + if(certs_seen.count(fprint) > 0) // already seen? + { + return Certificate_Status_Code::CERT_CHAIN_LOOP; + } + + certs_seen.insert(fprint); + cert_path.push_back(issuer); + + if(issuer->is_self_signed()) + { + if(trusted_issuer) + { + return Certificate_Status_Code::OK; + } + else + { + return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; + } + } + } + } + +/** + * utilities for PKIX::build_all_certificate_paths + */ +namespace +{ +// <certificate, trusted?> +using cert_maybe_trusted = std::pair<std::shared_ptr<const X509_Certificate>,bool>; +} + +/** + * Build all possible certificate paths from the end certificate to self-signed trusted roots. + * + * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found, + * one of the encountered errors is returned arbitrarily. + * + * todo add a path building function that returns detailed information on errors encountered while building + * the potentially numerous path candidates. + * + * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS. + * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate + * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we + * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN + + * authority key id need not be unique among the certificates used for building the path. In such a case, + * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them. + * + */ +Certificate_Status_Code +PKIX::build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths_out, + const std::vector<Certificate_Store*>& trusted_certstores, + const std::shared_ptr<const X509_Certificate>& end_entity, + const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra) + { + if(!cert_paths_out.empty()) + { + throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty"); + } + + if(end_entity->is_self_signed()) + { + return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST; + } + + /* + * Pile up error messages + */ + std::vector<Certificate_Status_Code> stats; + + Certificate_Store_In_Memory ee_extras; + for(size_t i = 0; i != end_entity_extra.size(); ++i) + { + ee_extras.add_certificate(end_entity_extra[i]); + } + + /* + * This is an inelegant but functional way of preventing path loops + * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate + * fingerprints in the path. If there is a duplicate, we error out. + * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc. + */ + std::set<std::string> certs_seen; + + // new certs are added and removed from the path during the DFS + // it is copied into cert_paths_out when we encounter a trusted root + std::vector<std::shared_ptr<const X509_Certificate>> path_so_far; + + // todo can we assume that the end certificate is not trusted? + std::vector<cert_maybe_trusted> stack = { {end_entity, false} }; + + while(!stack.empty()) + { + // found a deletion marker that guides the DFS, backtracing + if(stack.back().first == nullptr) + { + stack.pop_back(); + std::string fprint = path_so_far.back()->fingerprint("SHA-256"); + certs_seen.erase(fprint); + path_so_far.pop_back(); + } + // process next cert on the path + else + { + std::shared_ptr<const X509_Certificate> last = stack.back().first; + bool trusted = stack.back().second; + stack.pop_back(); + + // certificate already seen? + const std::string fprint = last->fingerprint("SHA-256"); + if(certs_seen.count(fprint) == 1) + { + stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP); + // the current path ended in a loop + continue; + } + + // the current path ends here + if(last->is_self_signed()) + { + // found a trust anchor + if(trusted) + { + cert_paths_out.push_back(path_so_far); + cert_paths_out.back().push_back(last); + + continue; + } + // found an untrustworthy root + else + { + stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); + continue; + } + } + + const X509_DN issuer_dn = last->issuer_dn(); + const std::vector<uint8_t> auth_key_id = last->authority_key_id(); + + // search for trusted issuers + std::vector<std::shared_ptr<const X509_Certificate>> trusted_issuers; + for(Certificate_Store* store : trusted_certstores) + { + auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id); + trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end()); + } + + // search the supplemental certs + std::vector<std::shared_ptr<const X509_Certificate>> misc_issuers = + ee_extras.find_all_certs(issuer_dn, auth_key_id); + + // if we could not find any issuers, the current path ends here + if(trusted_issuers.size() + misc_issuers.size() == 0) + { + stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND); + continue; + } + + // push the latest certificate onto the path_so_far + path_so_far.push_back(last); + certs_seen.emplace(fprint); + + // push a deletion marker on the stack for backtracing later + stack.push_back({std::shared_ptr<const X509_Certificate>(nullptr),false}); + + for(const auto& trusted_cert : trusted_issuers) + { + stack.push_back({trusted_cert,true}); + } + + for(const auto& misc : misc_issuers) + { + stack.push_back({misc,false}); + } + } + } + + // could not construct any potentially valid path + if(cert_paths_out.empty()) + { + if(stats.empty()) + throw Internal_Error("X509 path building failed for unknown reasons"); + else + // arbitrarily return the first error + return stats[0]; + } + else + { + return Certificate_Status_Code::OK; + } + } + + +void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status, + const CertificatePathStatusCodes& crl, + const CertificatePathStatusCodes& ocsp, + bool require_rev_on_end_entity, + bool require_rev_on_intermediates) + { + if(chain_status.empty()) + throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty"); + + for(size_t i = 0; i != chain_status.size() - 1; ++i) + { + bool had_crl = false, had_ocsp = false; + + if(i < crl.size() && crl[i].size() > 0) + { + for(auto&& code : crl[i]) + { + if(code == Certificate_Status_Code::VALID_CRL_CHECKED) + { + had_crl = true; + } + chain_status[i].insert(code); + } + } + + if(i < ocsp.size() && ocsp[i].size() > 0) + { + for(auto&& code : ocsp[i]) + { + if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD || + code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL || // softfail + code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) // softfail + { + had_ocsp = true; + } + + chain_status[i].insert(code); + } + } + + if(had_crl == false && had_ocsp == false) + { + if((require_rev_on_end_entity && i == 0) || + (require_rev_on_intermediates && i > 0)) + { + chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA); + } + } + } + } + +Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) + { + if(cert_status.empty()) + throw Invalid_Argument("PKIX::overall_status empty cert status"); + + Certificate_Status_Code overall_status = Certificate_Status_Code::OK; + + // take the "worst" error as overall + for(const std::set<Certificate_Status_Code>& s : cert_status) + { + if(!s.empty()) + { + auto worst = *s.rbegin(); + // Leave informative OCSP/CRL confirmations on cert-level status only + if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) + { + overall_status = worst; + } + } + } + return overall_status; + } + +Path_Validation_Result x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& trusted_roots, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds ocsp_timeout, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) + { + if(end_certs.empty()) + { + throw Invalid_Argument("x509_path_validate called with no subjects"); + } + + std::shared_ptr<const X509_Certificate> end_entity(std::make_shared<const X509_Certificate>(end_certs[0])); + std::vector<std::shared_ptr<const X509_Certificate>> end_entity_extra; + for(size_t i = 1; i < end_certs.size(); ++i) + { + end_entity_extra.push_back(std::make_shared<const X509_Certificate>(end_certs[i])); + } + + std::vector<std::vector<std::shared_ptr<const X509_Certificate>>> cert_paths; + Certificate_Status_Code path_building_result = PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra); + + // If we cannot successfully build a chain to a trusted self-signed root, stop now + if(path_building_result != Certificate_Status_Code::OK) + { + return Path_Validation_Result(path_building_result); + } + + std::vector<Path_Validation_Result> error_results; + // Try validating all the potentially valid paths and return the first one to validate properly + for(auto cert_path : cert_paths) + { + CertificatePathStatusCodes status = + PKIX::check_chain(cert_path, ref_time, + hostname, usage, + restrictions.minimum_key_strength(), + restrictions.trusted_hashes()); + + CertificatePathStatusCodes crl_status = + PKIX::check_crl(cert_path, trusted_roots, ref_time); + + CertificatePathStatusCodes ocsp_status; + + if(ocsp_resp.size() > 0) + { + ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions.max_ocsp_age()); + } + + if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) + { +#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL) + ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, + ocsp_timeout, restrictions.ocsp_all_intermediates()); +#else + ocsp_status.resize(1); + ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP); +#endif + } + + PKIX::merge_revocation_status(status, crl_status, ocsp_status, + restrictions.require_revocation_information(), + restrictions.ocsp_all_intermediates()); + + Path_Validation_Result pvd(status, std::move(cert_path)); + if(pvd.successful_validation()) + { + return pvd; + } + else + { + error_results.push_back(std::move(pvd)); + } + } + return error_results[0]; + } + +Path_Validation_Result x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& trusted_roots, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point when, + std::chrono::milliseconds ocsp_timeout, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) + { + std::vector<X509_Certificate> certs; + certs.push_back(end_cert); + return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); + } + +Path_Validation_Result x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const Certificate_Store& store, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point when, + std::chrono::milliseconds ocsp_timeout, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) + { + std::vector<Certificate_Store*> trusted_roots; + trusted_roots.push_back(const_cast<Certificate_Store*>(&store)); + + return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); + } + +Path_Validation_Result x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const Certificate_Store& store, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point when, + std::chrono::milliseconds ocsp_timeout, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp) + { + std::vector<X509_Certificate> certs; + certs.push_back(end_cert); + + std::vector<Certificate_Store*> trusted_roots; + trusted_roots.push_back(const_cast<Certificate_Store*>(&store)); + + return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp); + } + +Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, + size_t key_strength, + bool ocsp_intermediates, + std::chrono::seconds max_ocsp_age) : + m_require_revocation_information(require_rev), + m_ocsp_all_intermediates(ocsp_intermediates), + m_minimum_key_strength(key_strength), + m_max_ocsp_age(max_ocsp_age) + { + if(key_strength <= 80) + { m_trusted_hashes.insert("SHA-160"); } + + m_trusted_hashes.insert("SHA-224"); + m_trusted_hashes.insert("SHA-256"); + m_trusted_hashes.insert("SHA-384"); + m_trusted_hashes.insert("SHA-512"); + } + +namespace { +CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) + { + CertificatePathStatusCodes warnings; + for(const auto& status_set_i : all_statuses) + { + std::set<Certificate_Status_Code> warning_set_i; + for(const auto& code : status_set_i) + { + if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS && + code < Certificate_Status_Code::FIRST_ERROR_STATUS) + { + warning_set_i.insert(code); + } + } + warnings.push_back(warning_set_i); + } + return warnings; + } +} + +Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status, + std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) : + m_all_status(status), + m_warnings(find_warnings(m_all_status)), + m_cert_path(cert_chain), + m_overall(PKIX::overall_status(m_all_status)) + { + } + +const X509_Certificate& Path_Validation_Result::trust_root() const + { + if(m_cert_path.empty()) + throw Invalid_State("Path_Validation_Result::trust_root no path set"); + if(result() != Certificate_Status_Code::VERIFIED) + throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status"); + + return *m_cert_path[m_cert_path.size()-1]; + } + +std::set<std::string> Path_Validation_Result::trusted_hashes() const + { + std::set<std::string> hashes; + for(size_t i = 0; i != m_cert_path.size(); ++i) + hashes.insert(m_cert_path[i]->hash_used_for_signature()); + return hashes; + } + +bool Path_Validation_Result::successful_validation() const + { + return (result() == Certificate_Status_Code::VERIFIED || + result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD || + result() == Certificate_Status_Code::VALID_CRL_CHECKED); + } + +bool Path_Validation_Result::no_warnings() const + { + for(auto status_set_i : m_warnings) + if(!status_set_i.empty()) + return false; + return true; + } + +CertificatePathStatusCodes Path_Validation_Result::warnings() const + { + return m_warnings; + } + +std::string Path_Validation_Result::result_string() const + { + return status_string(result()); + } + +const char* Path_Validation_Result::status_string(Certificate_Status_Code code) + { + if(const char* s = to_string(code)) + return s; + + return "Unknown error"; + } + +std::string Path_Validation_Result::warnings_string() const + { + const std::string sep(", "); + std::string res; + for(size_t i = 0; i < m_warnings.size(); i++) + { + for(auto code : m_warnings[i]) + res += "[" + std::to_string(i) + "] " + status_string(code) + sep; + } + // remove last sep + if(res.size() >= sep.size()) + res = res.substr(0, res.size() - sep.size()); + return res; + } +} diff --git a/comm/third_party/botan/src/lib/x509/x509path.h b/comm/third_party/botan/src/lib/x509/x509path.h new file mode 100644 index 0000000000..c8575fd32b --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509path.h @@ -0,0 +1,475 @@ +/* +* X.509 Cert Path Validation +* (C) 2010-2011 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CERT_PATH_VALIDATION_H_ +#define BOTAN_X509_CERT_PATH_VALIDATION_H_ + +#include <botan/pkix_enums.h> +#include <botan/x509cert.h> +#include <botan/certstor.h> +#include <botan/ocsp.h> +#include <functional> +#include <set> +#include <chrono> + +#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL) + #define BOTAN_HAS_ONLINE_REVOCATION_CHECKS +#endif + +namespace Botan { + +/** +* This type represents the validation status of an entire certificate path. +* There is one set of status codes for each certificate in the path. +*/ +typedef std::vector<std::set<Certificate_Status_Code>> CertificatePathStatusCodes; + +/** +* Specifies restrictions on the PKIX path validation +*/ +class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final + { + public: + /** + * @param require_rev if true, revocation information is required + + * @param minimum_key_strength is the minimum strength (in terms of + * operations, eg 80 means 2^80) of a signature. Signatures weaker than + * this are rejected. If more than 80, SHA-1 signatures are also + * rejected. If possible use at least setting 110. + * + * 80 bit strength requires 1024 bit RSA + * 110 bit strength requires 2k bit RSA + * 128 bit strength requires ~3k bit RSA or P-256 + * @param ocsp_all_intermediates Make OCSP requests for all CAs as + * well as end entity (if OCSP enabled in path validation request) + * @param max_ocsp_age maximum age of OCSP responses w/o next_update. + * If zero, there is no maximum age + */ + Path_Validation_Restrictions(bool require_rev = false, + size_t minimum_key_strength = 110, + bool ocsp_all_intermediates = false, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + + /** + * @param require_rev if true, revocation information is required + * @param minimum_key_strength is the minimum strength (in terms of + * operations, eg 80 means 2^80) of a signature. Signatures + * weaker than this are rejected. + * @param ocsp_all_intermediates Make OCSP requests for all CAs as + * well as end entity (if OCSP enabled in path validation request) + * @param trusted_hashes a set of trusted hashes. Any signatures + * created using a hash other than one of these will be + * rejected. + * @param max_ocsp_age maximum age of OCSP responses w/o next_update. + * If zero, there is no maximum age + */ + Path_Validation_Restrictions(bool require_rev, + size_t minimum_key_strength, + bool ocsp_all_intermediates, + const std::set<std::string>& trusted_hashes, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()) : + m_require_revocation_information(require_rev), + m_ocsp_all_intermediates(ocsp_all_intermediates), + m_trusted_hashes(trusted_hashes), + m_minimum_key_strength(minimum_key_strength), + m_max_ocsp_age(max_ocsp_age) {} + + /** + * @return whether revocation information is required + */ + bool require_revocation_information() const + { return m_require_revocation_information; } + + /** + * @return whether all intermediate CAs should also be OCSPed. If false + * then only end entity OCSP is required/requested. + */ + bool ocsp_all_intermediates() const + { return m_ocsp_all_intermediates; } + + /** + * @return trusted signature hash functions + */ + const std::set<std::string>& trusted_hashes() const + { return m_trusted_hashes; } + + /** + * @return minimum required key strength + */ + size_t minimum_key_strength() const + { return m_minimum_key_strength; } + + /** + * @return maximum age of OCSP responses w/o next_update. + * If zero, there is no maximum age + */ + std::chrono::seconds max_ocsp_age() const + { return m_max_ocsp_age; } + + private: + bool m_require_revocation_information; + bool m_ocsp_all_intermediates; + std::set<std::string> m_trusted_hashes; + size_t m_minimum_key_strength; + std::chrono::seconds m_max_ocsp_age; + }; + +/** +* Represents the result of a PKIX path validation +*/ +class BOTAN_PUBLIC_API(2,0) Path_Validation_Result final + { + public: + typedef Certificate_Status_Code Code; + + /** + * @return the set of hash functions you are implicitly + * trusting by trusting this result. + */ + std::set<std::string> trusted_hashes() const; + + /** + * @return the trust root of the validation if successful + * throws an exception if the validation failed + */ + const X509_Certificate& trust_root() const; + + /** + * @return the full path from subject to trust root + * This path may be empty + */ + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path() const { return m_cert_path; } + + /** + * @return true iff the validation was successful + */ + bool successful_validation() const; + + /** + * @return true iff no warnings occured during validation + */ + bool no_warnings() const; + + /** + * @return overall validation result code + */ + Certificate_Status_Code result() const { return m_overall; } + + /** + * @return a set of status codes for each certificate in the chain + */ + const CertificatePathStatusCodes& all_statuses() const + { return m_all_status; } + + /** + * @return the subset of status codes that are warnings + */ + CertificatePathStatusCodes warnings() const; + + /** + * @return string representation of the validation result + */ + std::string result_string() const; + + /** + * @return string representation of the warnings + */ + std::string warnings_string() const; + + /** + * @param code validation status code + * @return corresponding validation status message + */ + static const char* status_string(Certificate_Status_Code code); + + /** + * Create a Path_Validation_Result + * @param status list of validation status codes + * @param cert_chain the certificate chain that was validated + */ + Path_Validation_Result(CertificatePathStatusCodes status, + std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain); + + /** + * Create a Path_Validation_Result + * @param status validation status code + */ + explicit Path_Validation_Result(Certificate_Status_Code status) : m_overall(status) {} + + private: + CertificatePathStatusCodes m_all_status; + CertificatePathStatusCodes m_warnings; + std::vector<std::shared_ptr<const X509_Certificate>> m_cert_path; + Certificate_Status_Code m_overall; + }; + +/** +* PKIX Path Validation +* @param end_certs certificate chain to validate (with end entity certificate in end_certs[0]) +* @param restrictions path validation restrictions +* @param trusted_roots list of certificate stores that contain trusted certificates +* @param hostname if not empty, compared against the DNS name in end_certs[0] +* @param usage if not set to UNSPECIFIED, compared against the key usage in end_certs[0] +* @param validation_time what reference time to use for validation +* @param ocsp_timeout timeout for OCSP operations, 0 disables OCSP check +* @param ocsp_resp additional OCSP responses to consider (eg from peer) +* @return result of the path validation +* note: when enabled, OCSP check is softfail by default: if the OCSP server is not +* reachable, Path_Validation_Result::successful_validation() will return true. +* Hardfail OCSP check can be achieve by also calling Path_Validation_Result::no_warnings(). +*/ +Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& trusted_roots, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0), + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp = {}); + +/** +* PKIX Path Validation +* @param end_cert certificate to validate +* @param restrictions path validation restrictions +* @param trusted_roots list of stores that contain trusted certificates +* @param hostname if not empty, compared against the DNS name in end_cert +* @param usage if not set to UNSPECIFIED, compared against the key usage in end_cert +* @param validation_time what reference time to use for validation +* @param ocsp_timeout timeout for OCSP operations, 0 disables OCSP check +* @param ocsp_resp additional OCSP responses to consider (eg from peer) +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& trusted_roots, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0), + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp = {}); + +/** +* PKIX Path Validation +* @param end_cert certificate to validate +* @param restrictions path validation restrictions +* @param store store that contains trusted certificates +* @param hostname if not empty, compared against the DNS name in end_cert +* @param usage if not set to UNSPECIFIED, compared against the key usage in end_cert +* @param validation_time what reference time to use for validation +* @param ocsp_timeout timeout for OCSP operations, 0 disables OCSP check +* @param ocsp_resp additional OCSP responses to consider (eg from peer) +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const Certificate_Store& store, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0), + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp = {}); + +/** +* PKIX Path Validation +* @param end_certs certificate chain to validate +* @param restrictions path validation restrictions +* @param store store that contains trusted certificates +* @param hostname if not empty, compared against the DNS name in end_certs[0] +* @param usage if not set to UNSPECIFIED, compared against the key usage in end_certs[0] +* @param validation_time what reference time to use for validation +* @param ocsp_timeout timeout for OCSP operations, 0 disables OCSP check +* @param ocsp_resp additional OCSP responses to consider (eg from peer) +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const Certificate_Store& store, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0), + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp = {}); + + +/** +* namespace PKIX holds the building blocks that are called by x509_path_validate. +* This allows custom validation logic to be written by applications and makes +* for easier testing, but unless you're positive you know what you're doing you +* probably want to just call x509_path_validate instead. +*/ +namespace PKIX { + +Certificate_Status_Code +build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths, + const std::vector<Certificate_Store*>& trusted_certstores, + const std::shared_ptr<const X509_Certificate>& end_entity, + const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra); + + +/** +* Build certificate path +* @param cert_path_out output parameter, cert_path will be appended to this vector +* @param trusted_certstores list of certificate stores that contain trusted certificates +* @param end_entity the cert to be validated +* @param end_entity_extra optional list of additional untrusted certs for path building +* @return result of the path building operation (OK or error) +*/ +Certificate_Status_Code +BOTAN_PUBLIC_API(2,0) build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path_out, + const std::vector<Certificate_Store*>& trusted_certstores, + const std::shared_ptr<const X509_Certificate>& end_entity, + const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra); + +/** +* Check the certificate chain, but not any revocation data +* +* @param cert_path path built by build_certificate_path with OK result +* @param ref_time whatever time you want to perform the validation +* against (normally current system clock) +* @param hostname the hostname +* @param usage end entity usage checks +* @param min_signature_algo_strength 80 or 110 typically +* Note 80 allows 1024 bit RSA and SHA-1. 110 allows 2048 bit RSA and SHA-2. +* Using 128 requires ECC (P-256) or ~3000 bit RSA keys. +* @param trusted_hashes set of trusted hash functions, empty means accept any +* hash we have an OID for +* @return vector of results on per certificate in the path, each containing a set of +* results. If all codes in the set are < Certificate_Status_Code::FIRST_ERROR_STATUS, +* then the result for that certificate is successful. If all results are +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2,0) check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + std::chrono::system_clock::time_point ref_time, + const std::string& hostname, + Usage_Type usage, + size_t min_signature_algo_strength, + const std::set<std::string>& trusted_hashes); + +/** +* Check OCSP responses for revocation information +* @param cert_path path already validated by check_chain +* @param ocsp_responses the OCSP responses to consider +* @param certstores trusted roots +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @param max_ocsp_age maximum age of OCSP responses w/o next_update. If zero, +* there is no maximum age +* @return revocation status +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2, 0) check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, + const std::vector<Certificate_Store*>& certstores, + std::chrono::system_clock::time_point ref_time, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + +/** +* Check CRLs for revocation information +* @param cert_path path already validated by check_chain +* @param crls the list of CRLs to check, it is assumed that crls[i] (if not null) +* is the associated CRL for the subject in cert_path[i]. +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @return revocation status +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2,0) check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<std::shared_ptr<const X509_CRL>>& crls, + std::chrono::system_clock::time_point ref_time); + +/** +* Check CRLs for revocation information +* @param cert_path path already validated by check_chain +* @param certstores a list of certificate stores to query for the CRL +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @return revocation status +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2,0) check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& certstores, + std::chrono::system_clock::time_point ref_time); + +#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) + +/** +* Check OCSP using online (HTTP) access. Current version creates a thread and +* network connection per OCSP request made. +* +* @param cert_path path already validated by check_chain +* @param trusted_certstores a list of certstores with trusted certs +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @param timeout for timing out the responses, though actually this function +* may block for up to timeout*cert_path.size()*C for some small C. +* @param ocsp_check_intermediate_CAs if true also performs OCSP on any intermediate +* CA certificates. If false, only does OCSP on the end entity cert. +* @param max_ocsp_age maximum age of OCSP responses w/o next_update. If zero, +* there is no maximum age +* @return revocation status +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2, 0) check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& trusted_certstores, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds timeout, + bool ocsp_check_intermediate_CAs, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + +/** +* Check CRL using online (HTTP) access. Current version creates a thread and +* network connection per CRL access. + +* @param cert_path path already validated by check_chain +* @param trusted_certstores a list of certstores with trusted certs +* @param certstore_to_recv_crls optional (nullptr to disable), all CRLs +* retreived will be saved to this cert store. +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @param timeout for timing out the responses, though actually this function +* may block for up to timeout*cert_path.size()*C for some small C. +* @return revocation status +*/ +CertificatePathStatusCodes +BOTAN_PUBLIC_API(2,0) check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& trusted_certstores, + Certificate_Store_In_Memory* certstore_to_recv_crls, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds timeout); + +#endif + +/** +* Find overall status (OK, error) of a validation +* @param cert_status result of merge_revocation_status or check_chain +*/ +Certificate_Status_Code BOTAN_PUBLIC_API(2,0) overall_status(const CertificatePathStatusCodes& cert_status); + +/** +* Merge the results from CRL and/or OCSP checks into chain_status +* @param chain_status the certificate status +* @param crl_status results from check_crl +* @param ocsp_status results from check_ocsp +* @param require_rev_on_end_entity require valid CRL or OCSP on end-entity cert +* @param require_rev_on_intermediates require valid CRL or OCSP on all intermediate certificates +*/ +void BOTAN_PUBLIC_API(2,0) merge_revocation_status(CertificatePathStatusCodes& chain_status, + const CertificatePathStatusCodes& crl_status, + const CertificatePathStatusCodes& ocsp_status, + bool require_rev_on_end_entity, + bool require_rev_on_intermediates); + +} + +} + +#endif diff --git a/comm/third_party/botan/src/lib/x509/x509self.cpp b/comm/third_party/botan/src/lib/x509/x509self.cpp new file mode 100644 index 0000000000..f75827fd83 --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509self.cpp @@ -0,0 +1,152 @@ +/* +* PKCS #10/Self Signed Cert Creation +* (C) 1999-2008,2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509self.h> +#include <botan/x509_key.h> +#include <botan/x509_ext.h> +#include <botan/x509_ca.h> +#include <botan/der_enc.h> +#include <botan/pubkey.h> +#include <botan/hash.h> + +namespace Botan { + +namespace { + +/* +* Load information from the X509_Cert_Options +*/ +void load_info(const X509_Cert_Options& opts, X509_DN& subject_dn, + AlternativeName& subject_alt) + { + subject_dn.add_attribute("X520.CommonName", opts.common_name); + subject_dn.add_attribute("X520.Country", opts.country); + subject_dn.add_attribute("X520.State", opts.state); + subject_dn.add_attribute("X520.Locality", opts.locality); + subject_dn.add_attribute("X520.Organization", opts.organization); + subject_dn.add_attribute("X520.OrganizationalUnit", opts.org_unit); + for(auto extra_ou : opts.more_org_units) { + subject_dn.add_attribute("X520.OrganizationalUnit", extra_ou); + } + + subject_dn.add_attribute("X520.SerialNumber", opts.serial_number); + subject_alt = AlternativeName(opts.email, opts.uri, opts.dns, opts.ip); + subject_alt.add_othername(OID::from_string("PKIX.XMPPAddr"), + opts.xmpp, UTF8_STRING); + + for(auto dns : opts.more_dns) + subject_alt.add_attribute("DNS", dns); + } +} + +namespace X509 { + +/* +* Create a new self-signed X.509 certificate +*/ +X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng) + { + AlgorithmIdentifier sig_algo; + 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} }; + + const std::vector<uint8_t> pub_key = X509::BER_encode(key); + std::unique_ptr<PK_Signer> signer(choose_sig_format(key, sig_opts, rng, hash_fn, sig_algo)); + BOTAN_ASSERT_NOMSG(sig_algo.get_oid().has_value()); + load_info(opts, subject_dn, subject_alt); + + Extensions extensions = opts.extensions; + + 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.add_new( + new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit), + true); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add_new(new Cert_Extension::Key_Usage(constraints), true); + } + + std::unique_ptr<Cert_Extension::Subject_Key_ID> skid(new Cert_Extension::Subject_Key_ID(pub_key, hash_fn)); + + extensions.add_new(new Cert_Extension::Authority_Key_ID(skid->get_key_id())); + extensions.add_new(skid.release()); + + extensions.add_new( + new Cert_Extension::Subject_Alternative_Name(subject_alt)); + + extensions.add_new( + new Cert_Extension::Extended_Key_Usage(opts.ex_constraints)); + + return X509_CA::make_cert(signer.get(), rng, sig_algo, pub_key, + opts.start, opts.end, + subject_dn, subject_dn, + extensions); + } + +/* +* Create a PKCS #10 certificate request +*/ +PKCS10_Request create_cert_req(const X509_Cert_Options& opts, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng) + { + X509_DN subject_dn; + AlternativeName subject_alt; + load_info(opts, subject_dn, subject_alt); + + 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 = opts.extensions; + + extensions.add_new(new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add_new(new Cert_Extension::Key_Usage(constraints)); + } + extensions.add_new(new Cert_Extension::Extended_Key_Usage(opts.ex_constraints)); + extensions.add_new(new Cert_Extension::Subject_Alternative_Name(subject_alt)); + + return PKCS10_Request::create(key, + subject_dn, + extensions, + hash_fn, + rng, + opts.padding_scheme, + opts.challenge); + } + +} + +} diff --git a/comm/third_party/botan/src/lib/x509/x509self.h b/comm/third_party/botan/src/lib/x509/x509self.h new file mode 100644 index 0000000000..27c30b12dc --- /dev/null +++ b/comm/third_party/botan/src/lib/x509/x509self.h @@ -0,0 +1,222 @@ +/* +* X.509 Self-Signed Certificate +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_SELF_H_ +#define BOTAN_X509_SELF_H_ + +#include <botan/x509cert.h> +#include <botan/pkcs10.h> +#include <botan/pkix_types.h> + +namespace Botan { + +class RandomNumberGenerator; +class Private_Key; + +/** +* Options for X.509 certificates. +*/ +class BOTAN_PUBLIC_API(2,0) X509_Cert_Options final + { + public: + /** + * the subject common name + */ + std::string common_name; + + /** + * the subject counry + */ + std::string country; + + /** + * the subject organization + */ + std::string organization; + + /** + * the subject organizational unit + */ + std::string org_unit; + + /** + * additional subject organizational units. + */ + std::vector<std::string> more_org_units; + + /** + * the subject locality + */ + std::string locality; + + /** + * the subject state + */ + std::string state; + + /** + * the subject serial number + */ + std::string serial_number; + + /** + * the subject email adress + */ + std::string email; + + /** + * the subject URI + */ + std::string uri; + + /** + * the subject IPv4 address + */ + std::string ip; + + /** + * the subject DNS + */ + std::string dns; + + /** + * additional subject DNS entries. + */ + std::vector<std::string> more_dns; + + /** + * the subject XMPP + */ + std::string xmpp; + + /** + * the subject challenge password + */ + std::string challenge; + + /** + * the subject notBefore + */ + X509_Time start; + /** + * the subject notAfter + */ + X509_Time end; + + /** + * Indicates whether the certificate request + */ + bool is_CA; + + /** + * Indicates the BasicConstraints path limit + */ + size_t path_limit; + + std::string padding_scheme; + + /** + * The key constraints for the subject public key + */ + Key_Constraints constraints; + + /** + * The key extended constraints for the subject public key + */ + std::vector<OID> ex_constraints; + + /** + * Additional X.509 extensions + */ + Extensions extensions; + + /** + * Mark the certificate as a CA certificate and set the path limit. + * @param limit the path limit to be set in the BasicConstraints extension. + */ + 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 + */ + void not_before(const std::string& time); + + /** + * Set the notAfter of the certificate. + * @param time the notAfter value of the certificate + */ + void not_after(const std::string& time); + + /** + * Add the key constraints of the KeyUsage extension. + * @param constr the constraints to set + */ + void add_constraints(Key_Constraints constr); + + /** + * Add constraints to the ExtendedKeyUsage extension. + * @param oid the oid to add + */ + void add_ex_constraint(const OID& oid); + + /** + * Add constraints to the ExtendedKeyUsage extension. + * @param name the name to look up the oid to add + */ + void add_ex_constraint(const std::string& name); + + /** + * Construct a new options object + * @param opts define the common name of this object. An example for this + * parameter would be "common_name/country/organization/organizational_unit". + * @param expire_time the expiration time (from the current clock in seconds) + */ + X509_Cert_Options(const std::string& opts = "", + uint32_t expire_time = 365 * 24 * 60 * 60); + }; + +namespace X509 { + +/** +* Create a self-signed X.509 certificate. +* @param opts the options defining the certificate to create +* @param key the private key used for signing, i.e. the key +* associated with this self-signed certificate +* @param hash_fn the hash function to use +* @param rng the rng to use +* @return newly created self-signed certificate +*/ +BOTAN_PUBLIC_API(2,0) X509_Certificate +create_self_signed_cert(const X509_Cert_Options& opts, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng); + +/** +* Create a PKCS#10 certificate request. +* @param opts the options defining the request to create +* @param key the key used to sign this request +* @param rng the rng to use +* @param hash_fn the hash function to use +* @return newly created PKCS#10 request +*/ +BOTAN_PUBLIC_API(2,0) PKCS10_Request create_cert_req(const X509_Cert_Options& opts, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng); + +} + +} + +#endif |