diff options
author | Jack Lloyd <[email protected]> | 2016-11-03 10:30:13 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-11-03 10:30:13 -0400 |
commit | 341fd32b46363cad4c2caee3fca166695100ba07 (patch) | |
tree | 89a98aa28a431f2625268cf61e7adf903fd24a98 /src/lib/x509 | |
parent | 1e72720661383466807ac496b941af41d756a2ce (diff) |
Move cert/x509 to top level and pem and pbes2 to pubkey.
The `cert` dir was just an artifact of having previously supported
CVC (smartcard cert format), removed a long time ago.
The pem and pbes2 code is directly related to the pubkey code,
in fact the only caller of pbes2 (likely anywhere, not just
in the library) is in pkcs8.cpp
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 |