aboutsummaryrefslogtreecommitdiffstats
path: root/src/cert/x509path/x509path.cpp
diff options
context:
space:
mode:
authorlloyd <[email protected]>2010-11-01 19:32:20 +0000
committerlloyd <[email protected]>2010-11-01 19:32:20 +0000
commitd8d5ce7a524820b462d192310db1e50add1f4702 (patch)
tree5b23b4c09fb9b8e15415d393cf8d3c73f51ca9b9 /src/cert/x509path/x509path.cpp
parent05edb9f3e52ce18d0833d612fcfebf1442f859ea (diff)
New branch for splitting up path validation vs certificate storage
Diffstat (limited to 'src/cert/x509path/x509path.cpp')
-rw-r--r--src/cert/x509path/x509path.cpp660
1 files changed, 660 insertions, 0 deletions
diff --git a/src/cert/x509path/x509path.cpp b/src/cert/x509path/x509path.cpp
new file mode 100644
index 000000000..f3fd091cb
--- /dev/null
+++ b/src/cert/x509path/x509path.cpp
@@ -0,0 +1,660 @@
+/*
+* X.509 Certificate Store
+* (C) 1999-2007 Jack Lloyd
+*
+* Distributed under the terms of the Botan license
+*/
+
+#include <botan/x509path.h>
+#include <botan/parsing.h>
+#include <botan/pubkey.h>
+#include <botan/oids.h>
+#include <botan/time.h>
+#include <algorithm>
+#include <memory>
+
+namespace Botan {
+
+namespace {
+
+/*
+* Do a validity check
+*/
+s32bit validity_check(const X509_Time& start, const X509_Time& end,
+ u64bit current_time, u32bit slack)
+ {
+ const s32bit NOT_YET_VALID = -1, VALID_TIME = 0, EXPIRED = 1;
+
+ if(start.cmp(current_time + slack) > 0)
+ return NOT_YET_VALID;
+ if(end.cmp(current_time - slack) < 0)
+ return EXPIRED;
+ return VALID_TIME;
+ }
+
+/*
+* Compare the value of unique ID fields
+*/
+bool compare_ids(const MemoryVector<byte>& id1,
+ const MemoryVector<byte>& id2)
+ {
+ if(!id1.size() || !id2.size())
+ return true;
+ return (id1 == id2);
+ }
+
+/*
+* Check a particular usage restriction
+*/
+bool check_usage(const X509_Certificate& cert, X509_Store::Cert_Usage usage,
+ X509_Store::Cert_Usage check_for, Key_Constraints constraints)
+ {
+ if((usage & check_for) == 0)
+ return true;
+ if(cert.constraints() == NO_CONSTRAINTS)
+ return true;
+ if(cert.constraints() & constraints)
+ return true;
+ return false;
+ }
+
+/*
+* Check a particular usage restriction
+*/
+bool check_usage(const X509_Certificate& cert, X509_Store::Cert_Usage usage,
+ X509_Store::Cert_Usage check_for,
+ const std::string& usage_oid)
+ {
+ if((usage & check_for) == 0)
+ return true;
+
+ const std::vector<std::string> constraints = cert.ex_constraints();
+
+ if(constraints.empty())
+ return true;
+
+ return std::binary_search(constraints.begin(), constraints.end(),
+ usage_oid);
+ }
+
+/*
+* Check the usage restrictions
+*/
+X509_Code usage_check(const X509_Certificate& cert,
+ X509_Store::Cert_Usage usage)
+ {
+ if(usage == X509_Store::ANY)
+ return VERIFIED;
+
+ if(!check_usage(cert, usage, X509_Store::CRL_SIGNING, CRL_SIGN))
+ return CA_CERT_NOT_FOR_CRL_ISSUER;
+
+ if(!check_usage(cert, usage, X509_Store::TLS_SERVER, "PKIX.ServerAuth"))
+ return INVALID_USAGE;
+ if(!check_usage(cert, usage, X509_Store::TLS_CLIENT, "PKIX.ClientAuth"))
+ return INVALID_USAGE;
+ if(!check_usage(cert, usage, X509_Store::CODE_SIGNING, "PKIX.CodeSigning"))
+ return INVALID_USAGE;
+ if(!check_usage(cert, usage, X509_Store::EMAIL_PROTECTION,
+ "PKIX.EmailProtection"))
+ return INVALID_USAGE;
+ if(!check_usage(cert, usage, X509_Store::TIME_STAMPING,
+ "PKIX.TimeStamping"))
+ return INVALID_USAGE;
+
+ return VERIFIED;
+ }
+
+}
+
+/*
+* Define equality for revocation data
+*/
+bool X509_Store::CRL_Data::operator==(const CRL_Data& other) const
+ {
+ if(issuer != other.issuer)
+ return false;
+ if(serial != other.serial)
+ return false;
+ return compare_ids(auth_key_id, other.auth_key_id);
+ }
+
+/*
+* Define inequality for revocation data
+*/
+bool X509_Store::CRL_Data::operator!=(const CRL_Data& other) const
+ {
+ return !((*this) == other);
+ }
+
+/*
+* Define an ordering for revocation data
+*/
+bool X509_Store::CRL_Data::operator<(const X509_Store::CRL_Data& other) const
+ {
+ if(*this == other)
+ return false;
+
+ const MemoryVector<byte>& serial1 = serial;
+ const MemoryVector<byte>& key_id1 = auth_key_id;
+ const MemoryVector<byte>& serial2 = other.serial;
+ const MemoryVector<byte>& key_id2 = other.auth_key_id;
+
+ if(compare_ids(key_id1, key_id2) == false)
+ {
+ if(std::lexicographical_compare(key_id1.begin(), key_id1.end(),
+ key_id2.begin(), key_id2.end()))
+ return true;
+
+ if(std::lexicographical_compare(key_id2.begin(), key_id2.end(),
+ key_id1.begin(), key_id1.end()))
+ return false;
+ }
+
+ if(compare_ids(serial1, serial2) == false)
+ {
+ if(std::lexicographical_compare(serial1.begin(), serial1.end(),
+ serial2.begin(), serial2.end()))
+ return true;
+
+ if(std::lexicographical_compare(serial2.begin(), serial2.end(),
+ serial1.begin(), serial1.end()))
+ return false;
+ }
+
+ return (issuer < other.issuer);
+ }
+
+/*
+* X509_Store Constructor
+*/
+X509_Store::X509_Store(u32bit slack, u32bit cache_timeout)
+ {
+ revoked_info_valid = true;
+
+ validation_cache_timeout = cache_timeout;
+ time_slack = slack;
+ }
+
+/*
+* X509_Store Copy Constructor
+*/
+X509_Store::X509_Store(const X509_Store& other)
+ {
+ certs = other.certs;
+ revoked = other.revoked;
+ revoked_info_valid = other.revoked_info_valid;
+ for(size_t j = 0; j != other.stores.size(); ++j)
+ stores[j] = other.stores[j]->clone();
+ time_slack = other.time_slack;
+ }
+
+/*
+* X509_Store Destructor
+*/
+X509_Store::~X509_Store()
+ {
+ for(size_t j = 0; j != stores.size(); ++j)
+ delete stores[j];
+ }
+
+/*
+* Verify a certificate's authenticity
+*/
+X509_Code X509_Store::validate_cert(const X509_Certificate& cert,
+ Cert_Usage cert_usage)
+ {
+ recompute_revoked_info();
+
+ std::vector<size_t> indexes;
+ X509_Code chaining_result = construct_cert_chain(cert, indexes);
+ if(chaining_result != VERIFIED)
+ return chaining_result;
+
+ const u64bit current_time = system_time();
+
+ s32bit time_check = validity_check(cert.start_time(), cert.end_time(),
+ current_time, time_slack);
+ if(time_check < 0) return CERT_NOT_YET_VALID;
+ else if(time_check > 0) return CERT_HAS_EXPIRED;
+
+ X509_Code sig_check_result = check_sig(cert, certs[indexes[0]]);
+ if(sig_check_result != VERIFIED)
+ return sig_check_result;
+
+ if(is_revoked(cert))
+ return CERT_IS_REVOKED;
+
+ for(size_t j = 0; j != indexes.size() - 1; ++j)
+ {
+ const X509_Certificate& current_cert = certs[indexes[j]].cert;
+
+ time_check = validity_check(current_cert.start_time(),
+ current_cert.end_time(),
+ current_time,
+ time_slack);
+
+ if(time_check < 0) return CERT_NOT_YET_VALID;
+ else if(time_check > 0) return CERT_HAS_EXPIRED;
+
+ sig_check_result = check_sig(certs[indexes[j]], certs[indexes[j+1]]);
+ if(sig_check_result != VERIFIED)
+ return sig_check_result;
+ }
+
+ return usage_check(cert, cert_usage);
+ }
+
+/*
+* Find this certificate
+*/
+size_t X509_Store::find_cert(const X509_DN& subject_dn,
+ const MemoryRegion<byte>& subject_key_id) const
+ {
+ for(size_t j = 0; j != certs.size(); ++j)
+ {
+ const X509_Certificate& this_cert = certs[j].cert;
+ if(compare_ids(this_cert.subject_key_id(), subject_key_id) &&
+ this_cert.subject_dn() == subject_dn)
+ return j;
+ }
+ return NO_CERT_FOUND;
+ }
+
+/*
+* Find the parent of this certificate
+*/
+size_t X509_Store::find_parent_of(const X509_Certificate& cert)
+ {
+ const X509_DN issuer_dn = cert.issuer_dn();
+ const MemoryVector<byte> auth_key_id = cert.authority_key_id();
+
+ size_t index = find_cert(issuer_dn, auth_key_id);
+
+ if(index != NO_CERT_FOUND)
+ return index;
+
+ for(size_t j = 0; j != stores.size(); ++j)
+ {
+ std::vector<X509_Certificate> got =
+ stores[j]->find_cert_by_subject_and_key_id(issuer_dn, auth_key_id);
+
+ for(size_t k = 0; k != got.size(); ++k)
+ add_cert(got[k]);
+ }
+
+ return find_cert(issuer_dn, auth_key_id);
+ }
+
+/*
+* Construct a chain of certificate relationships
+*/
+X509_Code X509_Store::construct_cert_chain(const X509_Certificate& end_cert,
+ std::vector<size_t>& indexes,
+ bool need_full_chain)
+ {
+ size_t parent = find_parent_of(end_cert);
+
+ while(true)
+ {
+ if(parent == NO_CERT_FOUND)
+ return CERT_ISSUER_NOT_FOUND;
+ indexes.push_back(parent);
+
+ if(certs[parent].is_verified(validation_cache_timeout))
+ if(certs[parent].verify_result() != VERIFIED)
+ return certs[parent].verify_result();
+
+ const X509_Certificate& parent_cert = certs[parent].cert;
+ if(!parent_cert.is_CA_cert())
+ return CA_CERT_NOT_FOR_CERT_ISSUER;
+
+ if(certs[parent].is_trusted())
+ break;
+ if(parent_cert.is_self_signed())
+ return CANNOT_ESTABLISH_TRUST;
+
+ if(parent_cert.path_limit() < indexes.size() - 1)
+ return CERT_CHAIN_TOO_LONG;
+
+ parent = find_parent_of(parent_cert);
+ }
+
+ if(need_full_chain)
+ return VERIFIED;
+
+ while(true)
+ {
+ if(indexes.size() < 2)
+ break;
+
+ const size_t cert = indexes.back();
+
+ if(certs[cert].is_verified(validation_cache_timeout))
+ {
+ if(certs[cert].verify_result() != VERIFIED)
+ throw Internal_Error("X509_Store::construct_cert_chain");
+ indexes.pop_back();
+ }
+ else
+ break;
+ }
+
+ const size_t last_cert = indexes.back();
+ const size_t parent_of_last_cert = find_parent_of(certs[last_cert].cert);
+ if(parent_of_last_cert == NO_CERT_FOUND)
+ return CERT_ISSUER_NOT_FOUND;
+ indexes.push_back(parent_of_last_cert);
+
+ return VERIFIED;
+ }
+
+/*
+* Check the CAs signature on a certificate
+*/
+X509_Code X509_Store::check_sig(const Cert_Info& cert_info,
+ const Cert_Info& ca_cert_info) const
+ {
+ if(cert_info.is_verified(validation_cache_timeout))
+ return cert_info.verify_result();
+
+ const X509_Certificate& cert = cert_info.cert;
+ const X509_Certificate& ca_cert = ca_cert_info.cert;
+
+ X509_Code verify_code = check_sig(cert, ca_cert.subject_public_key());
+
+ cert_info.set_result(verify_code);
+
+ return verify_code;
+ }
+
+/*
+* Check a CA's signature
+*/
+X509_Code X509_Store::check_sig(const X509_Object& object, Public_Key* key)
+ {
+ std::auto_ptr<Public_Key> pub_key(key);
+
+ try {
+ std::vector<std::string> sig_info =
+ split_on(OIDS::lookup(object.signature_algorithm().oid), '/');
+
+ if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name())
+ return SIGNATURE_ERROR;
+
+ std::string padding = sig_info[1];
+ Signature_Format format;
+ if(key->message_parts() >= 2) format = DER_SEQUENCE;
+ else format = IEEE_1363;
+
+ PK_Verifier verifier(*pub_key.get(), padding, format);
+
+ bool valid = verifier.verify_message(object.tbs_data(),
+ object.signature());
+
+ if(valid)
+ return VERIFIED;
+ else
+ return SIGNATURE_ERROR;
+ }
+ catch(Lookup_Error) { return CA_CERT_CANNOT_SIGN; }
+ catch(Decoding_Error) { return CERT_FORMAT_ERROR; }
+ catch(Exception) {}
+
+ return UNKNOWN_X509_ERROR;
+ }
+
+/*
+* Recompute the revocation status of the certs
+*/
+void X509_Store::recompute_revoked_info() const
+ {
+ if(revoked_info_valid)
+ return;
+
+ for(size_t j = 0; j != certs.size(); ++j)
+ {
+ if((certs[j].is_verified(validation_cache_timeout)) &&
+ (certs[j].verify_result() != VERIFIED))
+ continue;
+
+ if(is_revoked(certs[j].cert))
+ certs[j].set_result(CERT_IS_REVOKED);
+ }
+
+ revoked_info_valid = true;
+ }
+
+/*
+* Check if a certificate is revoked
+*/
+bool X509_Store::is_revoked(const X509_Certificate& cert) const
+ {
+ CRL_Data revoked_info;
+ revoked_info.issuer = cert.issuer_dn();
+ revoked_info.serial = cert.serial_number();
+ revoked_info.auth_key_id = cert.authority_key_id();
+
+ if(std::binary_search(revoked.begin(), revoked.end(), revoked_info))
+ return true;
+ return false;
+ }
+
+/*
+* Construct a path back to a root for this cert
+*/
+std::vector<X509_Certificate>
+X509_Store::get_cert_chain(const X509_Certificate& cert)
+ {
+ std::vector<X509_Certificate> result;
+ std::vector<size_t> indexes;
+ X509_Code chaining_result = construct_cert_chain(cert, indexes, true);
+
+ if(chaining_result != VERIFIED)
+ throw Invalid_State("X509_Store::get_cert_chain: Can't construct chain");
+
+ for(size_t j = 0; j != indexes.size(); ++j)
+ result.push_back(certs[indexes[j]].cert);
+ return result;
+ }
+
+/*
+* Add a certificate store to the list of stores
+*/
+void X509_Store::add_new_certstore(Certificate_Store* certstore)
+ {
+ stores.push_back(certstore);
+ }
+
+/*
+* Add a certificate to the store
+*/
+void X509_Store::add_cert(const X509_Certificate& cert, bool trusted)
+ {
+ if(trusted && !cert.is_self_signed())
+ throw Invalid_Argument("X509_Store: Trusted certs must be self-signed");
+
+ if(find_cert(cert.subject_dn(), cert.subject_key_id()) == NO_CERT_FOUND)
+ {
+ revoked_info_valid = false;
+ Cert_Info info(cert, trusted);
+ certs.push_back(info);
+ }
+ else if(trusted)
+ {
+ for(size_t j = 0; j != certs.size(); ++j)
+ {
+ const X509_Certificate& this_cert = certs[j].cert;
+ if(this_cert == cert)
+ certs[j].trusted = trusted;
+ }
+ }
+ }
+
+/*
+* Add one or more certificates to the store
+*/
+void X509_Store::do_add_certs(DataSource& source, bool trusted)
+ {
+ while(!source.end_of_data())
+ {
+ try {
+ X509_Certificate cert(source);
+ add_cert(cert, trusted);
+ }
+ catch(Decoding_Error) {}
+ catch(Invalid_Argument) {}
+ }
+ }
+
+/*
+* Add one or more certificates to the store
+*/
+void X509_Store::add_certs(DataSource& source)
+ {
+ do_add_certs(source, false);
+ }
+
+/*
+* Add one or more certificates to the store
+*/
+void X509_Store::add_trusted_certs(DataSource& source)
+ {
+ do_add_certs(source, true);
+ }
+
+/*
+* Add one or more certificates to the store
+*/
+X509_Code X509_Store::add_crl(const X509_CRL& crl)
+ {
+ s32bit time_check = validity_check(crl.this_update(), crl.next_update(),
+ system_time(), time_slack);
+
+ if(time_check < 0) return CRL_NOT_YET_VALID;
+ else if(time_check > 0) return CRL_HAS_EXPIRED;
+
+ size_t cert_index = NO_CERT_FOUND;
+
+ for(size_t j = 0; j != certs.size(); ++j)
+ {
+ const X509_Certificate& this_cert = certs[j].cert;
+ if(compare_ids(this_cert.subject_key_id(), crl.authority_key_id()))
+ {
+ if(this_cert.subject_dn() == crl.issuer_dn())
+ cert_index = j;
+ }
+ }
+
+ if(cert_index == NO_CERT_FOUND)
+ return CRL_ISSUER_NOT_FOUND;
+
+ const X509_Certificate& ca_cert = certs[cert_index].cert;
+
+ X509_Code verify_result = validate_cert(ca_cert, CRL_SIGNING);
+ if(verify_result != VERIFIED)
+ return verify_result;
+
+ verify_result = check_sig(crl, ca_cert.subject_public_key());
+ if(verify_result != VERIFIED)
+ return verify_result;
+
+ std::vector<CRL_Entry> revoked_certs = crl.get_revoked();
+
+ for(size_t j = 0; j != revoked_certs.size(); ++j)
+ {
+ CRL_Data revoked_info;
+ revoked_info.issuer = crl.issuer_dn();
+ revoked_info.serial = revoked_certs[j].serial_number();
+ revoked_info.auth_key_id = crl.authority_key_id();
+
+ std::vector<CRL_Data>::iterator p =
+ std::find(revoked.begin(), revoked.end(), revoked_info);
+
+ if(revoked_certs[j].reason_code() == REMOVE_FROM_CRL)
+ {
+ if(p == revoked.end()) continue;
+ revoked.erase(p);
+ }
+ else
+ {
+ if(p != revoked.end()) continue;
+ revoked.push_back(revoked_info);
+ }
+ }
+
+ std::sort(revoked.begin(), revoked.end());
+ revoked_info_valid = false;
+
+ return VERIFIED;
+ }
+
+/*
+* PEM encode the set of certificates
+*/
+std::string X509_Store::PEM_encode() const
+ {
+ std::string cert_store;
+ for(size_t j = 0; j != certs.size(); ++j)
+ cert_store += certs[j].cert.PEM_encode();
+ return cert_store;
+ }
+
+/*
+* Create a Cert_Info structure
+*/
+X509_Store::Cert_Info::Cert_Info(const X509_Certificate& c,
+ bool t) : cert(c), trusted(t)
+ {
+ checked = false;
+ result = UNKNOWN_X509_ERROR;
+ last_checked = 0;
+ }
+
+/*
+* Return the verification results
+*/
+X509_Code X509_Store::Cert_Info::verify_result() const
+ {
+ if(!checked)
+ throw Invalid_State("Cert_Info::verify_result() called; not checked");
+ return result;
+ }
+
+/*
+* Set the verification results
+*/
+void X509_Store::Cert_Info::set_result(X509_Code code) const
+ {
+ result = code;
+ last_checked = system_time();
+ checked = true;
+ }
+
+/*
+* Check if this certificate can be trusted
+*/
+bool X509_Store::Cert_Info::is_trusted() const
+ {
+ return trusted;
+ }
+
+/*
+* Check if this certificate has been verified
+*/
+bool X509_Store::Cert_Info::is_verified(u32bit timeout) const
+ {
+ if(!checked)
+ return false;
+ if(result != VERIFIED && result != CERT_NOT_YET_VALID)
+ return true;
+
+ const u64bit current_time = system_time();
+
+ if(current_time > last_checked + timeout)
+ checked = false;
+
+ return checked;
+ }
+
+}