diff options
Diffstat (limited to 'src/lib/x509')
37 files changed, 7052 insertions, 0 deletions
diff --git a/src/lib/x509/cert_status.h b/src/lib/x509/cert_status.h new file mode 100644 index 000000000..b69bd1832 --- /dev/null +++ b/src/lib/x509/cert_status.h @@ -0,0 +1,63 @@ +/* +* Result enums +* (C) 2013 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_PATH_RESULT_H__ +#define BOTAN_X509_PATH_RESULT_H__ + +namespace Botan { + +/** +* Certificate validation status code +*/ +enum class Certificate_Status_Code { + VERIFIED = 0x00000000, + OCSP_RESPONSE_GOOD, + NO_REVOCATION_DATA, + + // Local policy failures + SIGNATURE_METHOD_TOO_WEAK = 1000, + UNTRUSTED_HASH, + + // Time problems + CERT_NOT_YET_VALID = 2000, + CERT_HAS_EXPIRED, + OCSP_NOT_YET_VALID, + OCSP_HAS_EXPIRED, + CRL_NOT_YET_VALID, + CRL_HAS_EXPIRED, + + // Chain generation problems + CERT_ISSUER_NOT_FOUND = 3000, + CANNOT_ESTABLISH_TRUST, + + CERT_CHAIN_LOOP, + + // Validation errors + POLICY_ERROR = 4000, + INVALID_USAGE, + CERT_CHAIN_TOO_LONG, + CA_CERT_NOT_FOR_CERT_ISSUER, + NAME_CONSTRAINT_ERROR, + + // Revocation errors + CA_CERT_NOT_FOR_CRL_ISSUER, + OCSP_CERT_NOT_LISTED, + OCSP_BAD_STATUS, + + CERT_NAME_NOMATCH, + + UNKNOWN_CRITICAL_EXTENSION, + + // Hard failures + CERT_IS_REVOKED = 5000, + CRL_BAD_SIGNATURE, + SIGNATURE_ERROR, +}; + +} + +#endif diff --git a/src/lib/x509/certstor.cpp b/src/lib/x509/certstor.cpp new file mode 100644 index 000000000..24cd84de7 --- /dev/null +++ b/src/lib/x509/certstor.cpp @@ -0,0 +1,152 @@ +/* +* Certificate Store +* (C) 1999-2010,2013 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor.h> +#include <botan/internal/filesystem.h> + +namespace Botan { + +std::shared_ptr<const X509_CRL> Certificate_Store::find_crl_for(const X509_Certificate&) const + { + return std::shared_ptr<const X509_CRL>(); + } + +void Certificate_Store_In_Memory::add_certificate(const X509_Certificate& cert) + { + for(size_t i = 0; i != m_certs.size(); ++i) + { + if(*m_certs[i] == cert) + return; + } + + m_certs.push_back(std::make_shared<X509_Certificate>(cert)); + } + +std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const + { + std::vector<X509_DN> subjects; + for(size_t i = 0; i != m_certs.size(); ++i) + subjects.push_back(m_certs[i]->subject_dn()); + return subjects; + } + +namespace { + +template<typename T> +std::shared_ptr<const X509_Certificate> +cert_search(const X509_DN& subject_dn, const std::vector<byte>& key_id, + const std::vector<std::shared_ptr<T>>& certs) + { + for(size_t i = 0; i != certs.size(); ++i) + { + // Only compare key ids if set in both call and in the cert + if(key_id.size()) + { + std::vector<byte> skid = certs[i]->subject_key_id(); + + if(skid.size() && skid != key_id) // no match + continue; + } + + if(certs[i]->subject_dn() == subject_dn) + return certs[i]; + } + + return std::shared_ptr<const X509_Certificate>(); + } + +} + +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn, + const std::vector<byte>& key_id) const + { + return cert_search(subject_dn, key_id, m_certs); + } + +void Certificate_Store_In_Memory::add_crl(const X509_CRL& crl) + { + X509_DN crl_issuer = crl.issuer_dn(); + + for(size_t i = 0; i != m_crls.size(); ++i) + { + // Found an update of a previously existing one; replace it + if(m_crls[i]->issuer_dn() == crl_issuer) + { + if(m_crls[i]->this_update() <= crl.this_update()) + m_crls[i] = std::make_shared<X509_CRL>(crl); + return; + } + } + + // Totally new CRL, add to the list + m_crls.push_back(std::make_shared<X509_CRL>(crl)); + } + +std::shared_ptr<const X509_CRL> Certificate_Store_In_Memory::find_crl_for(const X509_Certificate& subject) const + { + const std::vector<byte>& key_id = subject.authority_key_id(); + + for(size_t i = 0; i != m_crls.size(); ++i) + { + // Only compare key ids if set in both call and in the CRL + if(key_id.size()) + { + std::vector<byte> akid = m_crls[i]->authority_key_id(); + + if(akid.size() && akid != key_id) // no match + continue; + } + + if(m_crls[i]->issuer_dn() == subject.issuer_dn()) + return m_crls[i]; + } + + return std::shared_ptr<const X509_CRL>(); + } + +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); + for(auto&& cert_file : maybe_certs) + { + try + { + m_certs.push_back(std::make_shared<X509_Certificate>(cert_file)); + } + catch(std::exception&) + { + } + } + } +#endif + +std::shared_ptr<const X509_Certificate> +Certificate_Store_Overlay::find_cert(const X509_DN& subject_dn, + const std::vector<byte>& key_id) const + { + return cert_search(subject_dn, key_id, m_certs); + } + +std::vector<X509_DN> Certificate_Store_Overlay::all_subjects() const + { + std::vector<X509_DN> subjects; + for(size_t i = 0; i != m_certs.size(); ++i) + subjects.push_back(m_certs[i]->subject_dn()); + return subjects; + } + +} diff --git a/src/lib/x509/certstor.h b/src/lib/x509/certstor.h new file mode 100644 index 000000000..56176739b --- /dev/null +++ b/src/lib/x509/certstor.h @@ -0,0 +1,134 @@ +/* +* 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_DLL 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 + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<byte>& key_id) 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_DLL Certificate_Store_In_Memory : 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() {} + + /** + * Add a certificate to the store. + * @param cert certificate to be added + */ + void add_certificate(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); + + /** + * @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 + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<byte>& key_id) 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<X509_Certificate>> m_certs; + std::vector<std::shared_ptr<X509_CRL>> m_crls; + }; + +/** +* FIXME add doc +*/ +class BOTAN_DLL Certificate_Store_Overlay : public Certificate_Store + { + public: + explicit Certificate_Store_Overlay(const std::vector<std::shared_ptr<const X509_Certificate>>& certs) : + m_certs(certs) {} + + /** + * @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 + */ + std::shared_ptr<const X509_Certificate> find_cert( + const X509_DN& subject_dn, + const std::vector<byte>& key_id) const override; + private: + const std::vector<std::shared_ptr<const X509_Certificate>>& m_certs; + }; + +} +#endif diff --git a/src/lib/x509/certstor_sql/certstor_sql.cpp b/src/lib/x509/certstor_sql/certstor_sql.cpp new file mode 100644 index 000000000..dfb8c5d78 --- /dev/null +++ b/src/lib/x509/certstor_sql/certstor_sql.cpp @@ -0,0 +1,301 @@ +/* +* 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_sql.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> +#include <botan/internal/filesystem.h> +#include <botan/pkcs8.h> +#include <botan/data_src.h> +#include <botan/hash.h> +#include <botan/hex.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<byte>& key_id) const + { + DER_Encoder enc; + std::shared_ptr<SQL_Database::Statement> stmt; + + subject_dn.encode_into(enc); + + if(key_id.empty()) + { + stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE subject_dn == ?1"); + stmt->bind(1,enc.get_contents_unlocked()); + } + 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,enc.get_contents_unlocked()); + stmt->bind(2,key_id); + } + + std::shared_ptr<const X509_Certificate> cert; + while(stmt->step()) + { + auto blob = stmt->get_blob(0); + cert = std::make_shared<X509_Certificate>( + std::vector<byte>(blob.first,blob.first + blob.second)); + + } + + return cert; + } + +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) + { + if(find_cert(cert.subject_dn(),cert.subject_key_id())) + return false; + + DER_Encoder enc; + 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")); + cert.subject_dn().encode_into(enc); + stmt->bind(2,enc.get_contents_unlocked()); + stmt->bind(3,cert.subject_key_id()); + stmt->bind(4,std::vector<byte>()); + enc = DER_Encoder(); + cert.encode_into(enc); + stmt->bind(5,enc.get_contents_unlocked()); + 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("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<byte>(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("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("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()) + { + DER_Encoder der; + time.encode_into(der); + stmt1->bind(3,der.get_contents_unlocked()); + } + else + { + stmt1->bind(3,-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<byte>(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/src/lib/x509/certstor_sql/certstor_sql.h b/src/lib/x509/certstor_sql/certstor_sql.h new file mode 100644 index 000000000..0025884f9 --- /dev/null +++ b/src/lib/x509/certstor_sql/certstor_sql.h @@ -0,0 +1,104 @@ +/* +* 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> + +namespace Botan { + +class RandomNumberGenerator; + +/** + * Certificate and private key store backed by an SQL database. + */ +class BOTAN_DLL 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. + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<byte>& key_id) const override; + + /** + * Returns all subject DNs known to the store instance. + */ + virtual 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. + */ + virtual 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/src/lib/x509/certstor_sql/info.txt b/src/lib/x509/certstor_sql/info.txt new file mode 100644 index 000000000..cfdd521a2 --- /dev/null +++ b/src/lib/x509/certstor_sql/info.txt @@ -0,0 +1,5 @@ +define CERTSTOR_SQL 20160818 + +<requires> +datastor +</requires> diff --git a/src/lib/x509/certstor_sqlite3/certstor_sqlite.cpp b/src/lib/x509/certstor_sqlite3/certstor_sqlite.cpp new file mode 100644 index 000000000..b7c066483 --- /dev/null +++ b/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/src/lib/x509/certstor_sqlite3/certstor_sqlite.h b/src/lib/x509/certstor_sqlite3/certstor_sqlite.h new file mode 100644 index 000000000..d1a1e3d21 --- /dev/null +++ b/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 (http://sqlite.org) database. +*/ +class BOTAN_DLL Certificate_Store_In_SQLite : 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/src/lib/x509/certstor_sqlite3/info.txt b/src/lib/x509/certstor_sqlite3/info.txt new file mode 100644 index 000000000..d5e50fc95 --- /dev/null +++ b/src/lib/x509/certstor_sqlite3/info.txt @@ -0,0 +1,6 @@ +define CERTSTOR_SQLITE3 20160818 + +<requires> +certstor_sql +sqlite3 +</requires> diff --git a/src/lib/x509/crl_ent.cpp b/src/lib/x509/crl_ent.cpp new file mode 100644 index 000000000..7074f0609 --- /dev/null +++ b/src/lib/x509/crl_ent.cpp @@ -0,0 +1,104 @@ +/* +* CRL Entry +* (C) 1999-2010 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/crl_ent.h> +#include <botan/x509cert.h> +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/bigint.h> +#include <botan/oids.h> + +namespace Botan { + +/* +* Create a CRL_Entry +*/ +CRL_Entry::CRL_Entry(bool t_on_unknown_crit) : + m_throw_on_unknown_critical(t_on_unknown_crit) + { + m_reason = UNSPECIFIED; + } + +/* +* Create a CRL_Entry +*/ +CRL_Entry::CRL_Entry(const X509_Certificate& cert, CRL_Code why) : + m_throw_on_unknown_critical(false) + { + m_serial = cert.serial_number(); + m_time = X509_Time(std::chrono::system_clock::now()); + m_reason = 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 + { + Extensions extensions; + + extensions.add(new Cert_Extension::CRL_ReasonCode(m_reason)); + + der.start_cons(SEQUENCE) + .encode(BigInt::decode(m_serial)) + .encode(m_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; + m_reason = UNSPECIFIED; + + BER_Decoder entry = source.start_cons(SEQUENCE); + + entry.decode(serial_number_bn).decode(m_time); + + if(entry.more_items()) + { + Extensions extensions(m_throw_on_unknown_critical); + entry.decode(extensions); + Data_Store info; + extensions.contents_to(info, info); + m_reason = CRL_Code(info.get1_u32bit("X509v3.CRLReasonCode")); + } + + entry.end_cons(); + + m_serial = BigInt::encode(serial_number_bn); + } + +} diff --git a/src/lib/x509/crl_ent.h b/src/lib/x509/crl_ent.h new file mode 100644 index 000000000..6600621e5 --- /dev/null +++ b/src/lib/x509/crl_ent.h @@ -0,0 +1,98 @@ +/* +* CRL Entry +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CRL_ENTRY_H__ +#define BOTAN_CRL_ENTRY_H__ + +#include <botan/asn1_time.h> + +namespace Botan { + +class X509_Certificate; + +/** +* X.509v2 CRL Reason Code. +*/ +enum CRL_Code { + 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, + AA_COMPROMISE = 10, + + DELETE_CRL_ENTRY = 0xFF00, + OCSP_GOOD = 0xFF01, + OCSP_UNKNOWN = 0xFF02 +}; + +/** +* This class represents CRL entries +*/ +class BOTAN_DLL 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 + */ + std::vector<byte> serial_number() const { return m_serial; } + + /** + * Get the revocation date of the certificate associated with this entry + * @return certificate's revocation date + */ + X509_Time expire_time() const { return m_time; } + + /** + * Get the entries reason code + * @return reason code + */ + CRL_Code reason_code() const { return m_reason; } + + /** + * Construct an empty CRL entry. + * @param throw_on_unknown_critical_extension should we throw an exception + * if an unknown CRL extension marked as critical is encountered + */ + explicit CRL_Entry(bool throw_on_unknown_critical_extension = false); + + /** + * 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: + bool m_throw_on_unknown_critical; + std::vector<byte> m_serial; + X509_Time m_time; + CRL_Code m_reason; + }; + +/** +* Test two CRL entries for equality in all fields. +*/ +BOTAN_DLL bool operator==(const CRL_Entry&, const CRL_Entry&); + +/** +* Test two CRL entries for inequality in at least one field. +*/ +BOTAN_DLL bool operator!=(const CRL_Entry&, const CRL_Entry&); + +} + +#endif diff --git a/src/lib/x509/info.txt b/src/lib/x509/info.txt new file mode 100644 index 000000000..be1e879c3 --- /dev/null +++ b/src/lib/x509/info.txt @@ -0,0 +1,10 @@ +define X509_CERTIFICATES 20151023 +define OCSP 20131128 + +<requires> +asn1 +datastor +http_util +pubkey +sha1 +</requires> diff --git a/src/lib/x509/key_constraint.cpp b/src/lib/x509/key_constraint.cpp new file mode 100644 index 000000000..30d1cb3b8 --- /dev/null +++ b/src/lib/x509/key_constraint.cpp @@ -0,0 +1,45 @@ +/* +* KeyUsage +* (C) 1999-2007 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/key_constraint.h> +#include <botan/x509_key.h> + +namespace Botan { + +/* +* 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; + + if(name == "DH" || name == "ECDH") + { + permitted |= KEY_AGREEMENT | ENCIPHER_ONLY | DECIPHER_ONLY; + } + + if(name == "RSA" || name == "ElGamal") + { + permitted |= KEY_ENCIPHERMENT | DATA_ENCIPHERMENT; + } + + if(name == "RSA" || name == "DSA" || name == "ECDSA" || name == "ECGDSA" || name == "ECKCDSA") + { + permitted |= DIGITAL_SIGNATURE | NON_REPUDIATION | KEY_CERT_SIGN | CRL_SIGN; + } + + if ( ( constraints & permitted ) != constraints ) + { + throw Exception("Constraint not permitted for key type " + name); + } + } + +} diff --git a/src/lib/x509/key_constraint.h b/src/lib/x509/key_constraint.h new file mode 100644 index 000000000..02c65acec --- /dev/null +++ b/src/lib/x509/key_constraint.h @@ -0,0 +1,46 @@ +/* +* Enumerations +* (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_ENUMS_H__ +#define BOTAN_ENUMS_H__ + +#include <botan/build.h> + +namespace Botan { + +/** +* 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 +}; + +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 Exception if the given constraints are not permitted for this key +*/ +BOTAN_DLL void verify_cert_constraints_valid_for_key_type(const Public_Key& pub_key, + Key_Constraints constraints); + +} + +#endif diff --git a/src/lib/x509/name_constraint.cpp b/src/lib/x509/name_constraint.cpp new file mode 100644 index 000000000..e4d69c6ac --- /dev/null +++ b/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/name_constraint.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> +#include <botan/charset.h> +#include <botan/loadstor.h> +#include <botan/x509_dn.h> +#include <botan/x509cert.h> +#include <sstream> + +namespace Botan { + +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(class 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.class_tag != CONTEXT_SPECIFIC) && + (obj.class_tag != (CONTEXT_SPECIFIC | CONSTRUCTED))) + throw Decoding_Error("Invalid class tag while decoding GeneralName"); + + const ASN1_Tag tag = obj.type_tag; + + if(tag == 1 || tag == 2 || tag == 6) + { + m_name = Charset::transcode(ASN1::to_string(obj), LATIN1_CHARSET, LOCAL_CHARSET); + + if(tag == 1) + { + m_type = "RFC822"; + } + else if(tag == 2) + { + m_type = "DNS"; + } + else if(tag == 6) + { + m_type = "URI"; + } + } + else if(tag == 4) + { + X509_DN dn; + std::multimap<std::string, std::string> nam; + BER_Decoder dec(obj.value); + std::stringstream ss; + + dn.decode_from(dec); + ss << dn; + + m_name = ss.str(); + m_type = "DN"; + } + else if(tag == 7) + { + if(obj.value.size() == 8) + { + const std::vector<byte> ip(obj.value.begin(), obj.value.begin() + 4); + const std::vector<byte> net(obj.value.begin() + 4, obj.value.end()); + m_type = "IP"; + m_name = ipv4_to_string(load_be<u32bit>(ip.data(), 0)) + "/" + ipv4_to_string(load_be<u32bit>(net.data(), 0)); + } + else if(obj.value.size() == 32) + { + throw Decoding_Error("Unsupported IPv6 name constraint"); + } + else + { + throw Decoding_Error("Invalid IP name constraint size " + + std::to_string(obj.value.size())); + } + } + 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; + + if(type() == "DNS") + { + match_fn = std::mem_fn(&GeneralName::matches_dns); + nam = cert.subject_info("DNS"); + + if(nam.empty()) + { + nam = cert.subject_info("CN"); + } + } + else if(type() == "DN") + { + match_fn = std::mem_fn(&GeneralName::matches_dn); + + std::stringstream ss; + ss << cert.subject_dn(); + nam.push_back(ss.str()); + } + else if(type() == "IP") + { + match_fn = std::mem_fn(&GeneralName::matches_ip); + nam = cert.subject_info("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 nam == name(); + } + else if(name().size() > nam.size()) + { + return false; + } + else // name.size() < nam.size() + { + std::string constr = name().front() == '.' ? name() : "." + name(); + // constr is suffix of nam + return constr == nam.substr(nam.size() - constr.size(), constr.size()); + } + } + +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; + int trys = 0; + + for(const std::pair<OID,std::string>& c: my_dn.get_attributes()) + { + auto i = attr.equal_range(c.first); + + if(i.first != i.second) + { + trys += 1; + ret &= i.first->second == c.second; + } + } + + return trys > 0 && ret; + } + +bool GeneralName::matches_ip(const std::string& nam) const + { + u32bit 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"); + + u32bit net = string_to_ipv4(p.at(0)); + u32bit 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; + size_t min = std::stoull(str, &p0, 10); + size_t max = std::stoull(str.substr(p0 + 1), &p1, 10); + GeneralName gn(str.substr(p0 + p1 + 2)); + + if(p0 > 0 && p1 > 0) + { + m_minimum = min; + m_maximum = max; + m_base = gn; + } + else + { + throw Invalid_Argument("Failed to decode Name Constraint"); + } + } + +void GeneralSubtree::encode_into(class 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/src/lib/x509/name_constraint.h b/src/lib/x509/name_constraint.h new file mode 100644 index 000000000..43d7fcbcb --- /dev/null +++ b/src/lib/x509/name_constraint.h @@ -0,0 +1,179 @@ +/* +* X.509 Name Constraint +* (C) 2015 Kai Michaelis +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_NAME_CONSTRAINT_H__ +#define BOTAN_NAME_CONSTRAINT_H__ + +#include <botan/asn1_obj.h> +#include <ostream> + +namespace Botan { + +class X509_Certificate; + +/** +* @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_DLL GeneralName : public ASN1_Object + { + public: + enum MatchResult : int + { + All, + Some, + None, + NotFound, + UnknownType, + }; + + /** + * Creates an empty GeneralName. + */ + GeneralName() : m_type(), m_name() {} + + /** + * 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(class DER_Encoder&) const override; + + void decode_from(class 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_DLL GeneralSubtree : 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(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(class DER_Encoder&) const override; + + void decode_from(class BER_Decoder&) override; + + /** + * @return name + */ + 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_DLL NameConstraints + { + 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; +}; + +} + +#endif diff --git a/src/lib/x509/ocsp.cpp b/src/lib/x509/ocsp.cpp new file mode 100644 index 000000000..761c5b436 --- /dev/null +++ b/src/lib/x509/ocsp.cpp @@ -0,0 +1,251 @@ +/* +* 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/x509path.h> +#include <botan/http_util.h> + +namespace Botan { + +namespace OCSP { + +namespace { + +void decode_optional_list(BER_Decoder& ber, + ASN1_Tag tag, + std::vector<X509_Certificate>& output) + { + BER_Object obj = ber.get_next_object(); + + if(obj.type_tag != tag || obj.class_tag != (CONTEXT_SPECIFIC | CONSTRUCTED)) + { + ber.push_back(obj); + return; + } + + BER_Decoder list(obj.value); + + while(list.more_items()) + { + BER_Object certbits = list.get_next_object(); + X509_Certificate cert(unlock(certbits.value)); + output.push_back(std::move(cert)); + } + } + +void check_signature(const std::vector<byte>& tbs_response, + const AlgorithmIdentifier& sig_algo, + const std::vector<byte>& signature, + const X509_Certificate& cert) + { + std::unique_ptr<Public_Key> pub_key(cert.subject_public_key()); + + const std::vector<std::string> sig_info = + split_on(OIDS::lookup(sig_algo.oid), '/'); + + if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name()) + throw Exception("Information in OCSP response does not match cert"); + + std::string padding = sig_info[1]; + Signature_Format format = + (pub_key->message_parts() >= 2) ? DER_SEQUENCE : IEEE_1363; + + PK_Verifier verifier(*pub_key, padding, format); + + if(!verifier.verify_message(ASN1::put_in_sequence(tbs_response), signature)) + throw Exception("Signature on OCSP response does not verify"); + } + +void check_signature(const std::vector<byte>& tbs_response, + const AlgorithmIdentifier& sig_algo, + const std::vector<byte>& signature, + const Certificate_Store& trusted_roots, + const std::vector<X509_Certificate>& certs) + { + if(certs.size() < 1) + throw Invalid_Argument("Short cert chain for check_signature"); + + if(trusted_roots.certificate_known(certs[0])) + return check_signature(tbs_response, sig_algo, signature, certs[0]); + + // Otherwise attempt to chain the signing cert to a trust root + + if(!certs[0].allowed_extended_usage("PKIX.OCSPSigning")) + throw Exception("OCSP response cert does not allow OCSP signing"); + + auto result = x509_path_validate(certs, Path_Validation_Restrictions(), trusted_roots); + + if(!result.successful_validation()) + throw Exception("Certificate validation failure: " + result.result_string()); + + if(!trusted_roots.certificate_known(result.trust_root())) // not needed anymore? + throw Exception("Certificate chain roots in unknown/untrusted CA"); + + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path = result.cert_path(); + + check_signature(tbs_response, sig_algo, signature, *cert_path[0]); + } + +} + +std::vector<byte> Request::BER_encode() const + { + CertID certid(m_issuer, m_subject); + + return DER_Encoder().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(certid) + .end_cons() + .end_cons() + .end_cons() + .end_cons().get_contents_unlocked(); + } + +std::string Request::base64_encode() const + { + return Botan::base64_encode(BER_encode()); + } + +Response::Response(const Certificate_Store& trusted_roots, + const std::vector<byte>& response_bits) + { + BER_Decoder response_outer = BER_Decoder(response_bits).start_cons(SEQUENCE); + + size_t resp_status = 0; + + response_outer.decode(resp_status, ENUMERATED, UNIVERSAL); + + if(resp_status != 0) + throw Exception("OCSP response status " + std::to_string(resp_status)); + + 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); + + std::vector<byte> tbs_bits; + AlgorithmIdentifier sig_algo; + std::vector<byte> signature; + std::vector<X509_Certificate> certs; + + basicresponse.start_cons(SEQUENCE) + .raw_bytes(tbs_bits) + .end_cons() + .decode(sig_algo) + .decode(signature, BIT_STRING); + decode_optional_list(basicresponse, ASN1_Tag(0), certs); + + size_t responsedata_version = 0; + X509_DN name; + std::vector<byte> key_hash; + X509_Time produced_at; + Extensions extensions; + + BER_Decoder(tbs_bits) + .decode_optional(responsedata_version, ASN1_Tag(0), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode_optional(name, ASN1_Tag(1), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode_optional_string(key_hash, OCTET_STRING, 2, + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + + .decode(produced_at) + + .decode_list(m_responses) + + .decode_optional(extensions, ASN1_Tag(1), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + + if(certs.empty()) + { + if(auto cert = trusted_roots.find_cert(name, std::vector<byte>())) + certs.push_back(*cert); + else + throw Exception("Could not find certificate that signed OCSP response"); + } + + check_signature(tbs_bits, sig_algo, signature, trusted_roots, certs); + } + + response_outer.end_cons(); + } + +Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, + const X509_Certificate& subject) const + { + for(const auto& response : m_responses) + { + if(response.certid().is_id_for(issuer, subject)) + { + X509_Time current_time(std::chrono::system_clock::now()); + + if(response.cert_status() == 1) + return Certificate_Status_Code::CERT_IS_REVOKED; + + if(response.this_update() > current_time) + return Certificate_Status_Code::OCSP_NOT_YET_VALID; + + if(response.next_update().time_is_set() && current_time > response.next_update()) + return Certificate_Status_Code::OCSP_HAS_EXPIRED; + + 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; + } + +Response online_check(const X509_Certificate& issuer, + const X509_Certificate& subject, + const Certificate_Store* trusted_roots) + { + const std::string responder_url = subject.ocsp_responder(); + + if(responder_url.empty()) + throw Exception("No OCSP responder specified"); + + OCSP::Request req(issuer, subject); + + auto http = HTTP::POST_sync(responder_url, + "application/ocsp-request", + req.BER_encode()); + + http.throw_unless_ok(); + + // Check the MIME type? + + OCSP::Response response(*trusted_roots, http.body()); + + return response; + } + +} + +} diff --git a/src/lib/x509/ocsp.h b/src/lib/x509/ocsp.h new file mode 100644 index 000000000..fe1796984 --- /dev/null +++ b/src/lib/x509/ocsp.h @@ -0,0 +1,113 @@ +/* +* 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/cert_status.h> +#include <botan/ocsp_types.h> + +namespace Botan { + +class Certificate_Store; + +namespace OCSP { + +/** +* An OCSP request. +*/ +class BOTAN_DLL Request + { + 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) : + m_issuer(issuer_cert), + m_subject(subject_cert) + {} + + /** + * @return BER-encoded OCSP request + */ + std::vector<byte> 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 { return m_subject; } + private: + X509_Certificate m_issuer, m_subject; + }; + +/** +* An OCSP response. +*/ +class BOTAN_DLL Response + { + public: + /** + * Creates an empty OCSP response. + */ + Response() {} + + /** + * Creates an OCSP response. + * @param trusted_roots trusted roots for the OCSP response + * @param response_bits response bits received + */ + Response(const Certificate_Store& trusted_roots, + const std::vector<byte>& response_bits); + + /** + * Searches the OCSP response for issuer and subject certificate. + * @param issuer issuer certificate + * @param subject subject certificate + * @return OCSP status code, possible values: + * CERT_IS_REVOKED, + * OCSP_NOT_YET_VALID, + * OCSP_HAS_EXPIRED, + * OCSP_RESPONSE_GOOD, + * OCSP_BAD_STATUS, + * OCSP_CERT_NOT_LISTED + */ + Certificate_Status_Code status_for(const X509_Certificate& issuer, + const X509_Certificate& subject) const; + + private: + std::vector<SingleResponse> m_responses; + }; + +/** +* 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 +* @return OCSP response +*/ +BOTAN_DLL Response online_check(const X509_Certificate& issuer, + const X509_Certificate& subject, + const Certificate_Store* trusted_roots); + +} + +} + +#endif diff --git a/src/lib/x509/ocsp_types.cpp b/src/lib/x509/ocsp_types.cpp new file mode 100644 index 000000000..d470c2fa1 --- /dev/null +++ b/src/lib/x509/ocsp_types.cpp @@ -0,0 +1,119 @@ +/* +* OCSP subtypes +* (C) 2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/ocsp_types.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/x509_ext.h> +#include <botan/hash.h> +#include <botan/oids.h> + +namespace Botan { + +namespace OCSP { + +CertID::CertID(const X509_Certificate& issuer, + const X509_Certificate& subject) + { + /* + 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("SHA-160")); + + m_hash_id = AlgorithmIdentifier(hash->name(), AlgorithmIdentifier::USE_NULL_PARAM); + m_issuer_key_hash = unlock(hash->process(extract_key_bitstr(issuer))); + m_issuer_dn_hash = unlock(hash->process(subject.raw_issuer_dn())); + m_subject_serial = BigInt::decode(subject.serial_number()); + } + +std::vector<byte> CertID::extract_key_bitstr(const X509_Certificate& cert) const + { + const auto key_bits = cert.subject_public_key_bits(); + + AlgorithmIdentifier public_key_algid; + std::vector<byte> public_key_bitstr; + + BER_Decoder(key_bits) + .decode(public_key_algid) + .decode(public_key_bitstr, BIT_STRING); + + return public_key_bitstr; + } + +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; + + std::unique_ptr<HashFunction> hash(HashFunction::create(OIDS::lookup(m_hash_id.oid))); + + if(m_issuer_dn_hash != unlock(hash->process(subject.raw_issuer_dn()))) + return false; + + if(m_issuer_key_hash != unlock(hash->process(extract_key_bitstr(issuer)))) + 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_tag; + } + +} + +} diff --git a/src/lib/x509/ocsp_types.h b/src/lib/x509/ocsp_types.h new file mode 100644 index 000000000..6df8ac17f --- /dev/null +++ b/src/lib/x509/ocsp_types.h @@ -0,0 +1,67 @@ +/* +* OCSP subtypes +* (C) 2012 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_OCSP_TYPES_H__ +#define BOTAN_OCSP_TYPES_H__ + +#include <botan/x509cert.h> +#include <botan/asn1_time.h> +#include <botan/bigint.h> + +namespace Botan { + +namespace OCSP { + +class BOTAN_DLL CertID final : public ASN1_Object + { + public: + CertID() {} + + CertID(const X509_Certificate& issuer, + const X509_Certificate& subject); + + 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; + private: + std::vector<byte> extract_key_bitstr(const X509_Certificate& cert) const; + + AlgorithmIdentifier m_hash_id; + std::vector<byte> m_issuer_dn_hash; + std::vector<byte> m_issuer_key_hash; + BigInt m_subject_serial; + }; + +class BOTAN_DLL 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; + }; + +} + +} + +#endif diff --git a/src/lib/x509/pkcs10.cpp b/src/lib/x509/pkcs10.cpp new file mode 100644 index 000000000..ccd22454b --- /dev/null +++ b/src/lib/x509/pkcs10.cpp @@ -0,0 +1,210 @@ +/* +* PKCS #10 +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pkcs10.h> +#include <botan/x509_ext.h> +#include <botan/x509cert.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <botan/pem.h> + +namespace Botan { + +/* +* PKCS10_Request Constructor +*/ +PKCS10_Request::PKCS10_Request(DataSource& in) : + X509_Object(in, "CERTIFICATE REQUEST/NEW CERTIFICATE REQUEST") + { + do_decode(); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +/* +* PKCS10_Request Constructor +*/ +PKCS10_Request::PKCS10_Request(const std::string& fsname) : + X509_Object(fsname, "CERTIFICATE REQUEST/NEW CERTIFICATE REQUEST") + { + do_decode(); + } +#endif + +/* +* PKCS10_Request Constructor +*/ +PKCS10_Request::PKCS10_Request(const std::vector<byte>& in) : + X509_Object(in, "CERTIFICATE REQUEST/NEW CERTIFICATE REQUEST") + { + do_decode(); + } + +/* +* Deocde the CertificateRequestInfo +*/ +void PKCS10_Request::force_decode() + { + BER_Decoder cert_req_info(m_tbs_bits); + + 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)); + + X509_DN dn_subject; + cert_req_info.decode(dn_subject); + + m_info.add(dn_subject.contents()); + + BER_Object public_key = cert_req_info.get_next_object(); + if(public_key.type_tag != SEQUENCE || public_key.class_tag != CONSTRUCTED) + throw BER_Bad_Tag("PKCS10_Request: Unexpected tag for public key", + public_key.type_tag, public_key.class_tag); + + m_info.add("X509.Certificate.public_key", + PEM_Code::encode( + ASN1::put_in_sequence(unlock(public_key.value)), + "PUBLIC KEY" + ) + ); + + BER_Object attr_bits = cert_req_info.get_next_object(); + + if(attr_bits.type_tag == 0 && + attr_bits.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + { + BER_Decoder attributes(attr_bits.value); + while(attributes.more_items()) + { + Attribute attr; + attributes.decode(attr); + handle_attribute(attr); + } + attributes.verify_end(); + } + else if(attr_bits.type_tag != NO_OBJECT) + throw BER_Bad_Tag("PKCS10_Request: Unexpected tag for attributes", + attr_bits.type_tag, attr_bits.class_tag); + + cert_req_info.verify_end(); + + if(!this->check_signature(subject_public_key())) + throw Decoding_Error("PKCS #10 request: Bad signature detected"); + } + +/* +* Handle attributes in a PKCS #10 request +*/ +void PKCS10_Request::handle_attribute(const Attribute& attr) + { + BER_Decoder value(attr.parameters); + + if(attr.oid == OIDS::lookup("PKCS9.EmailAddress")) + { + ASN1_String email; + value.decode(email); + m_info.add("RFC822", email.value()); + } + else if(attr.oid == OIDS::lookup("PKCS9.ChallengePassword")) + { + ASN1_String challenge_password; + value.decode(challenge_password); + m_info.add("PKCS9.ChallengePassword", challenge_password.value()); + } + else if(attr.oid == OIDS::lookup("PKCS9.ExtensionRequest")) + { + Extensions extensions; + value.decode(extensions).verify_end(); + + Data_Store issuer_info; + extensions.contents_to(m_info, issuer_info); + } + } + +/* +* Return the challenge password (if any) +*/ +std::string PKCS10_Request::challenge_password() const + { + return m_info.get1("PKCS9.ChallengePassword"); + } + +/* +* Return the name of the requestor +*/ +X509_DN PKCS10_Request::subject_dn() const + { + return create_dn(m_info); + } + +/* +* Return the public key of the requestor +*/ +std::vector<byte> PKCS10_Request::raw_public_key() const + { + DataSource_Memory source(m_info.get1("X509.Certificate.public_key")); + return unlock(PEM_Code::decode_check_label(source, "PUBLIC KEY")); + } + +/* +* Return the public key of the requestor +*/ +Public_Key* PKCS10_Request::subject_public_key() const + { + DataSource_Memory source(m_info.get1("X509.Certificate.public_key")); + return X509::load_key(source); + } + +/* +* Return the alternative names of the requestor +*/ +AlternativeName PKCS10_Request::subject_alt_name() const + { + return create_alt_name(m_info); + } + +/* +* Return the key constraints (if any) +*/ +Key_Constraints PKCS10_Request::constraints() const + { + return Key_Constraints(m_info.get1_u32bit("X509v3.KeyUsage", NO_CONSTRAINTS)); + } + +/* +* Return the extendend key constraints (if any) +*/ +std::vector<OID> PKCS10_Request::ex_constraints() const + { + std::vector<std::string> oids = m_info.get("X509v3.ExtendedKeyUsage"); + + std::vector<OID> result; + for(size_t i = 0; i != oids.size(); ++i) + result.push_back(OID(oids[i])); + return result; + } + +/* +* Return is a CA certificate is requested +*/ +bool PKCS10_Request::is_CA() const + { + return (m_info.get1_u32bit("X509v3.BasicConstraints.is_ca") > 0); + } + +/* +* Return the desired path limit (if any) +*/ +u32bit PKCS10_Request::path_limit() const + { + return m_info.get1_u32bit("X509v3.BasicConstraints.path_constraint", 0); + } + +} diff --git a/src/lib/x509/pkcs10.h b/src/lib/x509/pkcs10.h new file mode 100644 index 000000000..c7a9ec300 --- /dev/null +++ b/src/lib/x509/pkcs10.h @@ -0,0 +1,112 @@ +/* +* PKCS #10 +* (C) 1999-2007 Jack Lloyd +* +* 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/x509_dn.h> +#include <botan/datastor.h> +#include <botan/key_constraint.h> +#include <botan/asn1_attribute.h> +#include <botan/asn1_alt_name.h> +#include <vector> + +namespace Botan { + +/** +* PKCS #10 Certificate Request. +*/ +class BOTAN_DLL 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 + */ + std::vector<byte> raw_public_key() const; + + /** + * Get the subject DN. + * @return subject DN + */ + X509_DN subject_dn() const; + + /** + * Get the subject alternative name. + * @return subject alternative name. + */ + 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 + */ + u32bit path_limit() const; + + /** + * Get the challenge password for this request + * @return challenge password for this request + */ + std::string challenge_password() 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<byte>& vec); + private: + void force_decode() override; + void handle_attribute(const Attribute&); + + Data_Store m_info; + }; + +} + +#endif diff --git a/src/lib/x509/x509_ca.cpp b/src/lib/x509/x509_ca.cpp new file mode 100644 index 000000000..179d903c4 --- /dev/null +++ b/src/lib/x509/x509_ca.cpp @@ -0,0 +1,266 @@ +/* +* 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/pubkey.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/bigint.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <botan/hash.h> +#include <botan/key_constraint.h> +#include <algorithm> +#include <typeinfo> +#include <iterator> +#include <set> + +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_cert(c) + { + if(!m_cert.is_CA_cert()) + throw Invalid_Argument("X509_CA: This certificate is not for a CA"); + + m_signer = choose_sig_format(key, rng, hash_fn, m_ca_sig_algo); + } + +/* +* X509_CA Destructor +*/ +X509_CA::~X509_CA() + { + delete m_signer; + } + +/* +* 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) + { + 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; + + extensions.add( + new Cert_Extension::Basic_Constraints(req.is_CA(), req.path_limit()), + true); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add(new Cert_Extension::Key_Usage(constraints), true); + } + + extensions.add(new Cert_Extension::Authority_Key_ID(m_cert.subject_key_id())); + extensions.add(new Cert_Extension::Subject_Key_ID(req.raw_public_key())); + + extensions.add( + new Cert_Extension::Subject_Alternative_Name(req.subject_alt_name())); + + extensions.add( + new Cert_Extension::Extended_Key_Usage(req.ex_constraints())); + + return make_cert(m_signer, rng, m_ca_sig_algo, + req.raw_public_key(), + not_before, not_after, + m_cert.subject_dn(), req.subject_dn(), + extensions); + } + +/* +* Create a new certificate +*/ +X509_Certificate X509_CA::make_cert(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& sig_algo, + const std::vector<byte>& 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; + const size_t SERIAL_BITS = 128; + + BigInt serial_no(rng, SERIAL_BITS); + + // 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, + u32bit next_update) const + { + std::vector<CRL_Entry> empty; + return make_crl(empty, 1, next_update, rng); + } + +/* +* 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, + u32bit next_update) const + { + std::vector<CRL_Entry> revoked = crl.get_revoked(); + + std::copy(new_revoked.begin(), new_revoked.end(), + std::back_inserter(revoked)); + + return make_crl(revoked, crl.crl_number() + 1, next_update, rng); + } + +/* +* Create a CRL +*/ +X509_CRL X509_CA::make_crl(const std::vector<CRL_Entry>& revoked, + u32bit crl_number, u32bit next_update, + RandomNumberGenerator& rng) const + { + const size_t X509_CRL_VERSION = 2; + + if(next_update == 0) + next_update = timespec_to_u32bit("7d"); + + // Totally stupid: ties encoding logic to the return of std::time!! + auto current_time = std::chrono::system_clock::now(); + auto expire_time = current_time + std::chrono::seconds(next_update); + + Extensions extensions; + extensions.add( + new Cert_Extension::Authority_Key_ID(m_cert.subject_key_id())); + extensions.add(new Cert_Extension::CRL_Number(crl_number)); + + // clang-format off + const std::vector<byte> crl = X509_Object::make_signed( + m_signer, rng, m_ca_sig_algo, + DER_Encoder().start_cons(SEQUENCE) + .encode(X509_CRL_VERSION-1) + .encode(m_ca_sig_algo) + .encode(m_cert.issuer_dn()) + .encode(X509_Time(current_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_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) + { + const std::string algo_name = key.algo_name(); + + std::unique_ptr<HashFunction> hash(HashFunction::create(hash_fn)); + if(!hash) + throw Algorithm_Not_Found(hash_fn); + + if(key.max_input_bits() < hash->output_length() * 8) + throw Invalid_Argument("Key is too small for chosen hash function"); + + std::string padding; + if(algo_name == "RSA") + { + padding = "EMSA3"; + } + else if(algo_name == "DSA" || algo_name == "ECDSA" || algo_name == "ECGDSA" || algo_name == "ECKCDSA") + { + padding = "EMSA1"; + } + else + { + throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); + } + + const Signature_Format format = (key.message_parts() > 1) ? DER_SEQUENCE : IEEE_1363; + + padding = padding + "(" + hash->name() + ")"; + + sig_algo.oid = OIDS::lookup(algo_name + "/" + padding); + sig_algo.parameters = key.algorithm_identifier().parameters; + + return new PK_Signer(key, rng, padding, format); + } + +} diff --git a/src/lib/x509/x509_ca.h b/src/lib/x509/x509_ca.h new file mode 100644 index 000000000..c96a709d5 --- /dev/null +++ b/src/lib/x509/x509_ca.h @@ -0,0 +1,145 @@ +/* +* 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 <botan/x509_ext.h> +#include <botan/pkcs10.h> +#include <botan/pubkey.h> + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +namespace Botan { + +/** +* This class represents X.509 Certificate Authorities (CAs). +*/ +class BOTAN_DLL X509_CA + { + 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); + + /** + * 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 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, + u32bit next_update = 0) 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, + u32bit next_update = 0) 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<byte>& 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); + +#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(); + private: + X509_CRL make_crl(const std::vector<CRL_Entry>& entries, + u32bit crl_number, u32bit next_update, + RandomNumberGenerator& rng) const; + + AlgorithmIdentifier m_ca_sig_algo; + X509_Certificate m_cert; + 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_DLL PK_Signer* choose_sig_format(const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& hash_fn, + AlgorithmIdentifier& alg_id); + +} + +#endif diff --git a/src/lib/x509/x509_crl.cpp b/src/lib/x509/x509_crl.cpp new file mode 100644 index 000000000..8eb4c01db --- /dev/null +++ b/src/lib/x509/x509_crl.cpp @@ -0,0 +1,202 @@ +/* +* 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 <botan/parsing.h> +#include <botan/bigint.h> +#include <botan/oids.h> + +namespace Botan { + +/* +* Load a X.509 CRL +*/ +X509_CRL::X509_CRL(DataSource& in, bool touc) : + X509_Object(in, "X509 CRL/CRL"), m_throw_on_unknown_critical(touc) + { + do_decode(); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +/* +* Load a X.509 CRL +*/ +X509_CRL::X509_CRL(const std::string& fsname, bool touc) : + X509_Object(fsname, "CRL/X509 CRL"), m_throw_on_unknown_critical(touc) + { + do_decode(); + } +#endif + +X509_CRL::X509_CRL(const std::vector<byte>& in, bool touc) : + X509_Object(in, "CRL/X509 CRL"), m_throw_on_unknown_critical(touc) + { + do_decode(); + } + +X509_CRL::X509_CRL(const X509_DN& issuer, const X509_Time& thisUpdate, + const X509_Time& nextUpdate, const std::vector<CRL_Entry>& revoked) : + X509_Object(), m_throw_on_unknown_critical(false), m_revoked(revoked) + { + m_info.add(issuer.contents()); + m_info.add("X509.CRL.start", thisUpdate.to_string()); + m_info.add("X509.CRL.end", nextUpdate.to_string()); + } + +/** +* 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<byte> crl_akid = authority_key_id(); + std::vector<byte> cert_akid = cert.authority_key_id(); + + if(!crl_akid.empty() && !cert_akid.empty()) + if(crl_akid != cert_akid) + return false; + + std::vector<byte> cert_serial = cert.serial_number(); + + bool is_revoked = false; + + for(size_t i = 0; i != m_revoked.size(); ++i) + { + if(cert_serial == m_revoked[i].serial_number()) + { + if(m_revoked[i].reason_code() == REMOVE_FROM_CRL) + is_revoked = false; + else + is_revoked = true; + } + } + + return is_revoked; + } + +/* +* Decode the TBSCertList data +*/ +void X509_CRL::force_decode() + { + BER_Decoder tbs_crl(m_tbs_bits); + + size_t version; + tbs_crl.decode_optional(version, INTEGER, UNIVERSAL); + + if(version != 0 && version != 1) + throw X509_CRL_Error("Unknown X.509 CRL version " + + std::to_string(version+1)); + + AlgorithmIdentifier sig_algo_inner; + tbs_crl.decode(sig_algo_inner); + + if(m_sig_algo != sig_algo_inner) + throw X509_CRL_Error("Algorithm identifier mismatch"); + + X509_DN dn_issuer; + tbs_crl.decode(dn_issuer); + m_info.add(dn_issuer.contents()); + + X509_Time start, end; + tbs_crl.decode(start).decode(end); + m_info.add("X509.CRL.start", start.to_string()); + m_info.add("X509.CRL.end", end.to_string()); + + BER_Object next = tbs_crl.get_next_object(); + + if(next.type_tag == SEQUENCE && next.class_tag == CONSTRUCTED) + { + BER_Decoder cert_list(next.value); + + while(cert_list.more_items()) + { + CRL_Entry entry(m_throw_on_unknown_critical); + cert_list.decode(entry); + m_revoked.push_back(entry); + } + next = tbs_crl.get_next_object(); + } + + if(next.type_tag == 0 && + next.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + { + BER_Decoder crl_options(next.value); + + Extensions extensions(m_throw_on_unknown_critical); + + crl_options.decode(extensions).verify_end(); + + extensions.contents_to(m_info, m_info); + + next = tbs_crl.get_next_object(); + } + + if(next.type_tag != NO_OBJECT) + throw X509_CRL_Error("Unknown tag in CRL"); + + tbs_crl.verify_end(); + } + +/* +* Return the list of revoked certificates +*/ +std::vector<CRL_Entry> X509_CRL::get_revoked() const + { + return m_revoked; + } + +/* +* Return the distinguished name of the issuer +*/ +X509_DN X509_CRL::issuer_dn() const + { + return create_dn(m_info); + } + +/* +* Return the key identifier of the issuer +*/ +std::vector<byte> X509_CRL::authority_key_id() const + { + return m_info.get1_memvec("X509v3.AuthorityKeyIdentifier"); + } + +/* +* Return the CRL number of this CRL +*/ +u32bit X509_CRL::crl_number() const + { + return m_info.get1_u32bit("X509v3.CRLNumber"); + } + +/* +* Return the issue data of the CRL +*/ +X509_Time X509_CRL::this_update() const + { + return X509_Time(m_info.get1("X509.CRL.start"), ASN1_Tag::UTC_OR_GENERALIZED_TIME); + } + +/* +* Return the date when a new CRL will be issued +*/ +X509_Time X509_CRL::next_update() const + { + return X509_Time(m_info.get1("X509.CRL.end"), ASN1_Tag::UTC_OR_GENERALIZED_TIME); + } + +} diff --git a/src/lib/x509/x509_crl.h b/src/lib/x509/x509_crl.h new file mode 100644 index 000000000..e11ea8f48 --- /dev/null +++ b/src/lib/x509/x509_crl.h @@ -0,0 +1,125 @@ +/* +* 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/x509_dn.h> +#include <botan/crl_ent.h> +#include <botan/datastor.h> +#include <vector> + +namespace Botan { + +class X509_Certificate; + +/** +* This class represents X.509 Certificate Revocation Lists (CRLs). +*/ +class BOTAN_DLL X509_CRL final : public X509_Object + { + public: + /** + * This class represents CRL related errors. + */ + struct BOTAN_DLL X509_CRL_Error : public Exception + { + explicit X509_CRL_Error(const std::string& error) : + Exception("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. + */ + std::vector<CRL_Entry> get_revoked() const; + + /** + * Get the issuer DN of this CRL. + * @return CRLs issuer DN + */ + X509_DN issuer_dn() const; + + /** + * Get the AuthorityKeyIdentifier of this CRL. + * @return this CRLs AuthorityKeyIdentifier + */ + std::vector<byte> authority_key_id() const; + + /** + * Get the serial number of this CRL. + * @return CRLs serial number + */ + u32bit crl_number() const; + + /** + * Get the CRL's thisUpdate value. + * @return CRLs thisUpdate + */ + X509_Time this_update() const; + + /** + * Get the CRL's nextUpdate value. + * @return CRLs nextdUpdate + */ + X509_Time next_update() const; + + /** + * Construct a CRL from a data source. + * @param source the data source providing the DER or PEM encoded CRL. + * @param throw_on_unknown_critical should we throw an exception + * if an unknown CRL extension marked as critical is encountered. + */ + X509_CRL(DataSource& source, bool throw_on_unknown_critical = false); + +#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 + * @param throw_on_unknown_critical should we throw an exception + * if an unknown CRL extension marked as critical is encountered. + */ + X509_CRL(const std::string& filename, + bool throw_on_unknown_critical = false); +#endif + + /** + * Construct a CRL from a binary vector + * @param vec the binary (DER) representation of the CRL + * @param throw_on_unknown_critical should we throw an exception + * if an unknown CRL extension marked as critical is encountered. + */ + X509_CRL(const std::vector<byte>& vec, + bool throw_on_unknown_critical = false); + + /** + * 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: + void force_decode() override; + + bool m_throw_on_unknown_critical; + std::vector<CRL_Entry> m_revoked; + Data_Store m_info; + }; + +} + +#endif diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp new file mode 100644 index 000000000..23340f784 --- /dev/null +++ b/src/lib/x509/x509_ext.cpp @@ -0,0 +1,834 @@ +/* +* X.509 Certificate Extensions +* (C) 1999-2010,2012 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_ext.h> +#include <botan/x509cert.h> +#include <botan/sha160.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/oids.h> +#include <botan/charset.h> +#include <botan/internal/bit_ops.h> +#include <algorithm> +#include <sstream> + +namespace Botan { + +/* +* List of X.509 Certificate Extensions +*/ +Certificate_Extension* Extensions::get_extension(const OID& oid, bool critical) + { +#define X509_EXTENSION(NAME, TYPE) \ + if(OIDS::name_of(oid, NAME)) \ + return new Cert_Extension::TYPE(); + + X509_EXTENSION("X509v3.KeyUsage", Key_Usage); + X509_EXTENSION("X509v3.BasicConstraints", Basic_Constraints); + X509_EXTENSION("X509v3.SubjectKeyIdentifier", Subject_Key_ID); + X509_EXTENSION("X509v3.AuthorityKeyIdentifier", Authority_Key_ID); + X509_EXTENSION("X509v3.ExtendedKeyUsage", Extended_Key_Usage); + X509_EXTENSION("X509v3.IssuerAlternativeName", Issuer_Alternative_Name); + X509_EXTENSION("X509v3.SubjectAlternativeName", Subject_Alternative_Name); + X509_EXTENSION("X509v3.NameConstraints", Name_Constraints); + X509_EXTENSION("X509v3.CertificatePolicies", Certificate_Policies); + X509_EXTENSION("X509v3.CRLDistributionPoints", CRL_Distribution_Points); + X509_EXTENSION("PKIX.AuthorityInformationAccess", Authority_Information_Access); + X509_EXTENSION("X509v3.CRLNumber", CRL_Number); + X509_EXTENSION("X509v3.ReasonCode", CRL_ReasonCode); + + return critical ? new Cert_Extension::Unknown_Critical_Extension(oid) : nullptr; + } + +/* +* Extensions Copy Constructor +*/ +Extensions::Extensions(const Extensions& extensions) : ASN1_Object() + { + *this = extensions; + } + +/* +* Extensions Assignment Operator +*/ +Extensions& Extensions::operator=(const Extensions& other) + { + m_extensions.clear(); + + for(size_t i = 0; i != other.m_extensions.size(); ++i) + m_extensions.push_back( + std::make_pair(std::unique_ptr<Certificate_Extension>(other.m_extensions[i].first->copy()), + other.m_extensions[i].second)); + + m_extensions_raw = other.m_extensions_raw; + m_throw_on_unknown_critical = other.m_throw_on_unknown_critical; + + return (*this); + } + +/* +* Return the OID of this extension +*/ +OID Certificate_Extension::oid_of() const + { + return OIDS::lookup(oid_name()); + } + +/* +* 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) + { + } + +void Extensions::add(Certificate_Extension* extn, bool critical) + { + m_extensions.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(extn), critical)); + m_extensions_raw.emplace(extn->oid_of(), std::make_pair(extn->encode_inner(), critical)); + } + +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_extensions) + { + exts.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(ext.first->copy()), ext.second)); + } + return exts; + } + +std::map<OID, std::pair<std::vector<byte>, bool>> Extensions::extensions_raw() const + { + return m_extensions_raw; + } + +/* +* Encode an Extensions list +*/ +void Extensions::encode_into(DER_Encoder& to_object) const + { + for(size_t i = 0; i != m_extensions.size(); ++i) + { + const Certificate_Extension* ext = m_extensions[i].first.get(); + const bool is_critical = m_extensions[i].second; + + const bool should_encode = ext->should_encode(); + + if(should_encode) + { + to_object.start_cons(SEQUENCE) + .encode(ext->oid_of()) + .encode_optional(is_critical, false) + .encode(ext->encode_inner(), OCTET_STRING) + .end_cons(); + } + } + } + +/* +* Decode a list of Extensions +*/ +void Extensions::decode_from(BER_Decoder& from_source) + { + m_extensions.clear(); + m_extensions_raw.clear(); + + BER_Decoder sequence = from_source.start_cons(SEQUENCE); + + while(sequence.more_items()) + { + OID oid; + std::vector<byte> value; + bool critical; + + sequence.start_cons(SEQUENCE) + .decode(oid) + .decode_optional(critical, BOOLEAN, UNIVERSAL, false) + .decode(value, OCTET_STRING) + .verify_end() + .end_cons(); + + m_extensions_raw.emplace(oid, std::make_pair(value, critical)); + + std::unique_ptr<Certificate_Extension> ext(get_extension(oid, critical)); + + if(!ext && critical && m_throw_on_unknown_critical) + throw Decoding_Error("Encountered unknown X.509 extension marked " + "as critical; OID = " + oid.as_string()); + + if(ext) + { + try + { + ext->decode_inner(value); + } + catch(std::exception& e) + { + throw Decoding_Error("Exception while decoding extension " + + oid.as_string() + ": " + e.what()); + } + + m_extensions.push_back(std::make_pair(std::move(ext), critical)); + } + } + + sequence.verify_end(); + } + +/* +* Write the extensions to an info store +*/ +void Extensions::contents_to(Data_Store& subject_info, + Data_Store& issuer_info) const + { + for(size_t i = 0; i != m_extensions.size(); ++i) + { + m_extensions[i].first->contents_to(subject_info, issuer_info); + subject_info.add(m_extensions[i].first->oid_name() + ".is_critical", (m_extensions[i].second ? 1 : 0)); + } + } + + +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<byte> Basic_Constraints::encode_inner() const + { + return DER_Encoder() + .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() + .get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Basic_Constraints::decode_inner(const std::vector<byte>& 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) + .verify_end() + .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<u32bit>(m_path_limit)); + } + +/* +* Encode the extension +*/ +std::vector<byte> Key_Usage::encode_inner() const + { + if(m_constraints == NO_CONSTRAINTS) + throw Encoding_Error("Cannot encode zero usage constraints"); + + const size_t unused_bits = low_bit(m_constraints) - 1; + + std::vector<byte> 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<byte>& in) + { + BER_Decoder ber(in); + + BER_Object obj = ber.get_next_object(); + + if(obj.type_tag != BIT_STRING || obj.class_tag != UNIVERSAL) + throw BER_Bad_Tag("Bad tag for usage constraint", + obj.type_tag, obj.class_tag); + + if(obj.value.size() != 2 && obj.value.size() != 3) + throw BER_Decoding_Error("Bad size for BITSTRING in usage constraint"); + + if(obj.value[0] >= 8) + throw BER_Decoding_Error("Invalid unused bits in usage constraint"); + + obj.value[obj.value.size()-1] &= (0xFF << obj.value[0]); + + u16bit usage = 0; + for(size_t i = 1; i != obj.value.size(); ++i) + { + usage = (obj.value[i] << 8*(sizeof(usage)-i)) | usage; + } + + 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<byte> Subject_Key_ID::encode_inner() const + { + return DER_Encoder().encode(m_key_id, OCTET_STRING).get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Subject_Key_ID::decode_inner(const std::vector<byte>& 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<byte>& pub_key) : m_key_id(unlock(SHA_160().process(pub_key))) + {} + +/* +* Encode the extension +*/ +std::vector<byte> Authority_Key_ID::encode_inner() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode(m_key_id, OCTET_STRING, ASN1_Tag(0), CONTEXT_SPECIFIC) + .end_cons() + .get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Authority_Key_ID::decode_inner(const std::vector<byte>& 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<byte> Alternative_Name::encode_inner() const + { + return DER_Encoder().encode(m_alt_name).get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Alternative_Name::decode_inner(const std::vector<byte>& in) + { + BER_Decoder(in).decode(m_alt_name); + } + +/* +* Return a textual representation +*/ +void Alternative_Name::contents_to(Data_Store& subject_info, + Data_Store& issuer_info) const + { + std::multimap<std::string, std::string> contents = + get_alt_name().contents(); + + if(m_oid_name_str == "X509v3.SubjectAlternativeName") + subject_info.add(contents); + else if(m_oid_name_str == "X509v3.IssuerAlternativeName") + issuer_info.add(contents); + else + throw Internal_Error("In Alternative_Name, unknown type " + + m_oid_name_str); + } + +/* +* Alternative_Name Constructor +*/ +Alternative_Name::Alternative_Name(const AlternativeName& alt_name, + const std::string& oid_name_str) : + m_oid_name_str(oid_name_str), + m_alt_name(alt_name) + {} + +/* +* Subject_Alternative_Name Constructor +*/ +Subject_Alternative_Name::Subject_Alternative_Name( + const AlternativeName& name) : + Alternative_Name(name, "X509v3.SubjectAlternativeName") + { + } + +/* +* Issuer_Alternative_Name Constructor +*/ +Issuer_Alternative_Name::Issuer_Alternative_Name(const AlternativeName& name) : + Alternative_Name(name, "X509v3.IssuerAlternativeName") + { + } + +/* +* Encode the extension +*/ +std::vector<byte> Extended_Key_Usage::encode_inner() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode_list(m_oids) + .end_cons() + .get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Extended_Key_Usage::decode_inner(const std::vector<byte>& 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].as_string()); + } + +/* +* Encode the extension +*/ +std::vector<byte> Name_Constraints::encode_inner() const + { + throw Not_Implemented("Name_Constraints encoding"); + } + + +/* +* Decode the extension +*/ +void Name_Constraints::decode_inner(const std::vector<byte>& 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.type_tag == 0 && per.class_tag == 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.type_tag == 1 && per.class_tag == 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() || !subject.is_critical("X509v3.NameConstraints")) + cert_status.at(pos).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + + const bool at_self_signed_root = (pos == cert_path.size() - 1); + + // Check that all subordinate certs pass the name constraint + for(size_t j = 0; j <= pos; ++j) + { + if(pos == j && at_self_signed_root) + continue; + + 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.is_critical("X509v3.NameConstraints"); + 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.is_critical("X509v3.NameConstraints"); + break; + default: + break; + } + } + + if(failed || !permitted) + { + cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + } + } + } + } + +namespace { + +/* +* A policy specifier +*/ +class Policy_Information : public ASN1_Object + { + public: + Policy_Information() {} + 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<byte> 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])); + + return DER_Encoder() + .start_cons(SEQUENCE) + .encode_list(policies) + .end_cons() + .get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void Certificate_Policies::decode_inner(const std::vector<byte>& 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].as_string()); + } + +std::vector<byte> Authority_Information_Access::encode_inner() const + { + ASN1_String url(m_ocsp_responder, IA5_STRING); + + return DER_Encoder() + .start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .encode(OIDS::lookup("PKIX.OCSP")) + .add_object(ASN1_Tag(6), CONTEXT_SPECIFIC, url.iso_8859()) + .end_cons() + .end_cons().get_contents_unlocked(); + } + +void Authority_Information_Access::decode_inner(const std::vector<byte>& 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 == OIDS::lookup("PKIX.OCSP")) + { + BER_Object name = info.get_next_object(); + + if(name.type_tag == 6 && name.class_tag == CONTEXT_SPECIFIC) + { + m_ocsp_responder = Charset::transcode(ASN1::to_string(name), + LATIN1_CHARSET, + LOCAL_CHARSET); + } + + } + } + } + +void Authority_Information_Access::contents_to(Data_Store& subject, Data_Store&) const + { + if(!m_ocsp_responder.empty()) + subject.add("OCSP.responder", m_ocsp_responder); + } + +/* +* 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<byte> CRL_Number::encode_inner() const + { + return DER_Encoder().encode(m_crl_number).get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void CRL_Number::decode_inner(const std::vector<byte>& in) + { + BER_Decoder(in).decode(m_crl_number); + } + +/* +* Return a textual representation +*/ +void CRL_Number::contents_to(Data_Store& info, Data_Store&) const + { + info.add("X509v3.CRLNumber", static_cast<u32bit>(m_crl_number)); + } + +/* +* Encode the extension +*/ +std::vector<byte> CRL_ReasonCode::encode_inner() const + { + return DER_Encoder() + .encode(static_cast<size_t>(m_reason), ENUMERATED, UNIVERSAL) + .get_contents_unlocked(); + } + +/* +* Decode the extension +*/ +void CRL_ReasonCode::decode_inner(const std::vector<byte>& 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<byte> CRL_Distribution_Points::encode_inner() const + { + throw Not_Implemented("CRL_Distribution_Points encoding"); + } + +void CRL_Distribution_Points::decode_inner(const std::vector<byte>& buf) + { + BER_Decoder(buf).decode_list(m_distribution_points).verify_end(); + } + +void CRL_Distribution_Points::contents_to(Data_Store& info, Data_Store&) const + { + for(size_t i = 0; i != m_distribution_points.size(); ++i) + { + auto point = m_distribution_points[i].point().contents(); + + auto uris = point.equal_range("URI"); + + for(auto uri = uris.first; uri != uris.second; ++uri) + info.add("CRL.DistributionPoint", uri->second); + } + } + +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<byte> Unknown_Critical_Extension::encode_inner() const + { + throw Not_Implemented("Unknown_Critical_Extension encoding"); + } + +void Unknown_Critical_Extension::decode_inner(const std::vector<byte>&) + { + } + +void Unknown_Critical_Extension::contents_to(Data_Store&, Data_Store&) const + { + } + +} + +} diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h new file mode 100644 index 000000000..b1984fa94 --- /dev/null +++ b/src/lib/x509/x509_ext.h @@ -0,0 +1,505 @@ +/* +* 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/asn1_obj.h> +#include <botan/asn1_oid.h> +#include <botan/asn1_alt_name.h> +#include <botan/cert_status.h> +#include <botan/datastor.h> +#include <botan/name_constraint.h> +#include <botan/key_constraint.h> +#include <botan/crl_ent.h> + +namespace Botan { + +class X509_Certificate; + +/** +* X.509 Certificate Extension +*/ +class BOTAN_DLL Certificate_Extension + { + public: + /** + * @return OID representing this extension + */ + virtual OID oid_of() const; + + /** + * 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; + + /* + * @return specific OID name + */ + virtual std::string oid_name() 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() {} + protected: + friend class Extensions; + virtual bool should_encode() const { return true; } + virtual std::vector<byte> encode_inner() const = 0; + virtual void decode_inner(const std::vector<byte>&) = 0; + }; + +/** +* X.509 Certificate Extension List +*/ +class BOTAN_DLL Extensions : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + void contents_to(Data_Store&, Data_Store&) const; + + void add(Certificate_Extension* extn, bool critical = false); + + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const; + + std::map<OID, std::pair<std::vector<byte>, bool>> extensions_raw() const; + + Extensions& operator=(const Extensions&); + + Extensions(const Extensions&); + + explicit Extensions(bool st = true) : m_throw_on_unknown_critical(st) {} + + private: + static Certificate_Extension* get_extension(const OID&, bool); + + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> m_extensions; + bool m_throw_on_unknown_critical; + std::map<OID, std::pair<std::vector<byte>, bool>> m_extensions_raw; + }; + +namespace Cert_Extension { + +static const size_t NO_CERT_PATH_LIMIT = 0xFFFFFFF0; + +/** +* Basic Constraints Extension +*/ +class BOTAN_DLL 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; + + private: + std::string oid_name() const override + { return "X509v3.BasicConstraints"; } + + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) 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_DLL 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; } + + private: + std::string oid_name() const override { return "X509v3.KeyUsage"; } + + bool should_encode() const override + { return (m_constraints != NO_CONSTRAINTS); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + Key_Constraints m_constraints; + }; + +/** +* Subject Key Identifier Extension +*/ +class BOTAN_DLL Subject_Key_ID final : public Certificate_Extension + { + public: + Subject_Key_ID* copy() const override + { return new Subject_Key_ID(m_key_id); } + + Subject_Key_ID() {} + explicit Subject_Key_ID(const std::vector<byte>&); + + std::vector<byte> get_key_id() const { return m_key_id; } + private: + std::string oid_name() const override + { return "X509v3.SubjectKeyIdentifier"; } + + bool should_encode() const override { return (m_key_id.size() > 0); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<byte> m_key_id; + }; + +/** +* Authority Key Identifier Extension +*/ +class BOTAN_DLL 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() {} + explicit Authority_Key_ID(const std::vector<byte>& k) : m_key_id(k) {} + + std::vector<byte> get_key_id() const { return m_key_id; } + + private: + std::string oid_name() const override + { return "X509v3.AuthorityKeyIdentifier"; } + + bool should_encode() const override { return (m_key_id.size() > 0); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<byte> m_key_id; + }; + +/** +* Alternative Name Extension Base Class +*/ +class BOTAN_DLL Alternative_Name : public Certificate_Extension + { + public: + AlternativeName get_alt_name() const { return m_alt_name; } + + protected: + Alternative_Name(const AlternativeName&, const std::string& oid_name); + + Alternative_Name(const std::string&, const std::string&); + + private: + std::string oid_name() const override { return m_oid_name_str; } + + bool should_encode() const override { return m_alt_name.has_items(); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::string m_oid_name_str; + AlternativeName m_alt_name; + }; + +/** +* Subject Alternative Name Extension +*/ +class BOTAN_DLL Subject_Alternative_Name : public Alternative_Name + { + public: + Subject_Alternative_Name* copy() const override + { return new Subject_Alternative_Name(get_alt_name()); } + + explicit Subject_Alternative_Name(const AlternativeName& = AlternativeName()); + }; + +/** +* Issuer Alternative Name Extension +*/ +class BOTAN_DLL Issuer_Alternative_Name : public Alternative_Name + { + public: + Issuer_Alternative_Name* copy() const override + { return new Issuer_Alternative_Name(get_alt_name()); } + + explicit Issuer_Alternative_Name(const AlternativeName& = AlternativeName()); + }; + +/** +* Extended Key Usage Extension +*/ +class BOTAN_DLL Extended_Key_Usage final : public Certificate_Extension + { + public: + Extended_Key_Usage* copy() const override + { return new Extended_Key_Usage(m_oids); } + + Extended_Key_Usage() {} + explicit Extended_Key_Usage(const std::vector<OID>& o) : m_oids(o) {} + + std::vector<OID> get_oids() const { return m_oids; } + + private: + std::string oid_name() const override + { return "X509v3.ExtendedKeyUsage"; } + + bool should_encode() const override { return (m_oids.size() > 0); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<OID> m_oids; + }; + +/** +* Name Constraints +*/ +class BOTAN_DLL Name_Constraints : public Certificate_Extension + { + public: + Name_Constraints* copy() const override + { return new Name_Constraints(m_name_constraints); } + + Name_Constraints() {} + 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; + + private: + std::string oid_name() const override + { return "X509v3.NameConstraints"; } + + bool should_encode() const override { return true; } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + NameConstraints m_name_constraints; + }; + +/** +* Certificate Policies Extension +*/ +class BOTAN_DLL Certificate_Policies final : public Certificate_Extension + { + public: + Certificate_Policies* copy() const override + { return new Certificate_Policies(m_oids); } + + Certificate_Policies() {} + explicit Certificate_Policies(const std::vector<OID>& o) : m_oids(o) {} + + std::vector<OID> get_oids() const { return m_oids; } + + private: + std::string oid_name() const override + { return "X509v3.CertificatePolicies"; } + + bool should_encode() const override { return (m_oids.size() > 0); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<OID> m_oids; + }; + +class BOTAN_DLL Authority_Information_Access final : public Certificate_Extension + { + public: + Authority_Information_Access* copy() const override + { return new Authority_Information_Access(m_ocsp_responder); } + + Authority_Information_Access() {} + + explicit Authority_Information_Access(const std::string& ocsp) : + m_ocsp_responder(ocsp) {} + + private: + std::string oid_name() const override + { return "PKIX.AuthorityInformationAccess"; } + + bool should_encode() const override { return (!m_ocsp_responder.empty()); } + + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + + void contents_to(Data_Store&, Data_Store&) const override; + + std::string m_ocsp_responder; + }; + +/** +* CRL Number Extension +*/ +class BOTAN_DLL 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; + + private: + std::string oid_name() const override { return "X509v3.CRLNumber"; } + + bool should_encode() const override { return m_has_value; } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) 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_DLL 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; } + + private: + std::string oid_name() const override { return "X509v3.ReasonCode"; } + + bool should_encode() const override { return (m_reason != UNSPECIFIED); } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + CRL_Code m_reason; + }; + +/** +* CRL Distribution Points Extension +*/ +class BOTAN_DLL CRL_Distribution_Points final : public Certificate_Extension + { + public: + class BOTAN_DLL 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() {} + + explicit CRL_Distribution_Points(const std::vector<Distribution_Point>& points) : + m_distribution_points(points) {} + + std::vector<Distribution_Point> distribution_points() const + { return m_distribution_points; } + + private: + std::string oid_name() const override + { return "X509v3.CRLDistributionPoints"; } + + bool should_encode() const override + { return !m_distribution_points.empty(); } + + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + std::vector<Distribution_Point> m_distribution_points; + }; + +/** +* An unknown X.509 extension marked as critical +* Will always add a failure to the path validation result. +*/ +class BOTAN_DLL Unknown_Critical_Extension final : public Certificate_Extension + { + public: + explicit Unknown_Critical_Extension(OID oid) : m_oid(oid) {} + + Unknown_Critical_Extension* copy() const override + { return new Unknown_Critical_Extension(m_oid); } + + OID oid_of() const override + { return m_oid; }; + + 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 + { + cert_status.at(pos).insert(Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION); + } + + private: + std::string oid_name() const override + { return "Unknown OID name"; } + + bool should_encode() const override { return false; } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + OID m_oid; + }; + +} + +} + +#endif diff --git a/src/lib/x509/x509_obj.cpp b/src/lib/x509/x509_obj.cpp new file mode 100644 index 000000000..3c5d2a9b4 --- /dev/null +++ b/src/lib/x509/x509_obj.cpp @@ -0,0 +1,249 @@ +/* +* X.509 SIGNED Object +* (C) 1999-2007 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509_obj.h> +#include <botan/x509_key.h> +#include <botan/pubkey.h> +#include <botan/oids.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/pem.h> +#include <algorithm> + +namespace Botan { + +/* +* Create a generic X.509 object +*/ +X509_Object::X509_Object(DataSource& stream, const std::string& labels) + { + init(stream, labels); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +/* +* Create a generic X.509 object +*/ +X509_Object::X509_Object(const std::string& file, const std::string& labels) + { + DataSource_Stream stream(file, true); + init(stream, labels); + } +#endif + +/* +* Create a generic X.509 object +*/ +X509_Object::X509_Object(const std::vector<byte>& vec, const std::string& labels) + { + DataSource_Memory stream(vec.data(), vec.size()); + init(stream, labels); + } + +/* +* Read a PEM or BER X.509 object +*/ +void X509_Object::init(DataSource& in, const std::string& labels) + { + m_PEM_labels_allowed = split_on(labels, '/'); + if(m_PEM_labels_allowed.size() < 1) + throw Invalid_Argument("Bad labels argument to X509_Object"); + + m_PEM_label_pref = m_PEM_labels_allowed[0]; + std::sort(m_PEM_labels_allowed.begin(), m_PEM_labels_allowed.end()); + + 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(!std::binary_search(m_PEM_labels_allowed.begin(), + m_PEM_labels_allowed.end(), got_label)) + throw Decoding_Error("Invalid PEM label: " + got_label); + + BER_Decoder dec(ber); + decode_from(dec); + } + } + catch(Decoding_Error& e) + { + throw Decoding_Error(m_PEM_label_pref + " decoding failed: " + e.what()); + } + } + + +void X509_Object::encode_into(DER_Encoder& to) const + { + to.start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .raw_bytes(m_tbs_bits) + .end_cons() + .encode(m_sig_algo) + .encode(m_sig, 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) + .verify_end() + .end_cons(); + } + +/* +* Return a BER encoded X.509 object +*/ +std::vector<byte> X509_Object::BER_encode() const + { + DER_Encoder der; + encode_into(der); + return der.get_contents_unlocked(); + } + +/* +* Return a PEM encoded X.509 object +*/ +std::string X509_Object::PEM_encode() const + { + return PEM_Code::encode(BER_encode(), m_PEM_label_pref); + } + +/* +* Return the TBS data +*/ +std::vector<byte> X509_Object::tbs_data() const + { + return ASN1::put_in_sequence(m_tbs_bits); + } + +/* +* Return the signature of this object +*/ +std::vector<byte> X509_Object::signature() const + { + return m_sig; + } + +/* +* Return the algorithm used to sign this object +*/ +AlgorithmIdentifier X509_Object::signature_algorithm() const + { + return m_sig_algo; + } + +/* +* Return the hash used in generating the signature +*/ +std::string X509_Object::hash_used_for_signature() const + { + std::vector<std::string> sig_info = + split_on(OIDS::lookup(m_sig_algo.oid), '/'); + + if(sig_info.size() != 2) + throw Internal_Error("Invalid name format found for " + + m_sig_algo.oid.as_string()); + + 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 Exception("No key provided for " + m_PEM_label_pref + " signature check"); + std::unique_ptr<const Public_Key> key(pub_key); + return check_signature(*key); +} + +/* +* Check the signature on an object +*/ +bool X509_Object::check_signature(const Public_Key& pub_key) const + { + try { + std::vector<std::string> sig_info = + split_on(OIDS::lookup(m_sig_algo.oid), '/'); + + if(sig_info.size() != 2 || sig_info[0] != pub_key.algo_name()) + return false; + + std::string padding = sig_info[1]; + Signature_Format format = + (pub_key.message_parts() >= 2) ? DER_SEQUENCE : IEEE_1363; + + PK_Verifier verifier(pub_key, padding, format); + + return verifier.verify_message(tbs_data(), signature()); + } + catch(std::exception&) + { + return false; + } + } + +/* +* Apply the X.509 SIGNED macro +*/ +std::vector<byte> X509_Object::make_signed(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& algo, + const secure_vector<byte>& tbs_bits) + { + return DER_Encoder() + .start_cons(SEQUENCE) + .raw_bytes(tbs_bits) + .encode(algo) + .encode(signer->sign_message(tbs_bits, rng), BIT_STRING) + .end_cons() + .get_contents_unlocked(); + } + +/* +* Try to decode the actual information +*/ +void X509_Object::do_decode() + { + try { + force_decode(); + } + catch(Decoding_Error& e) + { + throw Decoding_Error(m_PEM_label_pref + " decoding failed (" + + e.what() + ")"); + } + catch(Invalid_Argument& e) + { + throw Decoding_Error(m_PEM_label_pref + " decoding failed (" + + e.what() + ")"); + } + } + +} diff --git a/src/lib/x509/x509_obj.h b/src/lib/x509/x509_obj.h new file mode 100644 index 000000000..40324775c --- /dev/null +++ b/src/lib/x509/x509_obj.h @@ -0,0 +1,119 @@ +/* +* 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/x509_key.h> +#include <botan/rng.h> +#include <vector> + +namespace Botan { + +/** +* This class represents abstract X.509 signed objects as +* in the X.500 SIGNED macro +*/ +class BOTAN_DLL 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<byte> tbs_data() const; + + /** + * @return signature on tbs_data() + */ + std::vector<byte> signature() const; + + /** + * @return signature algorithm that was used to generate signature + */ + AlgorithmIdentifier signature_algorithm() const; + + /** + * @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<byte> make_signed(class PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& alg_id, + const secure_vector<byte>& tbs); + + /** + * 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 pointer will be deleted after use + * @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 BER encoding of this + */ + std::vector<byte> BER_encode() const; + + /** + * @return PEM encoding of this + */ + std::string PEM_encode() const; + + virtual ~X509_Object() {} + protected: + X509_Object(DataSource& src, const std::string& pem_labels); + X509_Object(const std::vector<byte>& vec, const std::string& labels); + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + X509_Object(const std::string& file, const std::string& pem_labels); +#endif + + void do_decode(); + X509_Object() {} + AlgorithmIdentifier m_sig_algo; + std::vector<byte> m_tbs_bits, m_sig; + private: + virtual void force_decode() = 0; + void init(DataSource&, const std::string&); + + std::vector<std::string> m_PEM_labels_allowed; + std::string m_PEM_label_pref; + }; + +} + +#endif diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp new file mode 100644 index 000000000..f56495a79 --- /dev/null +++ b/src/lib/x509/x509cert.cpp @@ -0,0 +1,672 @@ +/* +* X.509 Certificates +* (C) 1999-2010,2015 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509cert.h> +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/internal/stl_util.h> +#include <botan/parsing.h> +#include <botan/bigint.h> +#include <botan/oids.h> +#include <botan/pem.h> +#include <botan/hash.h> +#include <botan/hex.h> +#include <algorithm> +#include <iterator> +#include <sstream> + +namespace Botan { + +namespace { + +/* +* Lookup each OID in the vector +*/ +std::vector<std::string> lookup_oids(const std::vector<std::string>& in) + { + std::vector<std::string> out; + + for(auto i = in.begin(); i != in.end(); ++i) + out.push_back(OIDS::lookup(OID(*i))); + return out; + } + +} + +/* +* X509_Certificate Constructor +*/ +X509_Certificate::X509_Certificate(DataSource& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) + { + do_decode(); + } + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) +/* +* X509_Certificate Constructor +*/ +X509_Certificate::X509_Certificate(const std::string& fsname) : + X509_Object(fsname, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) + { + do_decode(); + } +#endif + +/* +* X509_Certificate Constructor +*/ +X509_Certificate::X509_Certificate(const std::vector<byte>& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) + { + do_decode(); + } + +/* +* Decode the TBSCertificate data +*/ +void X509_Certificate::force_decode() + { + size_t version; + BigInt serial_bn; + AlgorithmIdentifier sig_algo_inner; + X509_DN dn_issuer, dn_subject; + X509_Time start, end; + + BER_Decoder tbs_cert(m_tbs_bits); + + tbs_cert.decode_optional(version, ASN1_Tag(0), + ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + .decode(serial_bn) + .decode(sig_algo_inner) + .decode(dn_issuer) + .start_cons(SEQUENCE) + .decode(start) + .decode(end) + .verify_end() + .end_cons() + .decode(dn_subject); + + if(version > 2) + throw Decoding_Error("Unknown X.509 cert version " + std::to_string(version)); + if(m_sig_algo != sig_algo_inner) + throw Decoding_Error("Algorithm identifier mismatch"); + + + m_subject.add(dn_subject.contents()); + m_issuer.add(dn_issuer.contents()); + + m_subject.add("X509.Certificate.dn_bits", ASN1::put_in_sequence(dn_subject.get_bits())); + m_issuer.add("X509.Certificate.dn_bits", ASN1::put_in_sequence(dn_issuer.get_bits())); + + BER_Object public_key = tbs_cert.get_next_object(); + if(public_key.type_tag != SEQUENCE || public_key.class_tag != CONSTRUCTED) + throw BER_Bad_Tag("X509_Certificate: Unexpected tag for public key", + public_key.type_tag, public_key.class_tag); + + std::vector<byte> v2_issuer_key_id, v2_subject_key_id; + + tbs_cert.decode_optional_string(v2_issuer_key_id, BIT_STRING, 1); + tbs_cert.decode_optional_string(v2_subject_key_id, BIT_STRING, 2); + + BER_Object v3_exts_data = tbs_cert.get_next_object(); + if(v3_exts_data.type_tag == 3 && + v3_exts_data.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + { + BER_Decoder(v3_exts_data.value).decode(m_v3_extensions).verify_end(); + m_v3_extensions.contents_to(m_subject, m_issuer); + } + else if(v3_exts_data.type_tag != NO_OBJECT) + throw BER_Bad_Tag("Unknown tag in X.509 cert", + v3_exts_data.type_tag, v3_exts_data.class_tag); + + if(tbs_cert.more_items()) + throw Decoding_Error("TBSCertificate has more items that expected"); + + m_subject.add("X509.Certificate.version", static_cast<u32bit>(version)); + m_subject.add("X509.Certificate.serial", BigInt::encode(serial_bn)); + m_subject.add("X509.Certificate.start", start.to_string()); + m_subject.add("X509.Certificate.end", end.to_string()); + + m_issuer.add("X509.Certificate.v2.key_id", v2_issuer_key_id); + m_subject.add("X509.Certificate.v2.key_id", v2_subject_key_id); + + m_subject.add("X509.Certificate.public_key", + hex_encode(public_key.value)); + + std::unique_ptr<Public_Key> pub_key(subject_public_key()); + m_self_signed = (dn_subject == dn_issuer) && check_signature(*pub_key); + + if(m_self_signed && version == 0) + { + m_subject.add("X509v3.BasicConstraints.is_ca", 1); + m_subject.add("X509v3.BasicConstraints.path_constraint", Cert_Extension::NO_CERT_PATH_LIMIT); + } + + if(is_CA_cert() && + !m_subject.has_value("X509v3.BasicConstraints.path_constraint")) + { + const size_t limit = (x509_version() < 3) ? + Cert_Extension::NO_CERT_PATH_LIMIT : 0; + + m_subject.add("X509v3.BasicConstraints.path_constraint", static_cast<u32bit>(limit)); + } + } + +/* +* Return the X.509 version in use +*/ +u32bit X509_Certificate::x509_version() const + { + return (m_subject.get1_u32bit("X509.Certificate.version") + 1); + } + +/* +* Return the time this cert becomes valid +*/ +std::string X509_Certificate::start_time() const + { + return m_subject.get1("X509.Certificate.start"); + } + +/* +* Return the time this cert becomes invalid +*/ +std::string X509_Certificate::end_time() const + { + return m_subject.get1("X509.Certificate.end"); + } + +/* +* Return information about the subject +*/ +std::vector<std::string> +X509_Certificate::subject_info(const std::string& what) const + { + return m_subject.get(X509_DN::deref_info_field(what)); + } + +/* +* Return information about the issuer +*/ +std::vector<std::string> +X509_Certificate::issuer_info(const std::string& what) const + { + return m_issuer.get(X509_DN::deref_info_field(what)); + } + +/* +* Return the public key in this certificate +*/ +Public_Key* X509_Certificate::subject_public_key() const + { + return X509::load_key( + ASN1::put_in_sequence(this->subject_public_key_bits())); + } + +std::vector<byte> X509_Certificate::subject_public_key_bits() const + { + return hex_decode(m_subject.get1("X509.Certificate.public_key")); + } + +/* +* Check if the certificate is for a CA +*/ +bool X509_Certificate::is_CA_cert() const + { + if(!m_subject.get1_u32bit("X509v3.BasicConstraints.is_ca")) + return false; + + return allowed_usage(Key_Constraints(KEY_CERT_SIGN)); + } + +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 + { + const std::vector<std::string> ex = ex_constraints(); + + 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(); + } + + 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 + { + const std::vector<std::string> ex = ex_constraints(); + + if(ex.empty()) + { + return false; + } + + if(std::find(ex.begin(), ex.end(), ex_constraint) != ex.end()) + { + return true; + } + + return false; + } + +/* +* Return the path length constraint +*/ +u32bit X509_Certificate::path_limit() const + { + return m_subject.get1_u32bit("X509v3.BasicConstraints.path_constraint", 0); + } + +/* +* Return if a certificate extension is marked critical +*/ +bool X509_Certificate::is_critical(const std::string& ex_name) const + { + return !!m_subject.get1_u32bit(ex_name + ".is_critical",0); + } + +/* +* Return the key usage constraints +*/ +Key_Constraints X509_Certificate::constraints() const + { + return Key_Constraints(m_subject.get1_u32bit("X509v3.KeyUsage", + NO_CONSTRAINTS)); + } + +/* +* Return the list of extended key usage OIDs +*/ +std::vector<std::string> X509_Certificate::ex_constraints() const + { + return lookup_oids(m_subject.get("X509v3.ExtendedKeyUsage")); + } + +/* +* Return the name constraints +*/ +NameConstraints X509_Certificate::name_constraints() const + { + std::vector<GeneralSubtree> permit, exclude; + + for(const std::string& v: m_subject.get("X509v3.NameConstraints.permitted")) + { + permit.push_back(GeneralSubtree(v)); + } + + for(const std::string& v: m_subject.get("X509v3.NameConstraints.excluded")) + { + exclude.push_back(GeneralSubtree(v)); + } + + return NameConstraints(std::move(permit),std::move(exclude)); + } + +/* +* Return the list of certificate policies +*/ +std::vector<std::string> X509_Certificate::policies() const + { + return lookup_oids(m_subject.get("X509v3.CertificatePolicies")); + } + +Extensions X509_Certificate::v3_extensions() const + { + return m_v3_extensions; + } + +std::string X509_Certificate::ocsp_responder() const + { + return m_subject.get1("OCSP.responder", ""); + } + +std::string X509_Certificate::crl_distribution_point() const + { + return m_subject.get1("CRL.DistributionPoint", ""); + } + +/* +* Return the authority key id +*/ +std::vector<byte> X509_Certificate::authority_key_id() const + { + return m_issuer.get1_memvec("X509v3.AuthorityKeyIdentifier"); + } + +/* +* Return the subject key id +*/ +std::vector<byte> X509_Certificate::subject_key_id() const + { + return m_subject.get1_memvec("X509v3.SubjectKeyIdentifier"); + } + +/* +* Return the certificate serial number +*/ +std::vector<byte> X509_Certificate::serial_number() const + { + return m_subject.get1_memvec("X509.Certificate.serial"); + } + +X509_DN X509_Certificate::issuer_dn() const + { + return create_dn(m_issuer); + } + +std::vector<byte> X509_Certificate::raw_issuer_dn() const + { + return m_issuer.get1_memvec("X509.Certificate.dn_bits"); + } + +X509_DN X509_Certificate::subject_dn() const + { + return create_dn(m_subject); + } + +std::vector<byte> X509_Certificate::raw_subject_dn() const + { + return m_subject.get1_memvec("X509.Certificate.dn_bits"); + } + +std::string X509_Certificate::fingerprint(const std::string& hash_name) const + { + std::unique_ptr<HashFunction> hash(HashFunction::create(hash_name)); + hash->update(this->BER_encode()); + const auto hex_print = hex_encode(hash->final()); + + std::string formatted_print; + + for(size_t i = 0; i != hex_print.size(); i += 2) + { + formatted_print.push_back(hex_print[i]); + formatted_print.push_back(hex_print[i+1]); + + if(i != hex_print.size() - 2) + formatted_print.push_back(':'); + } + + return formatted_print; + } + +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 (m_sig == other.m_sig && + m_sig_algo == other.m_sig_algo && + m_self_signed == other.m_self_signed && + m_issuer == other.m_issuer && + m_subject == other.m_subject); + } + +bool X509_Certificate::operator<(const X509_Certificate& other) const + { + /* If signature values are not equal, sort by lexicographic ordering of that */ + if(m_sig != other.m_sig) + { + if(m_sig < other.m_sig) + return true; + return false; + } + + // Then compare the signed contents + return m_tbs_bits < other.m_tbs_bits; + } + +/* +* X.509 Certificate Comparison +*/ +bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2) + { + return !(cert1 == cert2); + } + +std::string X509_Certificate::to_string() const + { + const std::vector<std::string> dn_fields{ + "Name", + "Email", + "Organization", + "Organizational Unit", + "Locality", + "State", + "Country", + "IP", + "DNS", + "URI", + "PKIX.XMPPAddr" + }; + + std::ostringstream out; + + for(auto&& field : dn_fields) + { + for(auto&& val : subject_info(field)) + { + out << "Subject " << field << ": " << val << "\n"; + } + } + + for(auto&& field : dn_fields) + { + for(auto&& val : issuer_info(field)) + { + out << "Issuer " << field << ": " << val << "\n"; + } + } + + out << "Version: " << this->x509_version() << "\n"; + + out << "Not valid before: " << this->start_time() << "\n"; + out << "Not valid after: " << this->end_time() << "\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"; + } + + std::vector<std::string> policies = this->policies(); + if(!policies.empty()) + { + out << "Policies: " << "\n"; + for(size_t i = 0; i != policies.size(); i++) + out << " " << policies[i] << "\n"; + } + + std::vector<std::string> ex_constraints = this->ex_constraints(); + if(!ex_constraints.empty()) + { + out << "Extended Constraints:\n"; + for(size_t i = 0; i != ex_constraints.size(); i++) + out << " " << ex_constraints[i] << "\n"; + } + + 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"; + if(!crl_distribution_point().empty()) + out << "CRL " << crl_distribution_point() << "\n"; + + out << "Signature algorithm: " << + OIDS::lookup(this->signature_algorithm().oid) << "\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"; + + std::unique_ptr<X509_PublicKey> pubkey(this->subject_public_key()); + out << "Public Key:\n" << X509::PEM_encode(*pubkey); + + return out.str(); + } + +/* +* 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/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h new file mode 100644 index 000000000..acdba7e02 --- /dev/null +++ b/src/lib/x509/x509cert.h @@ -0,0 +1,330 @@ +/* +* X.509 Certificates +* (C) 1999-2007,2015 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 <botan/x509_dn.h> +#include <botan/x509_key.h> +#include <botan/x509_ext.h> +#include <botan/asn1_alt_name.h> +#include <botan/datastor.h> +#include <botan/key_constraint.h> +#include <botan/name_constraint.h> +#include <map> +#include <memory> + +namespace Botan { + +enum class Usage_Type + { + UNSPECIFIED, // no restrictions + TLS_SERVER_AUTH, + TLS_CLIENT_AUTH, + CERTIFICATE_AUTHORITY, + OCSP_RESPONDER + }; + +/** +* This class represents X.509 Certificate +*/ +class BOTAN_DLL X509_Certificate : public X509_Object + { + public: + /** + * Get the public key associated with this certificate. + * @return subject public key of this certificate + */ + Public_Key* subject_public_key() const; + + /** + * Get the public key associated with this certificate. + * @return subject public key of this certificate + */ + std::vector<byte> subject_public_key_bits() const; + + /** + * Get the certificate's issuer distinguished name (DN). + * @return issuer DN of this certificate + */ + X509_DN issuer_dn() const; + + /** + * Get the certificate's subject distinguished name (DN). + * @return subject DN of this certificate + */ + 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 are + * "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" or "X509.Certificate.serial". + * @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 subject DN + */ + std::vector<byte> raw_issuer_dn() const; + + /** + * Raw issuer DN + */ + std::vector<byte> raw_subject_dn() const; + + /** + * Get the notBefore of the certificate. + * @return notBefore of the certificate + */ + std::string start_time() const; + + /** + * Get the notAfter of the certificate. + * @return notAfter of the certificate + */ + std::string end_time() const; + + /** + * Get the X509 version of this certificate object. + * @return X509 version + */ + u32bit x509_version() const; + + /** + * Get the serial number of this certificate. + * @return certificates serial number + */ + std::vector<byte> serial_number() const; + + /** + * Get the DER encoded AuthorityKeyIdentifier of this certificate. + * @return DER encoded AuthorityKeyIdentifier + */ + std::vector<byte> authority_key_id() const; + + /** + * Get the DER encoded SubjectKeyIdentifier of this certificate. + * @return DER encoded SubjectKeyIdentifier + */ + std::vector<byte> subject_key_id() const; + + /** + * Check whether this certificate is self signed. + * @return true if this certificate is self signed + */ + bool is_self_signed() const { return m_self_signed; } + + /** + * 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 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 has_ex_constraint(const std::string& ex_constraint) const; + + /** + * Get the path limit as defined in the BasicConstraints extension of + * this certificate. + * @return path limit + */ + u32bit 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> ex_constraints() const; + + /** + * Get the name constraints as defined in the NameConstraints + * extension of this certificate. + * @return name constraints + */ + NameConstraints name_constraints() const; + + /** + * Get the policies as defined in the CertificatePolicies extension + * of this certificate. + * @return certificate policies + */ + std::vector<std::string> policies() const; + + /** + * Get all extensions of this certificate. + * @return certificate extensions + */ + Extensions v3_extensions() const; + + /** + * Return the listed address of an OCSP responder, or empty if not set + */ + std::string ocsp_responder() const; + + /** + * Return the CRL distribution point, or empty if not set + */ + std::string crl_distribution_point() const; + + /** + * @return a 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 + * @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<byte>& in); + + X509_Certificate(const X509_Certificate& other) = default; + + X509_Certificate& operator=(const X509_Certificate& other) = default; + + private: + void force_decode() override; + friend class X509_CA; + friend class BER_Decoder; + + X509_Certificate() {} + + Data_Store m_subject, m_issuer; + bool m_self_signed; + Extensions m_v3_extensions; + }; + +/** +* 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_DLL bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2); + +/* +* 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_DLL X509_DN 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_DLL AlternativeName create_alt_name(const Data_Store& info); + +} + +#endif diff --git a/src/lib/x509/x509opt.cpp b/src/lib/x509/x509opt.cpp new file mode 100644 index 000000000..2dd2098fe --- /dev/null +++ b/src/lib/x509/x509opt.cpp @@ -0,0 +1,94 @@ +/* +* 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/oids.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(OIDS::lookup(oid_str)); + } + +/* +* Mark this certificate for CA usage +*/ +void X509_Cert_Options::CA_key(size_t limit) + { + is_CA = true; + path_limit = limit; + } + +/* +* Initialize the certificate options +*/ +X509_Cert_Options::X509_Cert_Options(const std::string& initial_opts, + u32bit expiration_time) + { + is_CA = false; + path_limit = 0; + constraints = NO_CONSTRAINTS; + + 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/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp new file mode 100644 index 000000000..f0b07e5fc --- /dev/null +++ b/src/lib/x509/x509path.cpp @@ -0,0 +1,454 @@ +/* +* X.509 Certificate Path Validation +* (C) 2010,2011,2012,2014 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509path.h> +#include <botan/ocsp.h> +#include <botan/http_util.h> +#include <botan/parsing.h> +#include <botan/pubkey.h> +#include <botan/oids.h> +#include <algorithm> +#include <chrono> +#include <vector> +#include <set> +#include <future> + +namespace Botan { + +namespace { + +std::shared_ptr<const X509_Certificate> +find_issuing_cert(const X509_Certificate& cert, + Certificate_Store& end_certs, + const std::vector<Certificate_Store*>& certstores) + { + const X509_DN issuer_dn = cert.issuer_dn(); + const std::vector<byte> auth_key_id = cert.authority_key_id(); + + if(std::shared_ptr<const X509_Certificate> c = end_certs.find_cert(issuer_dn, auth_key_id)) + { + if(*c != cert) + return c; + } + + for(size_t i = 0; i != certstores.size(); ++i) + { + if(std::shared_ptr<const X509_Certificate> c = certstores[i]->find_cert(issuer_dn, auth_key_id)) + return c; + } + + return nullptr; + } + +std::shared_ptr<const X509_CRL> find_crls_for(const X509_Certificate& cert, + const std::vector<Certificate_Store*>& certstores) + { + for(size_t i = 0; i != certstores.size(); ++i) + { + if(std::shared_ptr<const X509_CRL> crl = certstores[i]->find_crl_for(cert)) + return crl; + } + +#if 0 + const std::string crl_url = cert.crl_distribution_point(); + if(crl_url != "") + { + std::cout << "Downloading CRL " << crl_url << "\n"; + auto http = HTTP::GET_sync(crl_url); + + std::cout << http.status_message() << "\n"; + + http.throw_unless_ok(); + // check the mime type + + std::unique_ptr<X509_CRL> crl(new X509_CRL(http.body())); + + return crl.release(); + } +#endif + + return nullptr; + } + +std::vector<std::set<Certificate_Status_Code>> +check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores, + std::chrono::system_clock::time_point ref_time) + { + const std::set<std::string>& trusted_hashes = restrictions.trusted_hashes(); + + const bool self_signed_ee_cert = (cert_path.size() == 1); + + X509_Time validation_time(ref_time); + + std::vector<std::future<OCSP::Response>> ocsp_responses; + + std::vector<std::set<Certificate_Status_Code>> cert_status(cert_path.size()); + + 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); + + std::shared_ptr<const X509_Certificate> subject = cert_path[i]; + + std::shared_ptr<const X509_Certificate> issuer = cert_path[at_self_signed_root ? (i) : (i + 1)]; + + if(i == 0 || restrictions.ocsp_all_intermediates()) + { + // certstore[0] is treated as trusted for OCSP (FIXME) + if(certstores.size() > 1) + ocsp_responses.push_back( + std::async(std::launch::async, + OCSP::online_check, *issuer, *subject, certstores[0])); + } + + // Check all certs for valid time range + if(validation_time < X509_Time(subject->start_time(), ASN1_Tag::UTC_OR_GENERALIZED_TIME)) + status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID); + + if(validation_time > X509_Time(subject->end_time(), ASN1_Tag::UTC_OR_GENERALIZED_TIME)) + 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); + + if(issuer->path_limit() < i) + status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG); + + std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key()); + + if(!issuer_key) + { + status.insert(Certificate_Status_Code::SIGNATURE_ERROR); + } + else + { + if(subject->check_signature(*issuer_key) == false) + status.insert(Certificate_Status_Code::SIGNATURE_ERROR); + + if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) + status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK); + } + + // Allow untrusted hashes on self-signed roots + if(!trusted_hashes.empty() && !at_self_signed_root) + { + if(!trusted_hashes.count(subject->hash_used_for_signature())) + status.insert(Certificate_Status_Code::UNTRUSTED_HASH); + } + + // Check cert extensions + Extensions extensions = subject->v3_extensions(); + for(auto& extension : extensions.extensions()) + { + extension.first->validate(*subject, *issuer, cert_path, cert_status, i); + } + } + + 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()) + { + try + { + OCSP::Response ocsp = ocsp_responses[i].get(); + + auto ocsp_status = ocsp.status_for(*ca, *subject); + + status.insert(ocsp_status); + + //std::cout << "OCSP status: " << Path_Validation_Result::status_string(ocsp_status) << "\n"; + + // Either way we have a definitive answer, no need to check CRLs + if(ocsp_status == Certificate_Status_Code::CERT_IS_REVOKED) + return cert_status; + else if(ocsp_status == Certificate_Status_Code::OCSP_RESPONSE_GOOD) + continue; + } + catch(std::exception&) + { + //std::cout << "OCSP error: " << e.what() << "\n"; + } + } + + std::shared_ptr<const X509_CRL> crl_p = find_crls_for(*subject, certstores); + + if(!crl_p) + { + if(restrictions.require_revocation_information()) + status.insert(Certificate_Status_Code::NO_REVOCATION_DATA); + continue; + } + + const X509_CRL& crl = *crl_p; + + if(!ca->allowed_usage(CRL_SIGN)) + status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER); + + if(validation_time < X509_Time(crl.this_update())) + status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID); + + if(validation_time > X509_Time(crl.next_update())) + status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED); + + if(crl.check_signature(ca->subject_public_key()) == false) + status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE); + + if(crl.is_revoked(*subject)) + status.insert(Certificate_Status_Code::CERT_IS_REVOKED); + } + + if(self_signed_ee_cert) + cert_status.back().insert(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); + + return cert_status; + } + +} + +Path_Validation_Result x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point validation_time) + { + if(end_certs.empty()) + throw Invalid_Argument("x509_path_validate called with no subjects"); + + std::vector<std::shared_ptr<const X509_Certificate>> cert_path; + std::vector<std::shared_ptr<const X509_Certificate>> end_certs_sharedptr; + cert_path.push_back(std::make_shared<X509_Certificate>(end_certs[0])); + + for(auto c: end_certs) + end_certs_sharedptr.push_back(std::make_shared<const X509_Certificate>(c)); + + /* + * 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. + */ + std::set<std::string> certs_seen; + + Certificate_Store_Overlay extra(end_certs_sharedptr); + + // iterate until we reach a root or cannot find the issuer + while(!cert_path.back()->is_self_signed()) + { + std::shared_ptr<const X509_Certificate> cert = find_issuing_cert(*cert_path.back(), extra, certstores); + if(!cert) + return Path_Validation_Result(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND); + + const std::string fprint = cert->fingerprint("SHA-256"); + if(certs_seen.count(fprint) > 0) + return Path_Validation_Result(Certificate_Status_Code::CERT_CHAIN_LOOP); + certs_seen.insert(fprint); + cert_path.push_back(cert); + } + + std::vector<std::set<Certificate_Status_Code>> res = + check_chain(cert_path, restrictions, certstores, validation_time); + + if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname)) + res[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH); + + if(!cert_path[0]->allowed_usage(usage)) + res[0].insert(Certificate_Status_Code::INVALID_USAGE); + + return Path_Validation_Result(res, std::move(cert_path)); + } + +Path_Validation_Result x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores, + const std::string& hostname, + Usage_Type usage, + std::chrono::system_clock::time_point when) + { + std::vector<X509_Certificate> certs; + certs.push_back(end_cert); + return x509_path_validate(certs, restrictions, certstores, hostname, usage, when); + } + +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::vector<Certificate_Store*> certstores; + certstores.push_back(const_cast<Certificate_Store*>(&store)); + + return x509_path_validate(end_certs, restrictions, certstores, hostname, usage, when); + } + +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::vector<X509_Certificate> certs; + certs.push_back(end_cert); + + std::vector<Certificate_Store*> certstores; + certstores.push_back(const_cast<Certificate_Store*>(&store)); + + return x509_path_validate(certs, restrictions, certstores, hostname, usage, when); + } + +Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, + size_t key_strength, + bool ocsp_all) : + m_require_revocation_information(require_rev), + m_ocsp_all_intermediates(ocsp_all), + m_minimum_key_strength(key_strength) + { + 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"); + } + +Path_Validation_Result::Path_Validation_Result(std::vector<std::set<Certificate_Status_Code>> status, + std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) : + m_overall(Certificate_Status_Code::VERIFIED), + m_all_status(status), + m_cert_path(cert_chain) + { + // take the "worst" error as overall + for(const auto& s : m_all_status) + { + if(!s.empty()) + { + auto worst = *s.rbegin(); + // Leave OCSP confirmations on cert-level status only + if(worst != Certificate_Status_Code::OCSP_RESPONSE_GOOD) + m_overall = worst; + } + } + } + +const X509_Certificate& Path_Validation_Result::trust_root() const + { + if(m_cert_path.empty()) + throw Exception("Path_Validation_Result::trust_root no path set"); + if(result() != Certificate_Status_Code::VERIFIED) + throw Exception("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 + { + if(result() == Certificate_Status_Code::VERIFIED || + result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD) + return true; + return false; + } + +std::string Path_Validation_Result::result_string() const + { + return status_string(result()); + } + +const char* Path_Validation_Result::status_string(Certificate_Status_Code code) + { + switch(code) + { + case Certificate_Status_Code::VERIFIED: + return "Verified"; + case Certificate_Status_Code::OCSP_RESPONSE_GOOD: + return "OCSP response good"; + 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 "Untrusted hash"; + + 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 has expired"; + case Certificate_Status_Code::CRL_NOT_YET_VALID: + return "CRL 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::POLICY_ERROR: + return "Policy error"; + case Certificate_Status_Code::INVALID_USAGE: + return "Invalid 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::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::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"; + // intentionally no default so we are warned + } + + return "Unknown error"; + } + +} diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h new file mode 100644 index 000000000..f65652e59 --- /dev/null +++ b/src/lib/x509/x509path.h @@ -0,0 +1,239 @@ +/* +* 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/cert_status.h> +#include <botan/x509cert.h> +#include <botan/certstor.h> +#include <set> +#include <chrono> + +namespace Botan { + +/** +* Specifies restrictions on the PKIX path validation +*/ +class BOTAN_DLL Path_Validation_Restrictions + { + 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. + * @param ocsp_all_intermediates + */ + Path_Validation_Restrictions(bool require_rev = false, + size_t minimum_key_strength = 80, + bool ocsp_all_intermediates = false); + + /** + * @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 + * @param trusted_hashes a set of trusted hashes. Any signatures + * created using a hash other than one of these will be + * rejected. + */ + Path_Validation_Restrictions(bool require_rev, + size_t minimum_key_strength, + bool ocsp_all_intermediates, + const std::set<std::string>& trusted_hashes) : + 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) {} + + /** + * @return whether revocation information is required + */ + bool require_revocation_information() const + { return m_require_revocation_information; } + + /** + * FIXME add doc + */ + 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; } + + private: + bool m_require_revocation_information; + bool m_ocsp_all_intermediates; + std::set<std::string> m_trusted_hashes; + size_t m_minimum_key_strength; + }; + +/** +* Represents the result of a PKIX path validation +*/ +class BOTAN_DLL Path_Validation_Result + { + 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 + */ + 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 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 std::vector<std::set<Certificate_Status_Code>>& all_statuses() const + { return m_all_status; } + + /** + * @return string representation of the validation result + */ + std::string result_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(std::vector<std::set<Certificate_Status_Code>> 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: + friend Path_Validation_Result BOTAN_DLL x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores); + + Certificate_Status_Code m_overall; + std::vector<std::set<Certificate_Status_Code>> m_all_status; + std::vector<std::shared_ptr<const X509_Certificate>> m_cert_path; + }; + + +/** +* PKIX Path Validation +* @param end_certs certificate chain to validate +* @param restrictions path validation restrictions +* @param certstores 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 +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_DLL x509_path_validate( + const std::vector<X509_Certificate>& end_certs, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + +/** +* PKIX Path Validation +* @param end_cert certificate to validate +* @param restrictions path validation restrictions +* @param certstores 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 +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_DLL x509_path_validate( + const X509_Certificate& end_cert, + const Path_Validation_Restrictions& restrictions, + const std::vector<Certificate_Store*>& certstores, + const std::string& hostname = "", + Usage_Type usage = Usage_Type::UNSPECIFIED, + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + +/** +* 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 +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_DLL 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()); + +/** +* 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 +* @return result of the path validation +*/ +Path_Validation_Result BOTAN_DLL 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()); + +} + +#endif diff --git a/src/lib/x509/x509self.cpp b/src/lib/x509/x509self.cpp new file mode 100644 index 000000000..a59632858 --- /dev/null +++ b/src/lib/x509/x509self.cpp @@ -0,0 +1,176 @@ +/* +* PKCS #10/Self Signed Cert Creation +* (C) 1999-2008 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/x509self.h> +#include <botan/x509_ext.h> +#include <botan/x509_ca.h> +#include <botan/der_enc.h> +#include <botan/oids.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); + subject_dn.add_attribute("X520.SerialNumber", opts.serial_number); + subject_alt = AlternativeName(opts.email, opts.uri, opts.dns, opts.ip); + subject_alt.add_othername(OIDS::lookup("PKIX.XMPPAddr"), + opts.xmpp, UTF8_STRING); + } + +} + +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; + + std::vector<byte> pub_key = X509::BER_encode(key); + std::unique_ptr<PK_Signer> signer(choose_sig_format(key, rng, hash_fn, sig_algo)); + 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; + + extensions.add( + new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit), + true); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add(new Cert_Extension::Key_Usage(constraints), true); + } + + extensions.add(new Cert_Extension::Subject_Key_ID(pub_key)); + + extensions.add( + new Cert_Extension::Subject_Alternative_Name(subject_alt)); + + extensions.add( + 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) + { + AlgorithmIdentifier sig_algo; + X509_DN subject_dn; + AlternativeName subject_alt; + + std::vector<byte> pub_key = X509::BER_encode(key); + std::unique_ptr<PK_Signer> signer(choose_sig_format(key, rng, hash_fn, sig_algo)); + load_info(opts, subject_dn, subject_alt); + + const size_t PKCS10_VERSION = 0; + + Key_Constraints constraints; + if(opts.is_CA) + { + constraints = Key_Constraints(KEY_CERT_SIGN | CRL_SIGN); + } + else + { + verify_cert_constraints_valid_for_key_type(key, opts.constraints); + constraints = opts.constraints; + } + + Extensions extensions; + + extensions.add( + new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); + + if(constraints != NO_CONSTRAINTS) + { + extensions.add( + new Cert_Extension::Key_Usage(constraints)); + } + extensions.add( + new Cert_Extension::Extended_Key_Usage(opts.ex_constraints)); + extensions.add( + new Cert_Extension::Subject_Alternative_Name(subject_alt)); + + DER_Encoder tbs_req; + + tbs_req.start_cons(SEQUENCE) + .encode(PKCS10_VERSION) + .encode(subject_dn) + .raw_bytes(pub_key) + .start_explicit(0); + + if(!opts.challenge.empty()) + { + ASN1_String challenge(opts.challenge, DIRECTORY_STRING); + + tbs_req.encode( + Attribute("PKCS9.ChallengePassword", + DER_Encoder().encode(challenge).get_contents_unlocked() + ) + ); + } + + tbs_req.encode( + Attribute("PKCS9.ExtensionRequest", + DER_Encoder() + .start_cons(SEQUENCE) + .encode(extensions) + .end_cons() + .get_contents_unlocked() + ) + ) + .end_explicit() + .end_cons(); + + const std::vector<byte> req = + X509_Object::make_signed(signer.get(), rng, sig_algo, + tbs_req.get_contents()); + + return PKCS10_Request(req); + } + +} + +} diff --git a/src/lib/x509/x509self.h b/src/lib/x509/x509self.h new file mode 100644 index 000000000..401b2eb2f --- /dev/null +++ b/src/lib/x509/x509self.h @@ -0,0 +1,197 @@ +/* +* 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/asn1_time.h> + +namespace Botan { + +/** +* Options for X.509 certificates. +*/ +class BOTAN_DLL X509_Cert_Options + { + 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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); + + /** + * 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 = "", + u32bit 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_DLL 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_DLL PKCS10_Request create_cert_req(const X509_Cert_Options& opts, + const Private_Key& key, + const std::string& hash_fn, + RandomNumberGenerator& rng); + +} + +} + +#endif |