diff options
Diffstat (limited to 'modules/x509')
-rw-r--r-- | modules/x509/certstor.cpp | 46 | ||||
-rw-r--r-- | modules/x509/certstor.h | 37 | ||||
-rw-r--r-- | modules/x509/crl_ent.cpp | 111 | ||||
-rw-r--r-- | modules/x509/crl_ent.h | 44 | ||||
-rw-r--r-- | modules/x509/modinfo.txt | 31 | ||||
-rw-r--r-- | modules/x509/pkcs10.cpp | 198 | ||||
-rw-r--r-- | modules/x509/pkcs10.h | 46 | ||||
-rw-r--r-- | modules/x509/x509_ca.cpp | 288 | ||||
-rw-r--r-- | modules/x509/x509_ca.h | 68 | ||||
-rw-r--r-- | modules/x509/x509_crl.cpp | 149 | ||||
-rw-r--r-- | modules/x509/x509_crl.h | 46 | ||||
-rw-r--r-- | modules/x509/x509_ext.cpp | 579 | ||||
-rw-r--r-- | modules/x509/x509_ext.h | 315 | ||||
-rw-r--r-- | modules/x509/x509_obj.cpp | 233 | ||||
-rw-r--r-- | modules/x509/x509_obj.h | 56 | ||||
-rw-r--r-- | modules/x509/x509cert.cpp | 364 | ||||
-rw-r--r-- | modules/x509/x509cert.h | 72 | ||||
-rw-r--r-- | modules/x509/x509find.cpp | 109 | ||||
-rw-r--r-- | modules/x509/x509find.h | 58 | ||||
-rw-r--r-- | modules/x509/x509opt.cpp | 106 | ||||
-rw-r--r-- | modules/x509/x509self.cpp | 174 | ||||
-rw-r--r-- | modules/x509/x509self.h | 75 | ||||
-rw-r--r-- | modules/x509/x509stor.cpp | 693 | ||||
-rw-r--r-- | modules/x509/x509stor.h | 137 |
24 files changed, 4035 insertions, 0 deletions
diff --git a/modules/x509/certstor.cpp b/modules/x509/certstor.cpp new file mode 100644 index 000000000..2b51590ad --- /dev/null +++ b/modules/x509/certstor.cpp @@ -0,0 +1,46 @@ +/************************************************* +* Certificate Store Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/certstor.h> + +namespace Botan { + +/************************************************* +* Search by name * +*************************************************/ +std::vector<X509_Certificate> +Certificate_Store::by_name(const std::string&) const + { + return std::vector<X509_Certificate>(); + } + +/************************************************* +* Search by email * +*************************************************/ +std::vector<X509_Certificate> +Certificate_Store::by_email(const std::string&) const + { + return std::vector<X509_Certificate>(); + } + +/************************************************* +* Search by X.500 distinguished name * +*************************************************/ +std::vector<X509_Certificate> +Certificate_Store::by_dn(const X509_DN&) const + { + return std::vector<X509_Certificate>(); + } + +/************************************************* +* Find any CRLs that might be useful * +*************************************************/ +std::vector<X509_CRL> +Certificate_Store::get_crls_for(const X509_Certificate&) const + { + return std::vector<X509_CRL>(); + } + +} diff --git a/modules/x509/certstor.h b/modules/x509/certstor.h new file mode 100644 index 000000000..ca0480496 --- /dev/null +++ b/modules/x509/certstor.h @@ -0,0 +1,37 @@ +/************************************************* +* Certificate Store Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#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 std::vector<X509_Certificate> + by_SKID(const MemoryRegion<byte>&) const = 0; + + virtual std::vector<X509_Certificate> by_name(const std::string&) const; + virtual std::vector<X509_Certificate> by_email(const std::string&) const; + virtual std::vector<X509_Certificate> by_dn(const X509_DN&) const; + + virtual std::vector<X509_CRL> + get_crls_for(const X509_Certificate&) const; + + virtual Certificate_Store* clone() const = 0; + + virtual ~Certificate_Store() {} + }; + +} + +#endif diff --git a/modules/x509/crl_ent.cpp b/modules/x509/crl_ent.cpp new file mode 100644 index 000000000..4a85b99c2 --- /dev/null +++ b/modules/x509/crl_ent.cpp @@ -0,0 +1,111 @@ +/************************************************* +* CRL Entry Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/crl_ent.h> +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/bigint.h> +#include <botan/libstate.h> +#include <botan/oids.h> +#include <botan/util.h> + +namespace Botan { + +/************************************************* +* Create a CRL_Entry * +*************************************************/ +CRL_Entry::CRL_Entry() + { + reason = UNSPECIFIED; + } + +/************************************************* +* Create a CRL_Entry * +*************************************************/ +CRL_Entry::CRL_Entry(const X509_Certificate& cert, CRL_Code why) + { + serial = cert.serial_number(); + time = X509_Time(system_time()); + 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); + } + +/************************************************* +* Compare two CRL_Entrys * +*************************************************/ +bool operator<(const CRL_Entry& a1, const CRL_Entry& a2) + { + return (a1.expire_time().cmp(a2.expire_time()) < 0); + } + +/************************************************* +* DER encode a CRL_Entry * +*************************************************/ +void CRL_Entry::encode_into(DER_Encoder& der) const + { + Extensions extensions; + + extensions.add(new Cert_Extension::CRL_ReasonCode(reason)); + + der.start_cons(SEQUENCE) + .encode(BigInt::decode(serial, serial.size())) + .encode(time) + .encode(extensions) + .end_cons(); + } + +/************************************************* +* Decode a BER encoded CRL_Entry * +*************************************************/ +void CRL_Entry::decode_from(BER_Decoder& source) + { + BigInt serial_number_bn; + + source.start_cons(SEQUENCE) + .decode(serial_number_bn) + .decode(time); + + if(source.more_items()) + { + std::string action = + global_state().option("x509/crl/unknown_critical"); + + if(action != "throw" && action != "ignore") + throw Invalid_Argument("Bad setting x509/crl/unknown_critical: " + + action); + + Extensions extensions(action == "throw"); + source.decode(extensions); + Data_Store info; + extensions.contents_to(info, info); + reason = CRL_Code(info.get1_u32bit("X509v3.CRLReasonCode")); + } + + serial = BigInt::encode(serial_number_bn); + } + +} diff --git a/modules/x509/crl_ent.h b/modules/x509/crl_ent.h new file mode 100644 index 000000000..05a9338b3 --- /dev/null +++ b/modules/x509/crl_ent.h @@ -0,0 +1,44 @@ +/************************************************* +* CRL Entry Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_CRL_ENTRY_H__ +#define BOTAN_CRL_ENTRY_H__ + +#include <botan/x509cert.h> + +namespace Botan { + +/************************************************* +* CRL Entry * +*************************************************/ +class BOTAN_DLL CRL_Entry : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const; + void decode_from(class BER_Decoder&); + + MemoryVector<byte> serial_number() const { return serial; } + X509_Time expire_time() const { return time; } + CRL_Code reason_code() const { return reason; } + + CRL_Entry(); + CRL_Entry(const X509_Certificate&, CRL_Code = UNSPECIFIED); + + private: + MemoryVector<byte> serial; + X509_Time time; + CRL_Code reason; + }; + +/************************************************* +* Comparison Operations * +*************************************************/ +BOTAN_DLL bool operator==(const CRL_Entry&, const CRL_Entry&); +BOTAN_DLL bool operator!=(const CRL_Entry&, const CRL_Entry&); +BOTAN_DLL bool operator<(const CRL_Entry&, const CRL_Entry&); + +} + +#endif diff --git a/modules/x509/modinfo.txt b/modules/x509/modinfo.txt new file mode 100644 index 000000000..162d3931b --- /dev/null +++ b/modules/x509/modinfo.txt @@ -0,0 +1,31 @@ +realname "X.509" + +define X509 + +load_on auto + +<add> +certstor.h +certstor.cpp +crl_ent.cpp +crl_ent.h +pkcs10.h +pkcs10.cpp +x509_ca.cpp +x509_ca.h +x509_crl.cpp +x509_crl.h +x509_ext.cpp +x509_ext.h +x509_obj.cpp +x509_obj.h +x509cert.cpp +x509cert.h +x509find.cpp +x509find.h +x509opt.cpp +x509self.cpp +x509self.h +x509stor.cpp +x509stor.h +</add> diff --git a/modules/x509/pkcs10.cpp b/modules/x509/pkcs10.cpp new file mode 100644 index 000000000..bd21cca7d --- /dev/null +++ b/modules/x509/pkcs10.cpp @@ -0,0 +1,198 @@ +/************************************************* +* PKCS #10 Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/pkcs10.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/x509stor.h> +#include <botan/x509_ext.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(); + } + +/************************************************* +* PKCS10_Request Constructor * +*************************************************/ +PKCS10_Request::PKCS10_Request(const std::string& in) : + X509_Object(in, "CERTIFICATE REQUEST/NEW CERTIFICATE REQUEST") + { + do_decode(); + } + +/************************************************* +* Deocde the CertificateRequestInfo * +*************************************************/ +void PKCS10_Request::force_decode() + { + BER_Decoder cert_req_info(tbs_bits); + + u32bit version; + cert_req_info.decode(version); + if(version != 0) + throw Decoding_Error("Unknown version code in PKCS #10 request: " + + to_string(version)); + + X509_DN dn_subject; + cert_req_info.decode(dn_subject); + + 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); + + info.add("X509.Certificate.public_key", + PEM_Code::encode( + ASN1::put_in_sequence(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(); + + X509_Code sig_check = X509_Store::check_sig(*this, subject_public_key()); + if(sig_check != VERIFIED) + 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); + info.add("RFC822", email.value()); + } + else if(attr.oid == OIDS::lookup("PKCS9.ChallengePassword")) + { + ASN1_String challenge_password; + value.decode(challenge_password); + 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(info, issuer_info); + } + } + +/************************************************* +* Return the challenge password (if any) * +*************************************************/ +std::string PKCS10_Request::challenge_password() const + { + return info.get1("PKCS9.ChallengePassword"); + } + +/************************************************* +* Return the name of the requestor * +*************************************************/ +X509_DN PKCS10_Request::subject_dn() const + { + return create_dn(info); + } + +/************************************************* +* Return the public key of the requestor * +*************************************************/ +MemoryVector<byte> PKCS10_Request::raw_public_key() const + { + DataSource_Memory source(info.get1("X509.Certificate.public_key")); + return 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(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(info); + } + +/************************************************* +* Return the key constraints (if any) * +*************************************************/ +Key_Constraints PKCS10_Request::constraints() const + { + return Key_Constraints(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 = info.get("X509v3.ExtendedKeyUsage"); + + std::vector<OID> result; + for(u32bit j = 0; j != oids.size(); ++j) + result.push_back(OID(oids[j])); + return result; + } + +/************************************************* +* Return is a CA certificate is requested * +*************************************************/ +bool PKCS10_Request::is_CA() const + { + return info.get1_u32bit("X509v3.BasicConstraints.is_ca"); + } + +/************************************************* +* Return the desired path limit (if any) * +*************************************************/ +u32bit PKCS10_Request::path_limit() const + { + return info.get1_u32bit("X509v3.BasicConstraints.path_constraint", 0); + } + +} diff --git a/modules/x509/pkcs10.h b/modules/x509/pkcs10.h new file mode 100644 index 000000000..f688688ee --- /dev/null +++ b/modules/x509/pkcs10.h @@ -0,0 +1,46 @@ +/************************************************* +* PKCS #10 Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_PKCS10_H__ +#define BOTAN_PKCS10_H__ + +#include <botan/x509_obj.h> +#include <botan/pkcs8.h> +#include <botan/datastor.h> +#include <vector> + +namespace Botan { + +/************************************************* +* PKCS #10 Certificate Request * +*************************************************/ +class BOTAN_DLL PKCS10_Request : public X509_Object + { + public: + Public_Key* subject_public_key() const; + + MemoryVector<byte> raw_public_key() const; + X509_DN subject_dn() const; + AlternativeName subject_alt_name() const; + Key_Constraints constraints() const; + std::vector<OID> ex_constraints() const; + + bool is_CA() const; + u32bit path_limit() const; + + std::string challenge_password() const; + + PKCS10_Request(DataSource&); + PKCS10_Request(const std::string&); + private: + void force_decode(); + void handle_attribute(const Attribute&); + + Data_Store info; + }; + +} + +#endif diff --git a/modules/x509/x509_ca.cpp b/modules/x509/x509_ca.cpp new file mode 100644 index 000000000..d455e4988 --- /dev/null +++ b/modules/x509/x509_ca.cpp @@ -0,0 +1,288 @@ +/************************************************* +* X.509 Certificate Authority Source File * +* (C) 1999-2008 Jack Lloyd * +*************************************************/ + +#include <botan/x509_ca.h> +#include <botan/x509stor.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/libstate.h> +#include <botan/lookup.h> +#include <botan/look_pk.h> +#include <botan/numthry.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <botan/util.h> +#include <algorithm> +#include <typeinfo> +#include <iterator> +#include <memory> +#include <set> + +namespace Botan { + +/************************************************* +* Load the certificate and private key * +*************************************************/ +X509_CA::X509_CA(const X509_Certificate& c, + const Private_Key& key) : cert(c) + { + const Private_Key* key_pointer = &key; + if(!dynamic_cast<const PK_Signing_Key*>(key_pointer)) + throw Invalid_Argument("X509_CA: " + key.algo_name() + " cannot sign"); + + if(!cert.is_CA_cert()) + throw Invalid_Argument("X509_CA: This certificate is not for a CA"); + + signer = choose_sig_format(key, ca_sig_algo); + } + +/************************************************* +* 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::auto_ptr<Public_Key> key(req.subject_public_key()); + constraints = X509::find_constraints(*key, req.constraints()); + } + + Extensions extensions; + + extensions.add(new Cert_Extension::Authority_Key_ID(cert.subject_key_id())); + extensions.add(new Cert_Extension::Subject_Key_ID(req.raw_public_key())); + + extensions.add( + new Cert_Extension::Basic_Constraints(req.is_CA(), req.path_limit())); + + extensions.add(new Cert_Extension::Key_Usage(constraints)); + extensions.add( + new Cert_Extension::Extended_Key_Usage(req.ex_constraints())); + + extensions.add( + new Cert_Extension::Subject_Alternative_Name(req.subject_alt_name())); + + return make_cert(signer, rng, ca_sig_algo, req.raw_public_key(), + not_before, not_after, + 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 MemoryRegion<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 u32bit X509_CERT_VERSION = 3; + const u32bit SERIAL_BITS = 128; + + BigInt serial_no(rng, SERIAL_BITS); + + DataSource_Memory source(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() + )); + + return X509_Certificate(source); + } + +/************************************************* +* 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> already_revoked = crl.get_revoked(); + std::vector<CRL_Entry> all_revoked; + + X509_Store store; + store.add_cert(cert, true); + if(store.add_crl(crl) != VERIFIED) + throw Invalid_Argument("X509_CA::update_crl: Invalid CRL provided"); + + std::set<SecureVector<byte> > removed_from_crl; + for(u32bit j = 0; j != new_revoked.size(); ++j) + { + if(new_revoked[j].reason_code() == DELETE_CRL_ENTRY) + removed_from_crl.insert(new_revoked[j].serial_number()); + else + all_revoked.push_back(new_revoked[j]); + } + + for(u32bit j = 0; j != already_revoked.size(); ++j) + { + std::set<SecureVector<byte> >::const_iterator i; + i = removed_from_crl.find(already_revoked[j].serial_number()); + + if(i == removed_from_crl.end()) + all_revoked.push_back(already_revoked[j]); + } + std::sort(all_revoked.begin(), all_revoked.end()); + + std::vector<CRL_Entry> cert_list; + std::unique_copy(all_revoked.begin(), all_revoked.end(), + std::back_inserter(cert_list)); + + return make_crl(cert_list, 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 u32bit X509_CRL_VERSION = 2; + + if(next_update == 0) + next_update = timespec_to_u32bit( + global_state().option("x509/crl/next_update")); + + // Totally stupid: ties encoding logic to the return of std::time!! + const u64bit current_time = system_time(); + + Extensions extensions; + extensions.add( + new Cert_Extension::Authority_Key_ID(cert.subject_key_id())); + extensions.add(new Cert_Extension::CRL_Number(crl_number)); + + DataSource_Memory source(X509_Object::make_signed(signer, rng, ca_sig_algo, + DER_Encoder().start_cons(SEQUENCE) + .encode(X509_CRL_VERSION-1) + .encode(ca_sig_algo) + .encode(cert.issuer_dn()) + .encode(X509_Time(current_time)) + .encode(X509_Time(current_time + next_update)) + .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() + )); + + return X509_CRL(source); + } + +/************************************************* +* Return the CA's certificate * +*************************************************/ +X509_Certificate X509_CA::ca_certificate() const + { + return cert; + } + +/************************************************* +* X509_CA Destructor * +*************************************************/ +X509_CA::~X509_CA() + { + delete signer; + } + +/************************************************* +* Choose a signing format for the key * +*************************************************/ +PK_Signer* choose_sig_format(const Private_Key& key, + AlgorithmIdentifier& sig_algo) + { + std::string padding; + Signature_Format format; + + const std::string algo_name = key.algo_name(); + + if(algo_name == "RSA") + { + std::string hash = global_state().option("x509/ca/rsa_hash"); + + if(hash == "") + throw Invalid_State("No value set for x509/ca/rsa_hash"); + + hash = global_state().deref_alias(hash); + + padding = "EMSA3(" + hash + ")"; + format = IEEE_1363; + } + else if(algo_name == "DSA") + { + std::string hash = global_state().deref_alias("SHA-1"); + padding = "EMSA1(" + hash + ")"; + format = DER_SEQUENCE; + } + else + throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); + + sig_algo.oid = OIDS::lookup(algo_name + "/" + padding); + + std::auto_ptr<X509_Encoder> encoding(key.x509_encoder()); + if(!encoding.get()) + throw Encoding_Error("Key " + algo_name + " does not support " + "X.509 encoding"); + + sig_algo.parameters = encoding->alg_id().parameters; + + const PK_Signing_Key& sig_key = dynamic_cast<const PK_Signing_Key&>(key); + + return get_pk_signer(sig_key, padding, format); + } + +} diff --git a/modules/x509/x509_ca.h b/modules/x509/x509_ca.h new file mode 100644 index 000000000..969e62558 --- /dev/null +++ b/modules/x509/x509_ca.h @@ -0,0 +1,68 @@ +/************************************************* +* X.509 Certificate Authority Header File * +* (C) 1999-2008 Jack Lloyd * +*************************************************/ + +#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/pkcs8.h> +#include <botan/pkcs10.h> +#include <botan/pubkey.h> + +namespace Botan { + +/************************************************* +* X.509 Certificate Authority * +*************************************************/ +class BOTAN_DLL X509_CA + { + public: + X509_Certificate sign_request(const PKCS10_Request& req, + RandomNumberGenerator& rng, + const X509_Time& not_before, + const X509_Time& not_after); + + X509_Certificate ca_certificate() const; + + X509_CRL new_crl(RandomNumberGenerator& rng, u32bit = 0) const; + X509_CRL update_crl(const X509_CRL&, + const std::vector<CRL_Entry>&, + RandomNumberGenerator& rng, + u32bit = 0) const; + + static X509_Certificate make_cert(PK_Signer*, + RandomNumberGenerator&, + const AlgorithmIdentifier&, + const MemoryRegion<byte>&, + const X509_Time&, const X509_Time&, + const X509_DN&, const X509_DN&, + const Extensions&); + + X509_CA(const X509_Certificate&, const Private_Key&); + ~X509_CA(); + private: + X509_CA(const X509_CA&) {} + X509_CA& operator=(const X509_CA&) { return (*this); } + + X509_CRL make_crl(const std::vector<CRL_Entry>&, + u32bit, u32bit, RandomNumberGenerator&) const; + + AlgorithmIdentifier ca_sig_algo; + X509_Certificate cert; + PK_Signer* signer; + }; + +/************************************************* +* Choose a signing format for the key * +*************************************************/ +BOTAN_DLL PK_Signer* choose_sig_format(const Private_Key&, + AlgorithmIdentifier&); + + +} + +#endif diff --git a/modules/x509/x509_crl.cpp b/modules/x509/x509_crl.cpp new file mode 100644 index 000000000..306c78bd1 --- /dev/null +++ b/modules/x509/x509_crl.cpp @@ -0,0 +1,149 @@ +/************************************************* +* X.509 CRL Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509_crl.h> +#include <botan/x509_ext.h> +#include <botan/ber_dec.h> +#include <botan/parsing.h> +#include <botan/bigint.h> +#include <botan/libstate.h> +#include <botan/oids.h> + +namespace Botan { + +/************************************************* +* Load a X.509 CRL * +*************************************************/ +X509_CRL::X509_CRL(DataSource& in) : X509_Object(in, "X509 CRL/CRL") + { + do_decode(); + } + +/************************************************* +* Load a X.509 CRL * +*************************************************/ +X509_CRL::X509_CRL(const std::string& in) : X509_Object(in, "CRL/X509 CRL") + { + do_decode(); + } + +/************************************************* +* Decode the TBSCertList data * +*************************************************/ +void X509_CRL::force_decode() + { + BER_Decoder tbs_crl(tbs_bits); + + u32bit version; + tbs_crl.decode_optional(version, INTEGER, UNIVERSAL); + + if(version != 0 && version != 1) + throw X509_CRL_Error("Unknown X.509 CRL version " + + to_string(version+1)); + + AlgorithmIdentifier sig_algo_inner; + tbs_crl.decode(sig_algo_inner); + + if(sig_algo != sig_algo_inner) + throw X509_CRL_Error("Algorithm identifier mismatch"); + + X509_DN dn_issuer; + tbs_crl.decode(dn_issuer); + info.add(dn_issuer.contents()); + + X509_Time start, end; + tbs_crl.decode(start).decode(end); + info.add("X509.CRL.start", start.readable_string()); + info.add("X509.CRL.end", end.readable_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; + cert_list.decode(entry); + 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); + + std::string action = global_state().option("x509/crl/unknown_critical"); + if(action != "throw" && action != "ignore") + throw Invalid_Argument("Bad value of x509/crl/unknown_critical: " + + action); + + Extensions extensions(action == "throw"); + + crl_options.decode(extensions).verify_end(); + + extensions.contents_to(info, 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 revoked; + } + +/************************************************* +* Return the distinguished name of the issuer * +*************************************************/ +X509_DN X509_CRL::issuer_dn() const + { + return create_dn(info); + } + +/************************************************* +* Return the key identifier of the issuer * +*************************************************/ +MemoryVector<byte> X509_CRL::authority_key_id() const + { + return info.get1_memvec("X509v3.AuthorityKeyIdentifier"); + } + +/************************************************* +* Return the CRL number of this CRL * +*************************************************/ +u32bit X509_CRL::crl_number() const + { + return info.get1_u32bit("X509v3.CRLNumber"); + } + +/************************************************* +* Return the issue data of the CRL * +*************************************************/ +X509_Time X509_CRL::this_update() const + { + return info.get1("X509.CRL.start"); + } + +/************************************************* +* Return the date when a new CRL will be issued * +*************************************************/ +X509_Time X509_CRL::next_update() const + { + return info.get1("X509.CRL.end"); + } + +} diff --git a/modules/x509/x509_crl.h b/modules/x509/x509_crl.h new file mode 100644 index 000000000..f7623b940 --- /dev/null +++ b/modules/x509/x509_crl.h @@ -0,0 +1,46 @@ +/************************************************* +* X.509 CRL Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_CRL_H__ +#define BOTAN_X509_CRL_H__ + +#include <botan/x509_obj.h> +#include <botan/crl_ent.h> +#include <vector> + +namespace Botan { + +/************************************************* +* X.509 CRL * +*************************************************/ +class BOTAN_DLL X509_CRL : public X509_Object + { + public: + struct X509_CRL_Error : public Exception + { + X509_CRL_Error(const std::string& error) : + Exception("X509_CRL: " + error) {} + }; + + std::vector<CRL_Entry> get_revoked() const; + + X509_DN issuer_dn() const; + MemoryVector<byte> authority_key_id() const; + + u32bit crl_number() const; + X509_Time this_update() const; + X509_Time next_update() const; + + X509_CRL(DataSource&); + X509_CRL(const std::string&); + private: + void force_decode(); + std::vector<CRL_Entry> revoked; + Data_Store info; + }; + +} + +#endif diff --git a/modules/x509/x509_ext.cpp b/modules/x509/x509_ext.cpp new file mode 100644 index 000000000..3595a0492 --- /dev/null +++ b/modules/x509/x509_ext.cpp @@ -0,0 +1,579 @@ +/************************************************* +* X.509 Certificate Extensions Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/lookup.h> +#include <botan/oids.h> +#include <botan/libstate.h> +#include <botan/bit_ops.h> +#include <algorithm> +#include <memory> + +namespace Botan { + +/************************************************* +* List of X.509 Certificate Extensions * +*************************************************/ +Certificate_Extension* Extensions::get_extension(const OID& oid) + { +#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.CRLNumber", CRL_Number); + X509_EXTENSION("X509v3.CertificatePolicies", Certificate_Policies); + + return 0; + } + +/************************************************* +* Extensions Copy Constructor * +*************************************************/ +Extensions::Extensions(const Extensions& extensions) : ASN1_Object() + { + *this = extensions; + } + +/************************************************* +* Extensions Assignment Operator * +*************************************************/ +Extensions& Extensions::operator=(const Extensions& other) + { + for(u32bit j = 0; j != extensions.size(); ++j) + delete extensions[j]; + extensions.clear(); + + for(u32bit j = 0; j != other.extensions.size(); ++j) + extensions.push_back(other.extensions[j]->copy()); + + return (*this); + } + +/************************************************* +* Return the OID of this extension * +*************************************************/ +OID Certificate_Extension::oid_of() const + { + return OIDS::lookup(oid_name()); + } + +/************************************************* +* Encode an Extensions list * +*************************************************/ +void Extensions::encode_into(DER_Encoder& to_object) const + { + for(u32bit j = 0; j != extensions.size(); ++j) + { + const Certificate_Extension* ext = extensions[j]; + + std::string setting; + + if(ext->config_id() != "") + setting = global_state().option("x509/exts/" + ext->config_id()); + + if(setting == "") + setting = "yes"; + + if(setting != "yes" && setting != "no" && setting != "critical") + throw Invalid_Argument("X509_CA:: Invalid value for option " + "x509/exts/" + ext->config_id() + " of " + + setting); + + bool is_critical = (setting == "critical"); + bool should_encode = ext->should_encode() && (setting != "no"); + + 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) + { + for(u32bit j = 0; j != extensions.size(); ++j) + delete extensions[j]; + extensions.clear(); + + BER_Decoder sequence = from_source.start_cons(SEQUENCE); + while(sequence.more_items()) + { + OID oid; + MemoryVector<byte> value; + bool critical; + + sequence.start_cons(SEQUENCE) + .decode(oid) + .decode_optional(critical, BOOLEAN, UNIVERSAL, false) + .decode(value, OCTET_STRING) + .verify_end() + .end_cons(); + + Certificate_Extension* ext = get_extension(oid); + + if(!ext) + { + if(!critical || !should_throw) + continue; + + throw Decoding_Error("Encountered unknown X.509 extension marked " + "as critical; OID = " + oid.as_string()); + } + + ext->decode_inner(value); + + extensions.push_back(ext); + } + sequence.verify_end(); + } + +/************************************************* +* Write the extensions to an info store * +*************************************************/ +void Extensions::contents_to(Data_Store& subject_info, + Data_Store& issuer_info) const + { + for(u32bit j = 0; j != extensions.size(); ++j) + extensions[j]->contents_to(subject_info, issuer_info); + } + +/************************************************* +* Delete an Extensions list * +*************************************************/ +Extensions::~Extensions() + { + for(u32bit j = 0; j != extensions.size(); ++j) + delete extensions[j]; + } + +namespace Cert_Extension { + +/************************************************* +* Checked accessor for the path_limit member * +*************************************************/ +u32bit Basic_Constraints::get_path_limit() const + { + if(!is_ca) + throw Invalid_State("Basic_Constraints::get_path_limit: Not a CA"); + return path_limit; + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Basic_Constraints::encode_inner() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode_if(is_ca, + DER_Encoder() + .encode(is_ca) + .encode_optional(path_limit, NO_CERT_PATH_LIMIT) + ) + .end_cons() + .get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Basic_Constraints::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_optional(is_ca, BOOLEAN, UNIVERSAL, false) + .decode_optional(path_limit, INTEGER, UNIVERSAL, NO_CERT_PATH_LIMIT) + .verify_end() + .end_cons(); + + if(is_ca == false) + path_limit = 0; + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void Basic_Constraints::contents_to(Data_Store& subject, Data_Store&) const + { + subject.add("X509v3.BasicConstraints.is_ca", (is_ca ? 1 : 0)); + subject.add("X509v3.BasicConstraints.path_constraint", path_limit); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Key_Usage::encode_inner() const + { + if(constraints == NO_CONSTRAINTS) + throw Encoding_Error("Cannot encode zero usage constraints"); + + const u32bit unused_bits = low_bit(constraints) - 1; + + SecureVector<byte> der; + der.append(BIT_STRING); + der.append(2 + ((unused_bits < 8) ? 1 : 0)); + der.append(unused_bits % 8); + der.append((constraints >> 8) & 0xFF); + if(constraints & 0xFF) + der.append(constraints & 0xFF); + + return der; + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Key_Usage::decode_inner(const MemoryRegion<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(u32bit j = 1; j != obj.value.size(); ++j) + usage = (obj.value[j] << 8) | usage; + + constraints = Key_Constraints(usage); + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void Key_Usage::contents_to(Data_Store& subject, Data_Store&) const + { + subject.add("X509v3.KeyUsage", constraints); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Subject_Key_ID::encode_inner() const + { + return DER_Encoder().encode(key_id, OCTET_STRING).get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Subject_Key_ID::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in).decode(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", key_id); + } + +/************************************************* +* Subject_Key_ID Constructor * +*************************************************/ +Subject_Key_ID::Subject_Key_ID(const MemoryRegion<byte>& pub_key) + { + std::auto_ptr<HashFunction> hash(get_hash("SHA-1")); + key_id = hash->process(pub_key); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Authority_Key_ID::encode_inner() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode(key_id, OCTET_STRING, ASN1_Tag(0), CONTEXT_SPECIFIC) + .end_cons() + .get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Authority_Key_ID::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_optional_string(key_id, OCTET_STRING, 0); + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void Authority_Key_ID::contents_to(Data_Store&, Data_Store& issuer) const + { + if(key_id.size()) + issuer.add("X509v3.AuthorityKeyIdentifier", key_id); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Alternative_Name::encode_inner() const + { + return DER_Encoder().encode(alt_name).get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Alternative_Name::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in).decode(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(oid_name_str == "X509v3.SubjectAlternativeName") + subject_info.add(contents); + else if(oid_name_str == "X509v3.IssuerAlternativeName") + issuer_info.add(contents); + else + throw Internal_Error("In Alternative_Name, unknown type " + + oid_name_str); + } + +/************************************************* +* Alternative_Name Constructor * +*************************************************/ +Alternative_Name::Alternative_Name(const AlternativeName& alt_name, + const std::string& oid_name_str, + const std::string& config_name_str) + { + this->alt_name = alt_name; + this->oid_name_str = oid_name_str; + this->config_name_str = config_name_str; + } + +/************************************************* +* Subject_Alternative_Name Constructor * +*************************************************/ +Subject_Alternative_Name::Subject_Alternative_Name( + const AlternativeName& name) : + + Alternative_Name(name, "X509v3.SubjectAlternativeName", + "subject_alternative_name") + { + } + +/************************************************* +* Issuer_Alternative_Name Constructor * +*************************************************/ +Issuer_Alternative_Name::Issuer_Alternative_Name(const AlternativeName& name) : + Alternative_Name(name, "X509v3.IssuerAlternativeName", + "issuer_alternative_name") + { + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Extended_Key_Usage::encode_inner() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode_list(oids) + .end_cons() + .get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Extended_Key_Usage::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_list(oids) + .end_cons(); + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void Extended_Key_Usage::contents_to(Data_Store& subject, Data_Store&) const + { + for(u32bit j = 0; j != oids.size(); ++j) + subject.add("X509v3.ExtendedKeyUsage", oids[j].as_string()); + } + +namespace { + +/************************************************* +* A policy specifier * +*************************************************/ +class Policy_Information : public ASN1_Object + { + public: + OID oid; + + void encode_into(DER_Encoder& codec) const + { + codec.start_cons(SEQUENCE) + .encode(oid) + .end_cons(); + } + + void decode_from(BER_Decoder& codec) + { + codec.start_cons(SEQUENCE) + .decode(oid) + .discard_remaining() + .end_cons(); + } + }; + +} + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> Certificate_Policies::encode_inner() const + { + throw Exception("Certificate_Policies::encode_inner: Bugged"); + + std::vector<Policy_Information> policies; + + return DER_Encoder() + .start_cons(SEQUENCE) + .encode_list(policies) + .end_cons() + .get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void Certificate_Policies::decode_inner(const MemoryRegion<byte>& in) + { + std::vector<Policy_Information> policies; + + BER_Decoder(in) + .start_cons(SEQUENCE) + .decode_list(policies) + .end_cons(); + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void Certificate_Policies::contents_to(Data_Store& info, Data_Store&) const + { + for(u32bit j = 0; j != oids.size(); ++j) + info.add("X509v3.ExtendedKeyUsage", oids[j].as_string()); + } + +/************************************************* +* Checked accessor for the crl_number member * +*************************************************/ +u32bit CRL_Number::get_crl_number() const + { + if(!has_value) + throw Invalid_State("CRL_Number::get_crl_number: Not set"); + return crl_number; + } + +/************************************************* +* Copy a CRL_Number extension * +*************************************************/ +CRL_Number* CRL_Number::copy() const + { + if(!has_value) + throw Invalid_State("CRL_Number::copy: Not set"); + return new CRL_Number(crl_number); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> CRL_Number::encode_inner() const + { + return DER_Encoder().encode(crl_number).get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void CRL_Number::decode_inner(const MemoryRegion<byte>& in) + { + BER_Decoder(in).decode(crl_number); + } + +/************************************************* +* Return a textual representation * +*************************************************/ +void CRL_Number::contents_to(Data_Store& info, Data_Store&) const + { + info.add("X509v3.CRLNumber", crl_number); + } + +/************************************************* +* Encode the extension * +*************************************************/ +MemoryVector<byte> CRL_ReasonCode::encode_inner() const + { + return DER_Encoder() + .encode(static_cast<u32bit>(reason), ENUMERATED, UNIVERSAL) + .get_contents(); + } + +/************************************************* +* Decode the extension * +*************************************************/ +void CRL_ReasonCode::decode_inner(const MemoryRegion<byte>& in) + { + u32bit reason_code = 0; + BER_Decoder(in).decode(reason_code, ENUMERATED, UNIVERSAL); + 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", reason); + } + +} + +} diff --git a/modules/x509/x509_ext.h b/modules/x509/x509_ext.h new file mode 100644 index 000000000..5b302df0d --- /dev/null +++ b/modules/x509/x509_ext.h @@ -0,0 +1,315 @@ +/************************************************* +* X.509 Certificate Extensions Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_EXTENSIONS_H__ +#define BOTAN_X509_EXTENSIONS_H__ + +#include <botan/asn1_int.h> +#include <botan/asn1_oid.h> +#include <botan/asn1_obj.h> +#include <botan/datastor.h> +#include <botan/enums.h> + +namespace Botan { + +/************************************************* +* X.509 Certificate Extension * +*************************************************/ +class BOTAN_DLL Certificate_Extension + { + public: + OID oid_of() const; + + virtual Certificate_Extension* copy() const = 0; + + virtual void contents_to(Data_Store&, Data_Store&) const = 0; + virtual std::string config_id() const = 0; + virtual std::string oid_name() const = 0; + + virtual ~Certificate_Extension() {} + protected: + friend class Extensions; + virtual bool should_encode() const { return true; } + virtual MemoryVector<byte> encode_inner() const = 0; + virtual void decode_inner(const MemoryRegion<byte>&) = 0; + }; + +/************************************************* +* X.509 Certificate Extension List * +*************************************************/ +class BOTAN_DLL Extensions : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const; + void decode_from(class BER_Decoder&); + + void contents_to(Data_Store&, Data_Store&) const; + + void add(Certificate_Extension* extn) + { extensions.push_back(extn); } + + Extensions& operator=(const Extensions&); + + Extensions(const Extensions&); + Extensions(bool st = true) : should_throw(st) {} + ~Extensions(); + private: + static Certificate_Extension* get_extension(const OID&); + + std::vector<Certificate_Extension*> extensions; + bool should_throw; + }; + +namespace Cert_Extension { + +/************************************************* +* Basic Constraints Extension * +*************************************************/ +class BOTAN_DLL Basic_Constraints : public Certificate_Extension + { + public: + Basic_Constraints* copy() const + { return new Basic_Constraints(is_ca, path_limit); } + + Basic_Constraints(bool ca = false, u32bit limit = 0) : + is_ca(ca), path_limit(limit) {} + + bool get_is_ca() const { return is_ca; } + u32bit get_path_limit() const; + private: + std::string config_id() const { return "basic_constraints"; } + std::string oid_name() const { return "X509v3.BasicConstraints"; } + + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + bool is_ca; + u32bit path_limit; + }; + +/************************************************* +* Key Usage Constraints Extension * +*************************************************/ +class BOTAN_DLL Key_Usage : public Certificate_Extension + { + public: + Key_Usage* copy() const { return new Key_Usage(constraints); } + + Key_Usage(Key_Constraints c = NO_CONSTRAINTS) : constraints(c) {} + + Key_Constraints get_constraints() const { return constraints; } + private: + std::string config_id() const { return "key_usage"; } + std::string oid_name() const { return "X509v3.KeyUsage"; } + + bool should_encode() const { return (constraints != NO_CONSTRAINTS); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + Key_Constraints constraints; + }; + +/************************************************* +* Subject Key Identifier Extension * +*************************************************/ +class BOTAN_DLL Subject_Key_ID : public Certificate_Extension + { + public: + Subject_Key_ID* copy() const { return new Subject_Key_ID(key_id); } + + Subject_Key_ID() {} + Subject_Key_ID(const MemoryRegion<byte>&); + + MemoryVector<byte> get_key_id() const { return key_id; } + private: + std::string config_id() const { return "subject_key_id"; } + std::string oid_name() const { return "X509v3.SubjectKeyIdentifier"; } + + bool should_encode() const { return (key_id.size() > 0); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + MemoryVector<byte> key_id; + }; + +/************************************************* +* Authority Key Identifier Extension * +*************************************************/ +class BOTAN_DLL Authority_Key_ID : public Certificate_Extension + { + public: + Authority_Key_ID* copy() const { return new Authority_Key_ID(key_id); } + + Authority_Key_ID() {} + Authority_Key_ID(const MemoryRegion<byte>& k) : key_id(k) {} + + MemoryVector<byte> get_key_id() const { return key_id; } + private: + std::string config_id() const { return "authority_key_id"; } + std::string oid_name() const { return "X509v3.AuthorityKeyIdentifier"; } + + bool should_encode() const { return (key_id.size() > 0); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + MemoryVector<byte> key_id; + }; + +/************************************************* +* Alternative Name Extension Base Class * +*************************************************/ +class BOTAN_DLL Alternative_Name : public Certificate_Extension + { + public: + AlternativeName get_alt_name() const { return alt_name; } + + protected: + Alternative_Name(const AlternativeName&, + const std::string&, const std::string&); + + Alternative_Name(const std::string&, const std::string&); + private: + std::string config_id() const { return config_name_str; } + std::string oid_name() const { return oid_name_str; } + + bool should_encode() const { return alt_name.has_items(); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + std::string config_name_str, oid_name_str; + AlternativeName alt_name; + }; + +/************************************************* +* Subject Alternative Name Extension * +*************************************************/ +class BOTAN_DLL Subject_Alternative_Name : public Alternative_Name + { + public: + Subject_Alternative_Name* copy() const + { return new Subject_Alternative_Name(get_alt_name()); } + + 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 + { return new Issuer_Alternative_Name(get_alt_name()); } + + Issuer_Alternative_Name(const AlternativeName& = AlternativeName()); + }; + +/************************************************* +* Extended Key Usage Extension * +*************************************************/ +class BOTAN_DLL Extended_Key_Usage : public Certificate_Extension + { + public: + Extended_Key_Usage* copy() const { return new Extended_Key_Usage(oids); } + + Extended_Key_Usage() {} + Extended_Key_Usage(const std::vector<OID>& o) : oids(o) {} + + std::vector<OID> get_oids() const { return oids; } + private: + std::string config_id() const { return "extended_key_usage"; } + std::string oid_name() const { return "X509v3.ExtendedKeyUsage"; } + + bool should_encode() const { return (oids.size() > 0); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + std::vector<OID> oids; + }; + +/************************************************* +* Certificate Policies Extension * +*************************************************/ +class BOTAN_DLL Certificate_Policies : public Certificate_Extension + { + public: + Certificate_Policies* copy() const + { return new Certificate_Policies(oids); } + + Certificate_Policies() {} + Certificate_Policies(const std::vector<OID>& o) : oids(o) {} + + std::vector<OID> get_oids() const { return oids; } + private: + std::string config_id() const { return "policy_info"; } + std::string oid_name() const { return "X509v3.CertificatePolicies"; } + + bool should_encode() const { return (oids.size() > 0); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + std::vector<OID> oids; + }; + +/************************************************* +* CRL Number Extension * +*************************************************/ +class BOTAN_DLL CRL_Number : public Certificate_Extension + { + public: + CRL_Number* copy() const; + + CRL_Number() : has_value(false), crl_number(0) {} + CRL_Number(u32bit n) : has_value(true), crl_number(n) {} + + u32bit get_crl_number() const; + private: + std::string config_id() const { return "crl_number"; } + std::string oid_name() const { return "X509v3.CRLNumber"; } + + bool should_encode() const { return has_value; } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + bool has_value; + u32bit crl_number; + }; + +/************************************************* +* CRL Entry Reason Code Extension * +*************************************************/ +class BOTAN_DLL CRL_ReasonCode : public Certificate_Extension + { + public: + CRL_ReasonCode* copy() const { return new CRL_ReasonCode(reason); } + + CRL_ReasonCode(CRL_Code r = UNSPECIFIED) : reason(r) {} + + CRL_Code get_reason() const { return reason; } + private: + std::string config_id() const { return "crl_reason"; } + std::string oid_name() const { return "X509v3.ReasonCode"; } + + bool should_encode() const { return (reason != UNSPECIFIED); } + MemoryVector<byte> encode_inner() const; + void decode_inner(const MemoryRegion<byte>&); + void contents_to(Data_Store&, Data_Store&) const; + + CRL_Code reason; + }; + +} + +} + +#endif diff --git a/modules/x509/x509_obj.cpp b/modules/x509/x509_obj.cpp new file mode 100644 index 000000000..e78790949 --- /dev/null +++ b/modules/x509/x509_obj.cpp @@ -0,0 +1,233 @@ +/************************************************* +* X.509 SIGNED Object Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509_obj.h> +#include <botan/x509_key.h> +#include <botan/look_pk.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> +#include <memory> + +namespace Botan { + +/************************************************* +* Create a generic X.509 object * +*************************************************/ +X509_Object::X509_Object(DataSource& stream, const std::string& labels) + { + init(stream, labels); + } + +/************************************************* +* Createa 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); + } + +/************************************************* +* Read a PEM or BER X.509 object * +*************************************************/ +void X509_Object::init(DataSource& in, const std::string& labels) + { + PEM_labels_allowed = split_on(labels, '/'); + if(PEM_labels_allowed.size() < 1) + throw Invalid_Argument("Bad labels argument to X509_Object"); + + PEM_label_pref = PEM_labels_allowed[0]; + std::sort(PEM_labels_allowed.begin(), PEM_labels_allowed.end()); + + try { + if(ASN1::maybe_BER(in) && !PEM_Code::matches(in)) + decode_info(in); + else + { + std::string got_label; + DataSource_Memory ber(PEM_Code::decode(in, got_label)); + + if(!std::binary_search(PEM_labels_allowed.begin(), + PEM_labels_allowed.end(), got_label)) + throw Decoding_Error("Invalid PEM label: " + got_label); + decode_info(ber); + } + } + catch(Decoding_Error) + { + throw Decoding_Error(PEM_label_pref + " decoding failed"); + } + } + +/************************************************* +* Read a BER encoded X.509 object * +*************************************************/ +void X509_Object::decode_info(DataSource& source) + { + BER_Decoder(source) + .start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .raw_bytes(tbs_bits) + .end_cons() + .decode(sig_algo) + .decode(sig, BIT_STRING) + .verify_end() + .end_cons(); + } + +/************************************************* +* Return a BER or PEM encoded X.509 object * +*************************************************/ +void X509_Object::encode(Pipe& out, X509_Encoding encoding) const + { + SecureVector<byte> der = DER_Encoder() + .start_cons(SEQUENCE) + .start_cons(SEQUENCE) + .raw_bytes(tbs_bits) + .end_cons() + .encode(sig_algo) + .encode(sig, BIT_STRING) + .end_cons() + .get_contents(); + + if(encoding == PEM) + out.write(PEM_Code::encode(der, PEM_label_pref)); + else + out.write(der); + } + +/************************************************* +* Return a BER encoded X.509 object * +*************************************************/ +SecureVector<byte> X509_Object::BER_encode() const + { + Pipe ber; + ber.start_msg(); + encode(ber, RAW_BER); + ber.end_msg(); + return ber.read_all(); + } + +/************************************************* +* Return a PEM encoded X.509 object * +*************************************************/ +std::string X509_Object::PEM_encode() const + { + Pipe pem; + pem.start_msg(); + encode(pem, PEM); + pem.end_msg(); + return pem.read_all_as_string(); + } + +/************************************************* +* Return the TBS data * +*************************************************/ +SecureVector<byte> X509_Object::tbs_data() const + { + return ASN1::put_in_sequence(tbs_bits); + } + +/************************************************* +* Return the signature of this object * +*************************************************/ +SecureVector<byte> X509_Object::signature() const + { + return sig; + } + +/************************************************* +* Return the algorithm used to sign this object * +*************************************************/ +AlgorithmIdentifier X509_Object::signature_algorithm() const + { + return sig_algo; + } + +/************************************************* +* Check the signature on an object * +*************************************************/ +bool X509_Object::check_signature(Public_Key& pub_key) const + { + try { + 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()) + return false; + + std::string padding = sig_info[1]; + Signature_Format format = + (pub_key.message_parts() >= 2) ? DER_SEQUENCE : IEEE_1363; + + std::auto_ptr<PK_Verifier> verifier; + + if(dynamic_cast<PK_Verifying_with_MR_Key*>(&pub_key)) + { + PK_Verifying_with_MR_Key& sig_key = + dynamic_cast<PK_Verifying_with_MR_Key&>(pub_key); + verifier.reset(get_pk_verifier(sig_key, padding, format)); + } + else if(dynamic_cast<PK_Verifying_wo_MR_Key*>(&pub_key)) + { + PK_Verifying_wo_MR_Key& sig_key = + dynamic_cast<PK_Verifying_wo_MR_Key&>(pub_key); + verifier.reset(get_pk_verifier(sig_key, padding, format)); + } + else + return false; + + return verifier->verify_message(tbs_data(), signature()); + } + catch(...) + { + return false; + } + } + +/************************************************* +* Apply the X.509 SIGNED macro * +*************************************************/ +MemoryVector<byte> X509_Object::make_signed(PK_Signer* signer, + RandomNumberGenerator& rng, + const AlgorithmIdentifier& algo, + const MemoryRegion<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(); + } + +/************************************************* +* Try to decode the actual information * +*************************************************/ +void X509_Object::do_decode() + { + try { + force_decode(); + } + catch(Decoding_Error& e) + { + const std::string what = e.what(); + throw Decoding_Error(PEM_label_pref + " decoding failed (" + + what.substr(23, std::string::npos) + ")"); + } + catch(Invalid_Argument& e) + { + const std::string what = e.what(); + throw Decoding_Error(PEM_label_pref + " decoding failed (" + + what.substr(7, std::string::npos) + ")"); + } + } + +} diff --git a/modules/x509/x509_obj.h b/modules/x509/x509_obj.h new file mode 100644 index 000000000..8808fd686 --- /dev/null +++ b/modules/x509/x509_obj.h @@ -0,0 +1,56 @@ +/************************************************* +* X.509 SIGNED Object Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_OBJECT_H__ +#define BOTAN_X509_OBJECT_H__ + +#include <botan/asn1_obj.h> +#include <botan/pipe.h> +#include <botan/enums.h> +#include <botan/rng.h> +#include <vector> + +namespace Botan { + +/************************************************* +* Generic X.509 SIGNED Object * +*************************************************/ +class BOTAN_DLL X509_Object + { + public: + SecureVector<byte> tbs_data() const; + SecureVector<byte> signature() const; + AlgorithmIdentifier signature_algorithm() const; + + static MemoryVector<byte> make_signed(class PK_Signer*, + RandomNumberGenerator&, + const AlgorithmIdentifier&, + const MemoryRegion<byte>&); + + bool check_signature(class Public_Key&) const; + + void encode(Pipe&, X509_Encoding = PEM) const; + SecureVector<byte> BER_encode() const; + std::string PEM_encode() const; + + X509_Object(DataSource&, const std::string&); + X509_Object(const std::string&, const std::string&); + virtual ~X509_Object() {} + protected: + void do_decode(); + X509_Object() {} + AlgorithmIdentifier sig_algo; + SecureVector<byte> tbs_bits, sig; + private: + virtual void force_decode() = 0; + void init(DataSource&, const std::string&); + void decode_info(DataSource&); + std::vector<std::string> PEM_labels_allowed; + std::string PEM_label_pref; + }; + +} + +#endif diff --git a/modules/x509/x509cert.cpp b/modules/x509/x509cert.cpp new file mode 100644 index 000000000..b24e6a036 --- /dev/null +++ b/modules/x509/x509cert.cpp @@ -0,0 +1,364 @@ +/************************************************* +* X.509 Certificates Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509cert.h> +#include <botan/x509_ext.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/stl_util.h> +#include <botan/parsing.h> +#include <botan/bigint.h> +#include <botan/oids.h> +#include <botan/pem.h> +#include <algorithm> + +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; + + std::vector<std::string>::const_iterator i = in.begin(); + while(i != in.end()) + { + out.push_back(OIDS::lookup(OID(*i))); + ++i; + } + return out; + } + +} + +/************************************************* +* X509_Certificate Constructor * +*************************************************/ +X509_Certificate::X509_Certificate(DataSource& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") + { + self_signed = false; + do_decode(); + } + +/************************************************* +* X509_Certificate Constructor * +*************************************************/ +X509_Certificate::X509_Certificate(const std::string& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") + { + self_signed = false; + do_decode(); + } + +/************************************************* +* Decode the TBSCertificate data * +*************************************************/ +void X509_Certificate::force_decode() + { + u32bit version; + BigInt serial_bn; + AlgorithmIdentifier sig_algo_inner; + X509_DN dn_issuer, dn_subject; + X509_Time start, end; + + BER_Decoder tbs_cert(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 " + to_string(version)); + if(sig_algo != sig_algo_inner) + throw Decoding_Error("Algorithm identifier mismatch"); + + self_signed = (dn_subject == dn_issuer); + + subject.add(dn_subject.contents()); + issuer.add(dn_issuer.contents()); + + 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); + + MemoryVector<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)) + { + Extensions extensions; + + BER_Decoder(v3_exts_data.value).decode(extensions).verify_end(); + + extensions.contents_to(subject, 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"); + + subject.add("X509.Certificate.version", version); + subject.add("X509.Certificate.serial", BigInt::encode(serial_bn)); + subject.add("X509.Certificate.start", start.readable_string()); + subject.add("X509.Certificate.end", end.readable_string()); + + issuer.add("X509.Certificate.v2.key_id", v2_issuer_key_id); + subject.add("X509.Certificate.v2.key_id", v2_subject_key_id); + + subject.add("X509.Certificate.public_key", + PEM_Code::encode( + ASN1::put_in_sequence(public_key.value), + "PUBLIC KEY" + ) + ); + + if(is_CA_cert() && + !subject.has_value("X509v3.BasicConstraints.path_constraint")) + { + u32bit limit = (x509_version() < 3) ? NO_CERT_PATH_LIMIT : 0; + subject.add("X509v3.BasicConstraints.path_constraint", limit); + } + } + +/************************************************* +* Return the X.509 version in use * +*************************************************/ +u32bit X509_Certificate::x509_version() const + { + return (subject.get1_u32bit("X509.Certificate.version") + 1); + } + +/************************************************* +* Return the time this cert becomes valid * +*************************************************/ +std::string X509_Certificate::start_time() const + { + return subject.get1("X509.Certificate.start"); + } + +/************************************************* +* Return the time this cert becomes invalid * +*************************************************/ +std::string X509_Certificate::end_time() const + { + return subject.get1("X509.Certificate.end"); + } + +/************************************************* +* Return information about the subject * +*************************************************/ +std::vector<std::string> +X509_Certificate::subject_info(const std::string& what) const + { + return 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 issuer.get(X509_DN::deref_info_field(what)); + } + +/************************************************* +* Return the public key in this certificate * +*************************************************/ +Public_Key* X509_Certificate::subject_public_key() const + { + DataSource_Memory source(subject.get1("X509.Certificate.public_key")); + return X509::load_key(source); + } + +/************************************************* +* Check if the certificate is for a CA * +*************************************************/ +bool X509_Certificate::is_CA_cert() const + { + if(!subject.get1_u32bit("X509v3.BasicConstraints.is_ca")) + return false; + if((constraints() & KEY_CERT_SIGN) || (constraints() == NO_CONSTRAINTS)) + return true; + return false; + } + +/************************************************* +* Return the path length constraint * +*************************************************/ +u32bit X509_Certificate::path_limit() const + { + return subject.get1_u32bit("X509v3.BasicConstraints.path_constraint", 0); + } + +/************************************************* +* Return the key usage constraints * +*************************************************/ +Key_Constraints X509_Certificate::constraints() const + { + return Key_Constraints(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(subject.get("X509v3.ExtendedKeyUsage")); + } + +/************************************************* +* Return the list of certificate policies * +*************************************************/ +std::vector<std::string> X509_Certificate::policies() const + { + return lookup_oids(subject.get("X509v3.CertificatePolicies")); + } + +/************************************************* +* Return the authority key id * +*************************************************/ +MemoryVector<byte> X509_Certificate::authority_key_id() const + { + return issuer.get1_memvec("X509v3.AuthorityKeyIdentifier"); + } + +/************************************************* +* Return the subject key id * +*************************************************/ +MemoryVector<byte> X509_Certificate::subject_key_id() const + { + return subject.get1_memvec("X509v3.SubjectKeyIdentifier"); + } + +/************************************************* +* Return the certificate serial number * +*************************************************/ +MemoryVector<byte> X509_Certificate::serial_number() const + { + return subject.get1_memvec("X509.Certificate.serial"); + } + +/************************************************* +* Return the distinguished name of the issuer * +*************************************************/ +X509_DN X509_Certificate::issuer_dn() const + { + return create_dn(issuer); + } + +/************************************************* +* Return the distinguished name of the subject * +*************************************************/ +X509_DN X509_Certificate::subject_dn() const + { + return create_dn(subject); + } + +/************************************************* +* Compare two certificates for equality * +*************************************************/ +bool X509_Certificate::operator==(const X509_Certificate& other) const + { + return (sig == other.sig && + sig_algo == other.sig_algo && + self_signed == other.self_signed && + issuer == other.issuer && + subject == other.subject); + } + +/************************************************* +* X.509 Certificate Comparison * +*************************************************/ +bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2) + { + return !(cert1 == cert2); + } + +/************************************************* +* Create and populate a X509_DN * +*************************************************/ +X509_DN create_dn(const Data_Store& info) + { + class DN_Matcher : public Data_Store::Matcher + { + public: + bool operator()(const std::string& key, const std::string&) const + { + if(key.find("X520.") != std::string::npos) + return true; + return false; + } + }; + + std::multimap<std::string, std::string> names = + info.search_with(DN_Matcher()); + + X509_DN dn; + + std::multimap<std::string, std::string>::iterator j; + for(j = names.begin(); j != names.end(); ++j) + dn.add_attribute(j->first, j->second); + + return dn; + } + +/************************************************* +* Create and populate an AlternativeName * +*************************************************/ +AlternativeName create_alt_name(const Data_Store& info) + { + class AltName_Matcher : public Data_Store::Matcher + { + public: + bool operator()(const std::string& key, const std::string&) const + { + for(u32bit j = 0; j != matches.size(); ++j) + if(key.compare(matches[j]) == 0) + return true; + return false; + } + + AltName_Matcher(const std::string& match_any_of) + { + matches = split_on(match_any_of, '/'); + } + private: + std::vector<std::string> matches; + }; + + std::multimap<std::string, std::string> names = + info.search_with(AltName_Matcher("RFC822/DNS/URI/IP")); + + AlternativeName alt_name; + + std::multimap<std::string, std::string>::iterator j; + for(j = names.begin(); j != names.end(); ++j) + alt_name.add_attribute(j->first, j->second); + + return alt_name; + } + +} diff --git a/modules/x509/x509cert.h b/modules/x509/x509cert.h new file mode 100644 index 000000000..c8dc2c435 --- /dev/null +++ b/modules/x509/x509cert.h @@ -0,0 +1,72 @@ +/************************************************* +* X.509 Certificates Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_CERTS_H__ +#define BOTAN_X509_CERTS_H__ + +#include <botan/x509_obj.h> +#include <botan/x509_key.h> +#include <botan/datastor.h> +#include <botan/enums.h> +#include <map> + +namespace Botan { + +/************************************************* +* X.509 Certificate * +*************************************************/ +class BOTAN_DLL X509_Certificate : public X509_Object + { + public: + Public_Key* subject_public_key() const; + + X509_DN issuer_dn() const; + X509_DN subject_dn() const; + std::vector<std::string> subject_info(const std::string&) const; + std::vector<std::string> issuer_info(const std::string&) const; + + std::string start_time() const; + std::string end_time() const; + + u32bit x509_version() const; + MemoryVector<byte> serial_number() const; + + MemoryVector<byte> authority_key_id() const; + MemoryVector<byte> subject_key_id() const; + bool is_self_signed() const { return self_signed; } + bool is_CA_cert() const; + + u32bit path_limit() const; + Key_Constraints constraints() const; + std::vector<std::string> ex_constraints() const; + std::vector<std::string> policies() const; + + bool operator==(const X509_Certificate&) const; + + X509_Certificate(DataSource&); + X509_Certificate(const std::string&); + private: + void force_decode(); + friend class X509_CA; + X509_Certificate() {} + + Data_Store subject, issuer; + bool self_signed; + }; + +/************************************************* +* X.509 Certificate Comparison * +*************************************************/ +BOTAN_DLL bool operator!=(const X509_Certificate&, const X509_Certificate&); + +/************************************************* +* Data Store Extraction Operations * +*************************************************/ +BOTAN_DLL X509_DN create_dn(const Data_Store&); +BOTAN_DLL AlternativeName create_alt_name(const Data_Store&); + +} + +#endif diff --git a/modules/x509/x509find.cpp b/modules/x509/x509find.cpp new file mode 100644 index 000000000..83d02449d --- /dev/null +++ b/modules/x509/x509find.cpp @@ -0,0 +1,109 @@ +/************************************************* +* X.509 Certificate Store Searching Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509find.h> +#include <botan/charset.h> +#include <algorithm> + +namespace Botan { + +namespace { + +/************************************************* +* Compare based on case-insensive substrings * +*************************************************/ +bool substring_match(const std::string& searching_for, + const std::string& found) + { + if(std::search(found.begin(), found.end(), searching_for.begin(), + searching_for.end(), Charset::caseless_cmp) != found.end()) + return true; + return false; + } + +/************************************************* +* Compare based on case-insensive match * +*************************************************/ +bool ignore_case(const std::string& searching_for, const std::string& found) + { + if(searching_for.size() != found.size()) + return false; + + return std::equal(found.begin(), found.end(), + searching_for.begin(), Charset::caseless_cmp); + } + +} + +/************************************************* +* Search based on the contents of a DN entry * +*************************************************/ +bool DN_Check::match(const X509_Certificate& cert) const + { + std::vector<std::string> info = cert.subject_info(dn_entry); + + for(u32bit j = 0; j != info.size(); ++j) + if(compare(info[j], looking_for)) + return true; + return false; + } + +/************************************************* +* DN_Check Constructor * +*************************************************/ +DN_Check::DN_Check(const std::string& dn_entry, const std::string& looking_for, + compare_fn func) + { + this->dn_entry = dn_entry; + this->looking_for = looking_for; + compare = func; + } + +/************************************************* +* DN_Check Constructor * +*************************************************/ +DN_Check::DN_Check(const std::string& dn_entry, const std::string& looking_for, + Search_Type method) + { + this->dn_entry = dn_entry; + this->looking_for = looking_for; + + if(method == SUBSTRING_MATCHING) + compare = &substring_match; + else if(method == IGNORE_CASE) + compare = &ignore_case; + else + throw Invalid_Argument("Unknown method argument to DN_Check()"); + } + +/************************************************* +* Match by issuer and serial number * +*************************************************/ +bool IandS_Match::match(const X509_Certificate& cert) const + { + if(cert.serial_number() != serial) + return false; + return (cert.issuer_dn() == issuer); + } + +/************************************************* +* IandS_Match Constructor * +*************************************************/ +IandS_Match::IandS_Match(const X509_DN& issuer, + const MemoryRegion<byte>& serial) + { + this->issuer = issuer; + this->serial = serial; + } + +/************************************************* +* Match by subject key identifier * +*************************************************/ +bool SKID_Match::match(const X509_Certificate& cert) const + { + return (cert.subject_key_id() == skid); + } + +} diff --git a/modules/x509/x509find.h b/modules/x509/x509find.h new file mode 100644 index 000000000..65781199f --- /dev/null +++ b/modules/x509/x509find.h @@ -0,0 +1,58 @@ +/************************************************* +* X.509 Certificate Store Searching Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_CERT_STORE_SEARCH_H__ +#define BOTAN_X509_CERT_STORE_SEARCH_H__ + +#include <botan/x509stor.h> + +namespace Botan { + +/************************************************* +* Search based on the contents of a DN entry * +*************************************************/ +class BOTAN_DLL DN_Check : public X509_Store::Search_Func + { + public: + typedef bool (*compare_fn)(const std::string&, const std::string&); + enum Search_Type { SUBSTRING_MATCHING, IGNORE_CASE }; + + bool match(const X509_Certificate& cert) const; + + DN_Check(const std::string&, const std::string&, compare_fn); + DN_Check(const std::string&, const std::string&, Search_Type); + private: + std::string dn_entry, looking_for; + compare_fn compare; + }; + +/************************************************* +* Search for a certificate by issuer/serial * +*************************************************/ +class BOTAN_DLL IandS_Match : public X509_Store::Search_Func + { + public: + bool match(const X509_Certificate& cert) const; + IandS_Match(const X509_DN&, const MemoryRegion<byte>&); + private: + X509_DN issuer; + MemoryVector<byte> serial; + }; + +/************************************************* +* Search for a certificate by subject keyid * +*************************************************/ +class BOTAN_DLL SKID_Match : public X509_Store::Search_Func + { + public: + bool match(const X509_Certificate& cert) const; + SKID_Match(const MemoryRegion<byte>& s) : skid(s) {} + private: + MemoryVector<byte> skid; + }; + +} + +#endif diff --git a/modules/x509/x509opt.cpp b/modules/x509/x509opt.cpp new file mode 100644 index 000000000..716884ed5 --- /dev/null +++ b/modules/x509/x509opt.cpp @@ -0,0 +1,106 @@ +/************************************************* +* X.509 Certificate Options Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509self.h> +#include <botan/util.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <ctime> + +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); + } + +/************************************************* +* Set when the certificate should expire * +*************************************************/ +void X509_Cert_Options::not_after(const std::string& time_string) + { + end = X509_Time(time_string); + } + +/************************************************* +* 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(u32bit limit) + { + is_CA = true; + path_limit = limit; + } + +/************************************************* +* Do basic sanity checks * +*************************************************/ +void X509_Cert_Options::sanity_check() const + { + if(common_name == "" || country == "") + throw Encoding_Error("X.509 certificate: name and country MUST be set"); + if(country.size() != 2) + throw Encoding_Error("Invalid ISO country code: " + country); + if(start >= end) + throw Encoding_Error("X509_Cert_Options: invalid time constraints"); + } + +/************************************************* +* Initialize the certificate options * +*************************************************/ +X509_Cert_Options::X509_Cert_Options(const std::string& initial_opts, + u32bit expiration_time_in_seconds) + { + is_CA = false; + path_limit = 0; + constraints = NO_CONSTRAINTS; + + const u32bit now = system_time(); + + start = X509_Time(now); + end = X509_Time(now + expiration_time_in_seconds); + + if(initial_opts == "") + 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/modules/x509/x509self.cpp b/modules/x509/x509self.cpp new file mode 100644 index 000000000..6c3baae9b --- /dev/null +++ b/modules/x509/x509self.cpp @@ -0,0 +1,174 @@ +/************************************************* +* PKCS #10/Self Signed Cert Creation Source File * +* (C) 1999-2008 Jack Lloyd * +*************************************************/ + +#include <botan/x509self.h> +#include <botan/x509_ext.h> +#include <botan/x509_ca.h> +#include <botan/der_enc.h> +#include <botan/look_pk.h> +#include <botan/oids.h> +#include <botan/pipe.h> +#include <memory> + +namespace Botan { + +namespace { + +/************************************************* +* Shared setup for self-signed items * +*************************************************/ +MemoryVector<byte> shared_setup(const X509_Cert_Options& opts, + const Private_Key& key) + { + const Private_Key* key_pointer = &key; + if(!dynamic_cast<const PK_Signing_Key*>(key_pointer)) + throw Invalid_Argument("Key type " + key.algo_name() + " cannot sign"); + + opts.sanity_check(); + + Pipe key_encoder; + key_encoder.start_msg(); + X509::encode(key, key_encoder, RAW_BER); + key_encoder.end_msg(); + + return key_encoder.read_all(); + } + +/************************************************* +* 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, + RandomNumberGenerator& rng) + { + AlgorithmIdentifier sig_algo; + X509_DN subject_dn; + AlternativeName subject_alt; + + MemoryVector<byte> pub_key = shared_setup(opts, key); + std::auto_ptr<PK_Signer> signer(choose_sig_format(key, 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 + constraints = find_constraints(key, opts.constraints); + + Extensions extensions; + + extensions.add(new Cert_Extension::Subject_Key_ID(pub_key)); + 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)); + extensions.add( + new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); + + 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, + RandomNumberGenerator& rng) + { + AlgorithmIdentifier sig_algo; + X509_DN subject_dn; + AlternativeName subject_alt; + + MemoryVector<byte> pub_key = shared_setup(opts, key); + std::auto_ptr<PK_Signer> signer(choose_sig_format(key, sig_algo)); + load_info(opts, subject_dn, subject_alt); + + const u32bit PKCS10_VERSION = 0; + + Extensions extensions; + + extensions.add( + new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); + extensions.add( + new Cert_Extension::Key_Usage( + opts.is_CA ? Key_Constraints(KEY_CERT_SIGN | CRL_SIGN) : + find_constraints(key, opts.constraints) + ) + ); + 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 != "") + { + ASN1_String challenge(opts.challenge, DIRECTORY_STRING); + + tbs_req.encode( + Attribute("PKCS9.ChallengePassword", + DER_Encoder().encode(challenge).get_contents() + ) + ); + } + + tbs_req.encode( + Attribute("PKCS9.ExtensionRequest", + DER_Encoder() + .start_cons(SEQUENCE) + .encode(extensions) + .end_cons() + .get_contents() + ) + ) + .end_explicit() + .end_cons(); + + DataSource_Memory source( + X509_Object::make_signed(signer.get(), + rng, + sig_algo, + tbs_req.get_contents()) + ); + + return PKCS10_Request(source); + } + +} + +} diff --git a/modules/x509/x509self.h b/modules/x509/x509self.h new file mode 100644 index 000000000..2f83a12be --- /dev/null +++ b/modules/x509/x509self.h @@ -0,0 +1,75 @@ +/************************************************* +* X.509 Self-Signed Certificate Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_SELF_H__ +#define BOTAN_X509_SELF_H__ + +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/pkcs10.h> + +namespace Botan { + +/************************************************* +* Options for X.509 Certificates * +*************************************************/ +class BOTAN_DLL X509_Cert_Options + { + public: + std::string common_name; + std::string country; + std::string organization; + std::string org_unit; + std::string locality; + std::string state; + std::string serial_number; + + std::string email, uri, dns, ip, xmpp; + + std::string challenge; + + X509_Time start, end; + + bool is_CA; + u32bit path_limit; + Key_Constraints constraints; + std::vector<OID> ex_constraints; + + void sanity_check() const; + + void CA_key(u32bit = 8); + void not_before(const std::string&); + void not_after(const std::string&); + + void add_constraints(Key_Constraints); + void add_ex_constraint(const OID&); + void add_ex_constraint(const std::string&); + + X509_Cert_Options(const std::string& = "", + u32bit expire = 365 * 24 * 60 * 60); + }; + +namespace X509 { + +/************************************************* +* Create a self-signed X.509 certificate * +*************************************************/ +BOTAN_DLL X509_Certificate +create_self_signed_cert(const X509_Cert_Options&, + const Private_Key&, + RandomNumberGenerator& rng); + +/************************************************* +* Create a PKCS #10 certificate request * +*************************************************/ +BOTAN_DLL PKCS10_Request create_cert_req(const X509_Cert_Options&, + const Private_Key&, + RandomNumberGenerator& rng); + +} + +} + +#endif diff --git a/modules/x509/x509stor.cpp b/modules/x509/x509stor.cpp new file mode 100644 index 000000000..f050b33b1 --- /dev/null +++ b/modules/x509/x509stor.cpp @@ -0,0 +1,693 @@ +/************************************************* +* X.509 Certificate Store Source File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#include <botan/x509stor.h> +#include <botan/parsing.h> +#include <botan/pubkey.h> +#include <botan/look_pk.h> +#include <botan/oids.h> +#include <botan/util.h> +#include <algorithm> +#include <memory> + +namespace Botan { + +namespace { + +/************************************************* +* Do a validity check * +*************************************************/ +s32bit validity_check(const X509_Time& start, const X509_Time& end, + u64bit current_time, u32bit slack) + { + const s32bit NOT_YET_VALID = -1, VALID_TIME = 0, EXPIRED = 1; + + if(start.cmp(current_time + slack) > 0) + return NOT_YET_VALID; + if(end.cmp(current_time - slack) < 0) + return EXPIRED; + return VALID_TIME; + } + +/************************************************* +* Compare the value of unique ID fields * +*************************************************/ +bool compare_ids(const MemoryVector<byte>& id1, + const MemoryVector<byte>& id2) + { + if(!id1.size() || !id2.size()) + return true; + return (id1 == id2); + } + +/************************************************* +* Check a particular usage restriction * +*************************************************/ +bool check_usage(const X509_Certificate& cert, X509_Store::Cert_Usage usage, + X509_Store::Cert_Usage check_for, Key_Constraints constraints) + { + if((usage & check_for) == 0) + return true; + if(cert.constraints() == NO_CONSTRAINTS) + return true; + if(cert.constraints() & constraints) + return true; + return false; + } + +/************************************************* +* Check a particular usage restriction * +*************************************************/ +bool check_usage(const X509_Certificate& cert, X509_Store::Cert_Usage usage, + X509_Store::Cert_Usage check_for, + const std::string& usage_oid) + { + if((usage & check_for) == 0) + return true; + + const std::vector<std::string> constraints = cert.ex_constraints(); + + if(constraints.empty()) + return true; + + return std::binary_search(constraints.begin(), constraints.end(), + usage_oid); + } + +/************************************************* +* Check the usage restrictions * +*************************************************/ +X509_Code usage_check(const X509_Certificate& cert, + X509_Store::Cert_Usage usage) + { + if(usage == X509_Store::ANY) + return VERIFIED; + + if(!check_usage(cert, usage, X509_Store::CRL_SIGNING, CRL_SIGN)) + return CA_CERT_NOT_FOR_CRL_ISSUER; + + if(!check_usage(cert, usage, X509_Store::TLS_SERVER, "PKIX.ServerAuth")) + return INVALID_USAGE; + if(!check_usage(cert, usage, X509_Store::TLS_CLIENT, "PKIX.ClientAuth")) + return INVALID_USAGE; + if(!check_usage(cert, usage, X509_Store::CODE_SIGNING, "PKIX.CodeSigning")) + return INVALID_USAGE; + if(!check_usage(cert, usage, X509_Store::EMAIL_PROTECTION, + "PKIX.EmailProtection")) + return INVALID_USAGE; + if(!check_usage(cert, usage, X509_Store::TIME_STAMPING, + "PKIX.TimeStamping")) + return INVALID_USAGE; + + return VERIFIED; + } + +} + +/************************************************* +* Define equality for revocation data * +*************************************************/ +bool X509_Store::CRL_Data::operator==(const CRL_Data& other) const + { + if(issuer != other.issuer) + return false; + if(serial != other.serial) + return false; + return compare_ids(auth_key_id, other.auth_key_id); + } + +/************************************************* +* Define inequality for revocation data * +*************************************************/ +bool X509_Store::CRL_Data::operator!=(const CRL_Data& other) const + { + return !((*this) == other); + } + +/************************************************* +* Define an ordering for revocation data * +*************************************************/ +bool X509_Store::CRL_Data::operator<(const X509_Store::CRL_Data& other) const + { + if(*this == other) + return false; + + const MemoryVector<byte>& serial1 = serial; + const MemoryVector<byte>& key_id1 = auth_key_id; + const MemoryVector<byte>& serial2 = other.serial; + const MemoryVector<byte>& key_id2 = other.auth_key_id; + + if(compare_ids(key_id1, key_id2) == false) + { + if(std::lexicographical_compare(key_id1.begin(), key_id1.end(), + key_id2.begin(), key_id2.end())) + return true; + + if(std::lexicographical_compare(key_id2.begin(), key_id2.end(), + key_id1.begin(), key_id1.end())) + return false; + } + + if(compare_ids(serial1, serial2) == false) + { + if(std::lexicographical_compare(serial1.begin(), serial1.end(), + serial2.begin(), serial2.end())) + return true; + + if(std::lexicographical_compare(serial2.begin(), serial2.end(), + serial1.begin(), serial1.end())) + return false; + } + + return (issuer < other.issuer); + } + +/************************************************* +* X509_Store Constructor * +*************************************************/ +X509_Store::X509_Store(u32bit slack, u32bit cache_timeout) + { + revoked_info_valid = true; + + validation_cache_timeout = cache_timeout; + time_slack = slack; + } + +/************************************************* +* X509_Store Copy Constructor * +*************************************************/ +X509_Store::X509_Store(const X509_Store& other) + { + certs = other.certs; + revoked = other.revoked; + revoked_info_valid = other.revoked_info_valid; + for(u32bit j = 0; j != other.stores.size(); ++j) + stores[j] = other.stores[j]->clone(); + time_slack = other.time_slack; + } + +/************************************************* +* X509_Store Destructor * +*************************************************/ +X509_Store::~X509_Store() + { + for(u32bit j = 0; j != stores.size(); ++j) + delete stores[j]; + } + +/************************************************* +* Verify a certificate's authenticity * +*************************************************/ +X509_Code X509_Store::validate_cert(const X509_Certificate& cert, + Cert_Usage cert_usage) + { + recompute_revoked_info(); + + std::vector<u32bit> indexes; + X509_Code chaining_result = construct_cert_chain(cert, indexes); + if(chaining_result != VERIFIED) + return chaining_result; + + const u64bit current_time = system_time(); + + s32bit time_check = validity_check(cert.start_time(), cert.end_time(), + current_time, time_slack); + if(time_check < 0) return CERT_NOT_YET_VALID; + else if(time_check > 0) return CERT_HAS_EXPIRED; + + X509_Code sig_check_result = check_sig(cert, certs[indexes[0]]); + if(sig_check_result != VERIFIED) + return sig_check_result; + + if(is_revoked(cert)) + return CERT_IS_REVOKED; + + for(u32bit j = 0; j != indexes.size() - 1; ++j) + { + const X509_Certificate& current_cert = certs[indexes[j]].cert; + + time_check = validity_check(current_cert.start_time(), + current_cert.end_time(), + current_time, + time_slack); + + if(time_check < 0) return CERT_NOT_YET_VALID; + else if(time_check > 0) return CERT_HAS_EXPIRED; + + sig_check_result = check_sig(certs[indexes[j]], certs[indexes[j+1]]); + if(sig_check_result != VERIFIED) + return sig_check_result; + } + + return usage_check(cert, cert_usage); + } + +/************************************************* +* Find this certificate * +*************************************************/ +u32bit X509_Store::find_cert(const X509_DN& subject_dn, + const MemoryRegion<byte>& subject_key_id) const + { + for(u32bit j = 0; j != certs.size(); ++j) + { + const X509_Certificate& this_cert = certs[j].cert; + if(compare_ids(this_cert.subject_key_id(), subject_key_id) && + this_cert.subject_dn() == subject_dn) + return j; + } + return NO_CERT_FOUND; + } + +/************************************************* +* Find the parent of this certificate * +*************************************************/ +u32bit X509_Store::find_parent_of(const X509_Certificate& cert) + { + const X509_DN issuer_dn = cert.issuer_dn(); + const MemoryVector<byte> auth_key_id = cert.authority_key_id(); + + u32bit index = find_cert(issuer_dn, auth_key_id); + + if(index != NO_CERT_FOUND) + return index; + + if(auth_key_id.size()) + { + for(u32bit j = 0; j != stores.size(); ++j) + { + std::vector<X509_Certificate> got = stores[j]->by_SKID(auth_key_id); + + if(got.empty()) + continue; + + for(u32bit k = 0; k != got.size(); ++k) + add_cert(got[k]); + return find_cert(issuer_dn, auth_key_id); + } + } + + return NO_CERT_FOUND; + } + +/************************************************* +* Construct a chain of certificate relationships * +*************************************************/ +X509_Code X509_Store::construct_cert_chain(const X509_Certificate& end_cert, + std::vector<u32bit>& indexes, + bool need_full_chain) + { + u32bit parent = find_parent_of(end_cert); + + while(true) + { + if(parent == NO_CERT_FOUND) + return CERT_ISSUER_NOT_FOUND; + indexes.push_back(parent); + + if(certs[parent].is_verified(validation_cache_timeout)) + if(certs[parent].verify_result() != VERIFIED) + return certs[parent].verify_result(); + + const X509_Certificate& parent_cert = certs[parent].cert; + if(!parent_cert.is_CA_cert()) + return CA_CERT_NOT_FOR_CERT_ISSUER; + + if(certs[parent].is_trusted()) + break; + if(parent_cert.is_self_signed()) + return CANNOT_ESTABLISH_TRUST; + + if(parent_cert.path_limit() < indexes.size() - 1) + return CERT_CHAIN_TOO_LONG; + + parent = find_parent_of(parent_cert); + } + + if(need_full_chain) + return VERIFIED; + + while(true) + { + if(indexes.size() < 2) + break; + + const u32bit cert = indexes.back(); + + if(certs[cert].is_verified(validation_cache_timeout)) + { + if(certs[cert].verify_result() != VERIFIED) + throw Internal_Error("X509_Store::construct_cert_chain"); + indexes.pop_back(); + } + else + break; + } + + const u32bit last_cert = indexes.back(); + const u32bit parent_of_last_cert = find_parent_of(certs[last_cert].cert); + if(parent_of_last_cert == NO_CERT_FOUND) + return CERT_ISSUER_NOT_FOUND; + indexes.push_back(parent_of_last_cert); + + return VERIFIED; + } + +/************************************************* +* Check the CAs signature on a certificate * +*************************************************/ +X509_Code X509_Store::check_sig(const Cert_Info& cert_info, + const Cert_Info& ca_cert_info) const + { + if(cert_info.is_verified(validation_cache_timeout)) + return cert_info.verify_result(); + + const X509_Certificate& cert = cert_info.cert; + const X509_Certificate& ca_cert = ca_cert_info.cert; + + X509_Code verify_code = check_sig(cert, ca_cert.subject_public_key()); + + cert_info.set_result(verify_code); + + return verify_code; + } + +/************************************************* +* Check a CA's signature * +*************************************************/ +X509_Code X509_Store::check_sig(const X509_Object& object, Public_Key* key) + { + std::auto_ptr<Public_Key> pub_key(key); + std::auto_ptr<PK_Verifier> verifier; + + try { + std::vector<std::string> sig_info = + split_on(OIDS::lookup(object.signature_algorithm().oid), '/'); + + if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name()) + return SIGNATURE_ERROR; + + std::string padding = sig_info[1]; + Signature_Format format; + if(key->message_parts() >= 2) format = DER_SEQUENCE; + else format = IEEE_1363; + + if(dynamic_cast<PK_Verifying_with_MR_Key*>(pub_key.get())) + { + PK_Verifying_with_MR_Key* sig_key = + dynamic_cast<PK_Verifying_with_MR_Key*>(pub_key.get()); + verifier.reset(get_pk_verifier(*sig_key, padding, format)); + } + else if(dynamic_cast<PK_Verifying_wo_MR_Key*>(pub_key.get())) + { + PK_Verifying_wo_MR_Key* sig_key = + dynamic_cast<PK_Verifying_wo_MR_Key*>(pub_key.get()); + verifier.reset(get_pk_verifier(*sig_key, padding, format)); + } + else + return CA_CERT_CANNOT_SIGN; + + bool valid = verifier->verify_message(object.tbs_data(), + object.signature()); + + if(valid) + return VERIFIED; + else + return SIGNATURE_ERROR; + } + catch(Decoding_Error) { return CERT_FORMAT_ERROR; } + catch(Exception) {} + + return UNKNOWN_X509_ERROR; + } + +/************************************************* +* Recompute the revocation status of the certs * +*************************************************/ +void X509_Store::recompute_revoked_info() const + { + if(revoked_info_valid) + return; + + for(u32bit j = 0; j != certs.size(); ++j) + { + if((certs[j].is_verified(validation_cache_timeout)) && + (certs[j].verify_result() != VERIFIED)) + continue; + + if(is_revoked(certs[j].cert)) + certs[j].set_result(CERT_IS_REVOKED); + } + + revoked_info_valid = true; + } + +/************************************************* +* Check if a certificate is revoked * +*************************************************/ +bool X509_Store::is_revoked(const X509_Certificate& cert) const + { + CRL_Data revoked_info; + revoked_info.issuer = cert.issuer_dn(); + revoked_info.serial = cert.serial_number(); + revoked_info.auth_key_id = cert.authority_key_id(); + + if(std::binary_search(revoked.begin(), revoked.end(), revoked_info)) + return true; + return false; + } + +/************************************************* +* Retrieve all the certificates in the store * +*************************************************/ +std::vector<X509_Certificate> +X509_Store::get_certs(const Search_Func& search) const + { + std::vector<X509_Certificate> found_certs; + for(u32bit j = 0; j != certs.size(); ++j) + { + if(search.match(certs[j].cert)) + found_certs.push_back(certs[j].cert); + } + return found_certs; + } + +/************************************************* +* Construct a path back to a root for this cert * +*************************************************/ +std::vector<X509_Certificate> +X509_Store::get_cert_chain(const X509_Certificate& cert) + { + std::vector<X509_Certificate> result; + std::vector<u32bit> indexes; + X509_Code chaining_result = construct_cert_chain(cert, indexes, true); + + if(chaining_result != VERIFIED) + throw Invalid_State("X509_Store::get_cert_chain: Can't construct chain"); + + for(u32bit j = 0; j != indexes.size(); ++j) + result.push_back(certs[indexes[j]].cert); + return result; + } + +/************************************************* +* Add a certificate store to the list of stores * +*************************************************/ +void X509_Store::add_new_certstore(Certificate_Store* certstore) + { + stores.push_back(certstore); + } + +/************************************************* +* Add a certificate to the store * +*************************************************/ +void X509_Store::add_cert(const X509_Certificate& cert, bool trusted) + { + if(trusted && !cert.is_self_signed()) + throw Invalid_Argument("X509_Store: Trusted certs must be self-signed"); + + if(find_cert(cert.subject_dn(), cert.subject_key_id()) == NO_CERT_FOUND) + { + revoked_info_valid = false; + Cert_Info info(cert, trusted); + certs.push_back(info); + } + else if(trusted) + { + for(u32bit j = 0; j != certs.size(); ++j) + { + const X509_Certificate& this_cert = certs[j].cert; + if(this_cert == cert) + certs[j].trusted = trusted; + } + } + } + +/************************************************* +* Add one or more certificates to the store * +*************************************************/ +void X509_Store::do_add_certs(DataSource& source, bool trusted) + { + while(!source.end_of_data()) + { + try { + X509_Certificate cert(source); + add_cert(cert, trusted); + } + catch(Decoding_Error) {} + catch(Invalid_Argument) {} + } + } + +/************************************************* +* Add one or more certificates to the store * +*************************************************/ +void X509_Store::add_certs(DataSource& source) + { + do_add_certs(source, false); + } + +/************************************************* +* Add one or more certificates to the store * +*************************************************/ +void X509_Store::add_trusted_certs(DataSource& source) + { + do_add_certs(source, true); + } + +/************************************************* +* Add one or more certificates to the store * +*************************************************/ +X509_Code X509_Store::add_crl(const X509_CRL& crl) + { + s32bit time_check = validity_check(crl.this_update(), crl.next_update(), + system_time(), time_slack); + + if(time_check < 0) return CRL_NOT_YET_VALID; + else if(time_check > 0) return CRL_HAS_EXPIRED; + + u32bit cert_index = NO_CERT_FOUND; + + for(u32bit j = 0; j != certs.size(); ++j) + { + const X509_Certificate& this_cert = certs[j].cert; + if(compare_ids(this_cert.subject_key_id(), crl.authority_key_id())) + { + if(this_cert.subject_dn() == crl.issuer_dn()) + cert_index = j; + } + } + + if(cert_index == NO_CERT_FOUND) + return CRL_ISSUER_NOT_FOUND; + + const X509_Certificate& ca_cert = certs[cert_index].cert; + + X509_Code verify_result = validate_cert(ca_cert, CRL_SIGNING); + if(verify_result != VERIFIED) + return verify_result; + + verify_result = check_sig(crl, ca_cert.subject_public_key()); + if(verify_result != VERIFIED) + return verify_result; + + std::vector<CRL_Entry> revoked_certs = crl.get_revoked(); + + for(u32bit j = 0; j != revoked_certs.size(); ++j) + { + CRL_Data revoked_info; + revoked_info.issuer = crl.issuer_dn(); + revoked_info.serial = revoked_certs[j].serial_number(); + revoked_info.auth_key_id = crl.authority_key_id(); + + std::vector<CRL_Data>::iterator p = + std::find(revoked.begin(), revoked.end(), revoked_info); + + if(revoked_certs[j].reason_code() == REMOVE_FROM_CRL) + { + if(p == revoked.end()) continue; + revoked.erase(p); + } + else + { + if(p != revoked.end()) continue; + revoked.push_back(revoked_info); + } + } + + std::sort(revoked.begin(), revoked.end()); + revoked_info_valid = false; + + return VERIFIED; + } + +/************************************************* +* PEM encode the set of certificates * +*************************************************/ +std::string X509_Store::PEM_encode() const + { + std::string cert_store; + for(u32bit j = 0; j != certs.size(); ++j) + cert_store += certs[j].cert.PEM_encode(); + return cert_store; + } + +/************************************************* +* Create a Cert_Info structure * +*************************************************/ +X509_Store::Cert_Info::Cert_Info(const X509_Certificate& c, + bool t) : cert(c), trusted(t) + { + checked = false; + result = UNKNOWN_X509_ERROR; + last_checked = 0; + } + +/************************************************* +* Return the verification results * +*************************************************/ +X509_Code X509_Store::Cert_Info::verify_result() const + { + if(!checked) + throw Invalid_State("Cert_Info::verify_result() called; not checked"); + return result; + } + +/************************************************* +* Set the verification results * +*************************************************/ +void X509_Store::Cert_Info::set_result(X509_Code code) const + { + result = code; + last_checked = system_time(); + checked = true; + } + +/************************************************* +* Check if this certificate can be trusted * +*************************************************/ +bool X509_Store::Cert_Info::is_trusted() const + { + return trusted; + } + +/************************************************* +* Check if this certificate has been verified * +*************************************************/ +bool X509_Store::Cert_Info::is_verified(u32bit timeout) const + { + if(!checked) + return false; + if(result != VERIFIED && result != CERT_NOT_YET_VALID) + return true; + + const u64bit current_time = system_time(); + + if(current_time > last_checked + timeout) + checked = false; + + return checked; + } + +} diff --git a/modules/x509/x509stor.h b/modules/x509/x509stor.h new file mode 100644 index 000000000..3f3f38ea0 --- /dev/null +++ b/modules/x509/x509stor.h @@ -0,0 +1,137 @@ +/************************************************* +* X.509 Certificate Store Header File * +* (C) 1999-2007 Jack Lloyd * +*************************************************/ + +#ifndef BOTAN_X509_CERT_STORE_H__ +#define BOTAN_X509_CERT_STORE_H__ + +#include <botan/x509cert.h> +#include <botan/x509_crl.h> +#include <botan/certstor.h> + +namespace Botan { + +/************************************************* +* X.509 Certificate Validation Result * +*************************************************/ +enum X509_Code { + VERIFIED, + UNKNOWN_X509_ERROR, + CANNOT_ESTABLISH_TRUST, + CERT_CHAIN_TOO_LONG, + SIGNATURE_ERROR, + POLICY_ERROR, + INVALID_USAGE, + + CERT_FORMAT_ERROR, + CERT_ISSUER_NOT_FOUND, + CERT_NOT_YET_VALID, + CERT_HAS_EXPIRED, + CERT_IS_REVOKED, + + CRL_FORMAT_ERROR, + CRL_ISSUER_NOT_FOUND, + CRL_NOT_YET_VALID, + CRL_HAS_EXPIRED, + + CA_CERT_CANNOT_SIGN, + CA_CERT_NOT_FOR_CERT_ISSUER, + CA_CERT_NOT_FOR_CRL_ISSUER +}; + +/************************************************* +* X.509 Certificate Store * +*************************************************/ +class BOTAN_DLL X509_Store + { + public: + class BOTAN_DLL Search_Func + { + public: + virtual bool match(const X509_Certificate&) const = 0; + virtual ~Search_Func() {} + }; + + enum Cert_Usage { + ANY = 0x00, + TLS_SERVER = 0x01, + TLS_CLIENT = 0x02, + CODE_SIGNING = 0x04, + EMAIL_PROTECTION = 0x08, + TIME_STAMPING = 0x10, + CRL_SIGNING = 0x20 + }; + + X509_Code validate_cert(const X509_Certificate&, Cert_Usage = ANY); + + std::vector<X509_Certificate> get_certs(const Search_Func&) const; + std::vector<X509_Certificate> get_cert_chain(const X509_Certificate&); + std::string PEM_encode() const; + + X509_Code add_crl(const X509_CRL&); + void add_cert(const X509_Certificate&, bool = false); + void add_certs(DataSource&); + void add_trusted_certs(DataSource&); + + void add_new_certstore(Certificate_Store*); + + static X509_Code check_sig(const X509_Object&, Public_Key*); + + X509_Store(u32bit time_slack = 24*60*60, + u32bit cache_results = 30*60); + + X509_Store(const X509_Store&); + ~X509_Store(); + private: + X509_Store& operator=(const X509_Store&) { return (*this); } + + class BOTAN_DLL Cert_Info + { + public: + bool is_verified(u32bit timeout) const; + bool is_trusted() const; + X509_Code verify_result() const; + void set_result(X509_Code) const; + Cert_Info(const X509_Certificate&, bool = false); + + X509_Certificate cert; + bool trusted; + private: + mutable bool checked; + mutable X509_Code result; + mutable u64bit last_checked; + }; + + class BOTAN_DLL CRL_Data + { + public: + X509_DN issuer; + MemoryVector<byte> serial, auth_key_id; + bool operator==(const CRL_Data&) const; + bool operator!=(const CRL_Data&) const; + bool operator<(const CRL_Data&) const; + }; + + u32bit find_cert(const X509_DN&, const MemoryRegion<byte>&) const; + X509_Code check_sig(const Cert_Info&, const Cert_Info&) const; + void recompute_revoked_info() const; + + void do_add_certs(DataSource&, bool); + X509_Code construct_cert_chain(const X509_Certificate&, + std::vector<u32bit>&, bool = false); + + u32bit find_parent_of(const X509_Certificate&); + bool is_revoked(const X509_Certificate&) const; + + static const u32bit NO_CERT_FOUND = 0xFFFFFFFF; + std::vector<Cert_Info> certs; + std::vector<CRL_Data> revoked; + std::vector<Certificate_Store*> stores; + u32bit time_slack, validation_cache_timeout; + mutable bool revoked_info_valid; + }; + +} + +#endif |