aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/x509
diff options
context:
space:
mode:
authorFabian Weissberg <[email protected]>2017-11-29 12:29:56 +0100
committerFabian Weissberg <[email protected]>2017-12-20 13:32:51 +0100
commit02e756dba4c1001b790c3496049f40ebfe89539b (patch)
tree30f36cd1faa600dd61f7ffbf6d699d4fefafe127 /src/lib/x509
parent2918801d97ccdad5327320ee29bdc2cf666fb08a (diff)
Fix various x509 path validation bugs + path building with ambiguous DNs
Signed-off-by: Fabian Weissberg <[email protected]>
Diffstat (limited to 'src/lib/x509')
-rw-r--r--src/lib/x509/asn1_alt_name.cpp22
-rw-r--r--src/lib/x509/cert_status.cpp13
-rw-r--r--src/lib/x509/cert_status.h9
-rw-r--r--src/lib/x509/certstor.cpp23
-rw-r--r--src/lib/x509/certstor.h16
-rw-r--r--src/lib/x509/certstor_sql/certstor_sql.cpp37
-rw-r--r--src/lib/x509/certstor_sql/certstor_sql.h7
-rw-r--r--src/lib/x509/x509_crl.cpp30
-rw-r--r--src/lib/x509/x509_crl.h6
-rw-r--r--src/lib/x509/x509_dn.cpp1
-rw-r--r--src/lib/x509/x509_dn_ub.cpp58
-rw-r--r--src/lib/x509/x509_dn_ub.h24
-rw-r--r--src/lib/x509/x509_ext.cpp56
-rw-r--r--src/lib/x509/x509_ext.h38
-rw-r--r--src/lib/x509/x509cert.cpp9
-rw-r--r--src/lib/x509/x509cert.h6
-rw-r--r--src/lib/x509/x509path.cpp358
-rw-r--r--src/lib/x509/x509path.h18
18 files changed, 683 insertions, 48 deletions
diff --git a/src/lib/x509/asn1_alt_name.cpp b/src/lib/x509/asn1_alt_name.cpp
index 6b3eda917..c9f7c780b 100644
--- a/src/lib/x509/asn1_alt_name.cpp
+++ b/src/lib/x509/asn1_alt_name.cpp
@@ -13,6 +13,9 @@
#include <botan/internal/stl_util.h>
#include <botan/parsing.h>
#include <botan/loadstor.h>
+#include <botan/x509_dn.h>
+
+#include <sstream>
namespace Botan {
@@ -131,6 +134,13 @@ void encode_entries(DER_Encoder& encoder,
store_be(ip, ip_buf);
encoder.add_object(tagging, CONTEXT_SPECIFIC, ip_buf, 4);
}
+ else if (type == "DN")
+ {
+ std::stringstream ss(i->second);
+ X509_DN dn;
+ ss >> dn;
+ encoder.encode(dn);
+ }
}
}
@@ -145,6 +155,7 @@ void AlternativeName::encode_into(DER_Encoder& der) const
encode_entries(der, m_alt_info, "RFC822", ASN1_Tag(1));
encode_entries(der, m_alt_info, "DNS", ASN1_Tag(2));
+ encode_entries(der, m_alt_info, "DN", ASN1_Tag(4));
encode_entries(der, m_alt_info, "URI", ASN1_Tag(6));
encode_entries(der, m_alt_info, "IP", ASN1_Tag(7));
@@ -213,6 +224,17 @@ void AlternativeName::decode_from(BER_Decoder& source)
if(tag == 2) add_attribute("DNS", ASN1::to_string(obj));
if(tag == 6) add_attribute("URI", ASN1::to_string(obj));
}
+ else if(tag == 4)
+ {
+ BER_Decoder dec(obj.value);
+ X509_DN dn;
+ std::stringstream ss;
+
+ dec.decode(dn);
+ ss << dn;
+
+ add_attribute("DN", ss.str());
+ }
else if(tag == 7)
{
if(obj.value.size() == 4)
diff --git a/src/lib/x509/cert_status.cpp b/src/lib/x509/cert_status.cpp
index 9561cae57..2c0e74d36 100644
--- a/src/lib/x509/cert_status.cpp
+++ b/src/lib/x509/cert_status.cpp
@@ -22,6 +22,11 @@ const char* to_string(Certificate_Status_Code code)
case Certificate_Status_Code::VALID_CRL_CHECKED:
return "Valid CRL examined";
+ case Certificate_Status_Code::CERT_SERIAL_NEGATIVE:
+ return "Certificate serial number is negative";
+ case Certificate_Status_Code::DN_TOO_LONG:
+ return "Distinguished name too long";
+
case Certificate_Status_Code::NO_REVOCATION_DATA:
return "No revocation data";
case Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK:
@@ -55,6 +60,8 @@ const char* to_string(Certificate_Status_Code code)
case Certificate_Status_Code::POLICY_ERROR:
return "Certificate policy error";
+ case Certificate_Status_Code::DUPLICATE_CERT_POLICY:
+ return "Certificate contains duplicate policy";
case Certificate_Status_Code::INVALID_USAGE:
return "Certificate does not allow the requested usage";
case Certificate_Status_Code::CERT_CHAIN_TOO_LONG:
@@ -63,6 +70,8 @@ const char* to_string(Certificate_Status_Code code)
return "CA certificate not allowed to issue certs";
case Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER:
return "CA certificate not allowed to issue CRLs";
+ case Certificate_Status_Code::NO_MATCHING_CRLDP:
+ return "No CRL with matching distribution point for certificate";
case Certificate_Status_Code::OCSP_CERT_NOT_LISTED:
return "OCSP cert not listed";
case Certificate_Status_Code::OCSP_BAD_STATUS:
@@ -73,6 +82,10 @@ const char* to_string(Certificate_Status_Code code)
return "Certificate does not pass name constraint";
case Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION:
return "Unknown critical extension encountered";
+ case Certificate_Status_Code::DUPLICATE_CERT_EXTENSION:
+ return "Duplicate certificate extension encountered";
+ case Certificate_Status_Code::EXT_IN_V1_V2_CERT:
+ return "Encountered extension in certificate with version < 3";
case Certificate_Status_Code::OCSP_SIGNATURE_ERROR:
return "OCSP signature error";
case Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND:
diff --git a/src/lib/x509/cert_status.h b/src/lib/x509/cert_status.h
index 0dd9f7b84..76dc9252b 100644
--- a/src/lib/x509/cert_status.h
+++ b/src/lib/x509/cert_status.h
@@ -25,12 +25,18 @@ enum class Certificate_Status_Code {
VALID_CRL_CHECKED = 3,
OCSP_NO_HTTP = 4,
+ // Warnings
+ FIRST_WARNING_STATUS = 500,
+ CERT_SERIAL_NEGATIVE = 500,
+ DN_TOO_LONG = 501,
+
// Errors
FIRST_ERROR_STATUS = 1000,
SIGNATURE_METHOD_TOO_WEAK = 1000,
UNTRUSTED_HASH = 1001,
NO_REVOCATION_DATA = 1002,
+ NO_MATCHING_CRLDP = 1003,
// Time problems
CERT_NOT_YET_VALID = 2000,
@@ -62,10 +68,13 @@ enum class Certificate_Status_Code {
// Other problems
CERT_NAME_NOMATCH = 4008,
UNKNOWN_CRITICAL_EXTENSION = 4009,
+ DUPLICATE_CERT_EXTENSION = 4010,
OCSP_SIGNATURE_ERROR = 4501,
OCSP_ISSUER_NOT_FOUND = 4502,
OCSP_RESPONSE_MISSING_KEYUSAGE = 4503,
OCSP_RESPONSE_INVALID = 4504,
+ EXT_IN_V1_V2_CERT = 4505,
+ DUPLICATE_CERT_POLICY = 4506,
// Hard failures
CERT_IS_REVOKED = 5000,
diff --git a/src/lib/x509/certstor.cpp b/src/lib/x509/certstor.cpp
index df4fc3365..23e8185c4 100644
--- a/src/lib/x509/certstor.cpp
+++ b/src/lib/x509/certstor.cpp
@@ -1,6 +1,7 @@
/*
* Certificate Store
* (C) 1999-2010,2013 Jack Lloyd
+* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -64,6 +65,28 @@ Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn,
return nullptr;
}
+std::vector<std::shared_ptr<const X509_Certificate>> Certificate_Store_In_Memory::find_all_certs(
+ const X509_DN& subject_dn,
+ const std::vector<uint8_t>& key_id) const
+ {
+ std::vector<std::shared_ptr<const X509_Certificate>> matches;
+
+ for(const auto& cert : m_certs)
+ {
+ if(key_id.size())
+ {
+ std::vector<uint8_t> skid = cert->subject_key_id();
+
+ if(skid.size() && skid != key_id) // no match
+ continue;
+ }
+
+ if(cert->subject_dn() == subject_dn)
+ matches.push_back(cert);
+ }
+
+ return matches;
+ }
std::shared_ptr<const X509_Certificate>
Certificate_Store_In_Memory::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const
diff --git a/src/lib/x509/certstor.h b/src/lib/x509/certstor.h
index f08e03bae..36d2e4abd 100644
--- a/src/lib/x509/certstor.h
+++ b/src/lib/x509/certstor.h
@@ -31,6 +31,14 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Store
find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const = 0;
/**
+ * Find all certificates with a given Subject DN.
+ * Subject DN and even the key identifier might not be unique.
+ */
+ virtual std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs(
+ const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const = 0;
+
+
+ /**
* Find a certificate by searching for one with a matching SHA-1 hash of
* public key. Used for OCSP.
* @param key_hash SHA-1 hash of the subject's public key
@@ -121,11 +129,19 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Store_In_Memory final : public Certifica
/*
* Find a certificate by Subject DN and (optionally) key identifier
+ * @return the first certificate that matches
*/
std::shared_ptr<const X509_Certificate> find_cert(
const X509_DN& subject_dn,
const std::vector<uint8_t>& key_id) const override;
+ /*
+ * Find all certificates with a given Subject DN.
+ * Subject DN and even the key identifier might not be unique.
+ */
+ std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs(
+ const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override;
+
std::shared_ptr<const X509_Certificate>
find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override;
diff --git a/src/lib/x509/certstor_sql/certstor_sql.cpp b/src/lib/x509/certstor_sql/certstor_sql.cpp
index 6acfed060..d2991a019 100644
--- a/src/lib/x509/certstor_sql/certstor_sql.cpp
+++ b/src/lib/x509/certstor_sql/certstor_sql.cpp
@@ -76,6 +76,40 @@ Certificate_Store_In_SQL::find_cert(const X509_DN& subject_dn, const std::vector
return cert;
}
+std::vector<std::shared_ptr<const X509_Certificate>>
+Certificate_Store_In_SQL::find_all_certs(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const
+ {
+ std::vector<std::shared_ptr<const X509_Certificate>> certs;
+
+ DER_Encoder enc;
+ std::shared_ptr<SQL_Database::Statement> stmt;
+
+ subject_dn.encode_into(enc);
+
+ if(key_id.empty())
+ {
+ stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE subject_dn == ?1");
+ stmt->bind(1,enc.get_contents_unlocked());
+ }
+ else
+ {
+ stmt = m_database->new_statement("SELECT certificate FROM " + m_prefix + "certificates WHERE\
+ subject_dn == ?1 AND (key_id == NULL OR key_id == ?2)");
+ stmt->bind(1,enc.get_contents_unlocked());
+ stmt->bind(2,key_id);
+ }
+
+ std::shared_ptr<const X509_Certificate> cert;
+ while(stmt->step())
+ {
+ auto blob = stmt->get_blob(0);
+ certs.push_back(std::make_shared<X509_Certificate>(
+ std::vector<uint8_t>(blob.first,blob.first + blob.second)));
+ }
+
+ return certs;
+ }
+
std::shared_ptr<const X509_Certificate>
Certificate_Store_In_SQL::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& /*key_hash*/) const
{
@@ -123,9 +157,6 @@ std::vector<X509_DN> Certificate_Store_In_SQL::all_subjects() const
bool Certificate_Store_In_SQL::insert_cert(const X509_Certificate& cert)
{
- if(find_cert(cert.subject_dn(),cert.subject_key_id()))
- return false;
-
DER_Encoder enc;
auto stmt = m_database->new_statement("INSERT OR REPLACE INTO " +
m_prefix + "certificates (\
diff --git a/src/lib/x509/certstor_sql/certstor_sql.h b/src/lib/x509/certstor_sql/certstor_sql.h
index 88e3968bf..fd80eb191 100644
--- a/src/lib/x509/certstor_sql/certstor_sql.h
+++ b/src/lib/x509/certstor_sql/certstor_sql.h
@@ -43,6 +43,13 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Store_In_SQL : public Certificate_Store
std::shared_ptr<const X509_Certificate>
find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override;
+ /*
+ * Find all certificates with a given Subject DN.
+ * Subject DN and even the key identifier might not be unique.
+ */
+ std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs(
+ const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override;
+
std::shared_ptr<const X509_Certificate>
find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override;
diff --git a/src/lib/x509/x509_crl.cpp b/src/lib/x509/x509_crl.cpp
index a739d2f60..c6449baf8 100644
--- a/src/lib/x509/x509_crl.cpp
+++ b/src/lib/x509/x509_crl.cpp
@@ -10,6 +10,8 @@
#include <botan/x509cert.h>
#include <botan/ber_dec.h>
+#include <sstream>
+
namespace Botan {
struct CRL_Data
@@ -23,6 +25,7 @@ struct CRL_Data
// cached values from extensions
size_t m_crl_number = 0;
std::vector<uint8_t> m_auth_key_id;
+ std::string m_issuing_distribution_point;
};
std::string X509_CRL::PEM_label() const
@@ -164,6 +167,26 @@ std::unique_ptr<CRL_Data> decode_crl_body(const std::vector<uint8_t>& body,
tbs_crl.verify_end();
+ // Now cache some fields from the extensions
+ if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Number>())
+ {
+ data->m_crl_number = ext->get_crl_number();
+ }
+ if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>())
+ {
+ data->m_auth_key_id = ext->get_key_id();
+ }
+ if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_Issuing_Distribution_Point>())
+ {
+ std::stringstream ss;
+
+ for(const auto& pair : ext->get_point().contents())
+ {
+ ss << pair.first << ": " << pair.second << " ";
+ }
+ data->m_issuing_distribution_point = ss.str();
+ }
+
return data;
}
@@ -236,4 +259,11 @@ const X509_Time& X509_CRL::next_update() const
return data().m_next_update;
}
+/*
+* Return the CRL's distribution point
+*/
+std::string X509_CRL::crl_issuing_distribution_point() const
+ {
+ return data().m_issuing_distribution_point;
+ }
}
diff --git a/src/lib/x509/x509_crl.h b/src/lib/x509/x509_crl.h
index fb8307d5a..89925aa04 100644
--- a/src/lib/x509/x509_crl.h
+++ b/src/lib/x509/x509_crl.h
@@ -83,6 +83,12 @@ class BOTAN_PUBLIC_API(2,0) X509_CRL final : public X509_Object
const X509_Time& next_update() const;
/**
+ * Get the CRL's distribution point
+ * @return CRL.IssuingDistributionPoint from the CRL's Data_Store
+ */
+ std::string crl_issuing_distribution_point() const;
+
+ /**
* Create an uninitialized CRL object. Any attempts to access
* this object will throw an exception.
*/
diff --git a/src/lib/x509/x509_dn.cpp b/src/lib/x509/x509_dn.cpp
index d07344aae..1561a10f9 100644
--- a/src/lib/x509/x509_dn.cpp
+++ b/src/lib/x509/x509_dn.cpp
@@ -11,6 +11,7 @@
#include <botan/parsing.h>
#include <botan/internal/stl_util.h>
#include <botan/oids.h>
+#include <botan/x509_dn_ub.h>
#include <ostream>
#include <cctype>
diff --git a/src/lib/x509/x509_dn_ub.cpp b/src/lib/x509/x509_dn_ub.cpp
new file mode 100644
index 000000000..20c88d97e
--- /dev/null
+++ b/src/lib/x509/x509_dn_ub.cpp
@@ -0,0 +1,58 @@
+/*
+* DN_UB maps: Upper bounds on the length of DN strings
+*
+* This file was automatically generated by ./src/scripts/oids.py on 2017-12-20
+*
+* All manual edits to this file will be lost. Edit the script
+* then regenerate this source file.
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/asn1_oid.h>
+#include <botan/x509_dn_ub.h>
+#include <map>
+#include <stdint.h>
+
+namespace {
+/**
+ * Upper bounds for the length of distinguished name fields as given in RFC 5280, Appendix A.
+ * Only OIDS recognized by botan are considered, so far.
+ * Maps OID string representations instead of human readable strings in order
+ * to avoid an additional lookup.
+ */
+static const std::map<Botan::OID, size_t> DN_UB =
+ {
+ { Botan::OID("2.5.4.10"), 64 }, // X520.Organization
+ { Botan::OID("2.5.4.11"), 64 }, // X520.OrganizationalUnit
+ { Botan::OID("2.5.4.12"), 64 }, // X520.Title
+ { Botan::OID("2.5.4.3"), 64 }, // X520.CommonName
+ { Botan::OID("2.5.4.4"), 40 }, // X520.Surname
+ { Botan::OID("2.5.4.42"), 32768 }, // X520.GivenName
+ { Botan::OID("2.5.4.43"), 32768 }, // X520.Initials
+ { Botan::OID("2.5.4.44"), 32768 }, // X520.GenerationalQualifier
+ { Botan::OID("2.5.4.46"), 64 }, // X520.DNQualifier
+ { Botan::OID("2.5.4.5"), 64 }, // X520.SerialNumber
+ { Botan::OID("2.5.4.6"), 3 }, // X520.Country
+ { Botan::OID("2.5.4.65"), 128 }, // X520.Pseudonym
+ { Botan::OID("2.5.4.7"), 128 }, // X520.Locality
+ { Botan::OID("2.5.4.8"), 128 } // X520.State
+ };
+}
+
+namespace Botan {
+
+size_t lookup_ub(const OID& oid)
+ {
+ auto ub_entry = DN_UB.find(oid);
+ if(ub_entry != DN_UB.end())
+ {
+ return ub_entry->second;
+ }
+ else
+ {
+ return SIZE_MAX;
+ }
+ }
+}
+
diff --git a/src/lib/x509/x509_dn_ub.h b/src/lib/x509/x509_dn_ub.h
new file mode 100644
index 000000000..b4433eb53
--- /dev/null
+++ b/src/lib/x509/x509_dn_ub.h
@@ -0,0 +1,24 @@
+/*
+* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+#ifndef BOTAN_X509_DN_UB_H_
+#define BOTAN_X509_DN_UB_H_
+
+#include <botan/asn1_oid.h>
+
+namespace Botan {
+
+/**
+* Lookup upper bounds in characters for the length of distinguished name fields
+* as given in RFC 5280, Appendix A.
+*
+* @param oid the oid of the DN to lookup
+* @return the upper bound, or SIZE_MAX if no ub is known to Botan
+*/
+size_t lookup_ub(const OID& oid);
+
+}
+
+#endif
diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp
index 1b13d36e1..d98818a4c 100644
--- a/src/lib/x509/x509_ext.cpp
+++ b/src/lib/x509/x509_ext.cpp
@@ -2,6 +2,7 @@
* X.509 Certificate Extensions
* (C) 1999-2010,2012 Jack Lloyd
* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity
+* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
@@ -15,6 +16,7 @@
#include <botan/hash.h>
#include <botan/internal/bit_ops.h>
#include <algorithm>
+#include <set>
#include <sstream>
namespace Botan {
@@ -71,6 +73,10 @@ Extensions::create_extn_obj(const OID& oid,
{
extn.reset(new Cert_Extension::CRL_Distribution_Points);
}
+ else if(oid == Cert_Extension::CRL_Issuing_Distribution_Point::static_oid())
+ {
+ extn.reset(new Cert_Extension::CRL_Issuing_Distribution_Point);
+ }
else if(oid == Cert_Extension::Certificate_Policies::static_oid())
{
extn.reset(new Cert_Extension::Certificate_Policies);
@@ -708,7 +714,6 @@ void Certificate_Policies::decode_inner(const std::vector<uint8_t>& in)
std::vector<Policy_Information> policies;
BER_Decoder(in).decode_list(policies);
-
m_oids.clear();
for(size_t i = 0; i != policies.size(); ++i)
m_oids.push_back(policies[i].oid());
@@ -723,6 +728,18 @@ void Certificate_Policies::contents_to(Data_Store& info, Data_Store&) const
info.add("X509v3.CertificatePolicies", m_oids[i].as_string());
}
+void Certificate_Policies::validate(const X509_Certificate& subject, const X509_Certificate& issuer,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ std::vector<std::set<Certificate_Status_Code>>& cert_status,
+ size_t pos)
+ {
+ std::set<OID> oid_set(m_oids.begin(), m_oids.end());
+ if(oid_set.size() != m_oids.size())
+ {
+ cert_status.at(pos).insert(Certificate_Status_Code::DUPLICATE_CERT_POLICY);
+ }
+ }
+
std::vector<uint8_t> Authority_Information_Access::encode_inner() const
{
ASN1_String url(m_ocsp_responder, IA5_STRING);
@@ -801,6 +818,7 @@ std::vector<uint8_t> CRL_Number::encode_inner() const
void CRL_Number::decode_inner(const std::vector<uint8_t>& in)
{
BER_Decoder(in).decode(m_crl_number);
+ m_has_value = true;
}
/*
@@ -850,14 +868,19 @@ void CRL_Distribution_Points::decode_inner(const std::vector<uint8_t>& buf)
.decode_list(m_distribution_points)
.verify_end();
+ std::stringstream ss;
+
for(size_t i = 0; i != m_distribution_points.size(); ++i)
{
- auto point = m_distribution_points[i].point().contents();
+ auto contents = m_distribution_points[i].point().contents();
- auto uris = point.equal_range("URI");
- for(auto uri = uris.first; uri != uris.second; ++uri)
- m_crl_distribution_urls.push_back(uri->second);
+ for(const auto& pair : contents)
+ {
+ ss << pair.first << ": " << pair.second << " ";
+ }
}
+
+ m_crl_distribution_urls.push_back(ss.str());
}
void CRL_Distribution_Points::contents_to(Data_Store& subject, Data_Store&) const
@@ -881,6 +904,29 @@ void CRL_Distribution_Points::Distribution_Point::decode_from(class BER_Decoder&
.end_cons().end_cons();
}
+std::vector<uint8_t> CRL_Issuing_Distribution_Point::encode_inner() const
+ {
+ throw Not_Implemented("CRL_Issuing_Distribution_Point encoding");
+ }
+
+void CRL_Issuing_Distribution_Point::decode_inner(const std::vector<uint8_t>& buf)
+ {
+ BER_Decoder(buf).decode(m_distribution_point).verify_end();
+ }
+
+void CRL_Issuing_Distribution_Point::contents_to(Data_Store& info, Data_Store&) const
+ {
+ auto contents = m_distribution_point.point().contents();
+ std::stringstream ss;
+
+ for(const auto& pair : contents)
+ {
+ ss << pair.first << ": " << pair.second << " ";
+ }
+
+ info.add("X509v3.CRLIssuingDistributionPoint", ss.str());
+ }
+
std::vector<uint8_t> Unknown_Extension::encode_inner() const
{
return m_bytes;
diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h
index 1680bd9dd..235496cbd 100644
--- a/src/lib/x509/x509_ext.h
+++ b/src/lib/x509/x509_ext.h
@@ -528,6 +528,10 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Policies final : public Certificate_Exte
static OID static_oid() { return OID("2.5.29.32"); }
OID oid_of() const override { return static_oid(); }
+ void validate(const X509_Certificate& subject, const X509_Certificate& issuer,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ std::vector<std::set<Certificate_Status_Code>>& cert_status,
+ size_t pos) override;
private:
std::string oid_name() const override
{ return "X509v3.CertificatePolicies"; }
@@ -627,6 +631,7 @@ class BOTAN_PUBLIC_API(2,0) CRL_ReasonCode final : public Certificate_Extension
/**
* CRL Distribution Points Extension
+* todo enforce restrictions from RFC 5280 4.2.1.13
*/
class BOTAN_PUBLIC_API(2,0) CRL_Distribution_Points final : public Certificate_Extension
{
@@ -675,6 +680,39 @@ class BOTAN_PUBLIC_API(2,0) CRL_Distribution_Points final : public Certificate_E
};
/**
+* CRL Issuing Distribution Point Extension
+* todo enforce restrictions from RFC 5280 5.2.5
+*/
+class CRL_Issuing_Distribution_Point final : public Certificate_Extension
+ {
+ public:
+ CRL_Issuing_Distribution_Point() = default;
+
+ explicit CRL_Issuing_Distribution_Point(const CRL_Distribution_Points::Distribution_Point& distribution_point) :
+ m_distribution_point(distribution_point) {}
+
+ CRL_Issuing_Distribution_Point* copy() const override
+ { return new CRL_Issuing_Distribution_Point(m_distribution_point); }
+
+ const AlternativeName& get_point() const
+ { return m_distribution_point.point(); }
+
+ static OID static_oid() { return OID("2.5.29.28"); }
+ OID oid_of() const override { return static_oid(); }
+
+ private:
+ std::string oid_name() const override
+ { return "X509v3.CRLIssuingDistributionPoint"; }
+
+ bool should_encode() const override { return true; }
+ std::vector<uint8_t> encode_inner() const override;
+ void decode_inner(const std::vector<uint8_t>&) override;
+ void contents_to(Data_Store&, Data_Store&) const override;
+
+ CRL_Distribution_Points::Distribution_Point m_distribution_point;
+ };
+
+/**
* An unknown X.509 extension
* Will add a failure to the path validation result, if critical
*/
diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp
index b1229679c..5a5521e37 100644
--- a/src/lib/x509/x509cert.cpp
+++ b/src/lib/x509/x509cert.cpp
@@ -25,6 +25,7 @@ struct X509_Certificate_Data
{
size_t m_version = 0;
std::vector<uint8_t> m_serial;
+ bool m_serial_negative;
AlgorithmIdentifier m_sig_algo_inner;
X509_DN m_issuer_dn;
X509_DN m_subject_dn;
@@ -113,6 +114,8 @@ std::unique_ptr<X509_Certificate_Data> parse_x509_cert_body(const X509_Object& o
throw Decoding_Error("Unknown X.509 cert version " + std::to_string(data->m_version));
if(obj.signature_algorithm() != data->m_sig_algo_inner)
throw Decoding_Error("X.509 Certificate had differing algorithm identifers in inner and outer ID fields");
+ // crude method to save the serial's sign; will get lost during decoding, otherwise
+ data->m_serial_negative = serial_bn.is_negative();
// for general sanity convert wire version (0 based) to standards version (v1 .. v3)
data->m_version += 1;
@@ -384,6 +387,12 @@ const std::vector<uint8_t>& X509_Certificate::serial_number() const
return data().m_serial;
}
+bool X509_Certificate::is_serial_negative() const
+ {
+ return data().m_serial_negative;
+ }
+
+
const X509_DN& X509_Certificate::issuer_dn() const
{
return data().m_issuer_dn;
diff --git a/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h
index dc32e70c1..9ee3d4eb8 100644
--- a/src/lib/x509/x509cert.h
+++ b/src/lib/x509/x509cert.h
@@ -186,6 +186,12 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object
const std::vector<uint8_t>& serial_number() const;
/**
+ * Get the serial number's sign
+ * @return 1 iff the serial is negative.
+ */
+ bool is_serial_negative() const;
+
+ /**
* Get the DER encoded AuthorityKeyIdentifier of this certificate.
* @return DER encoded AuthorityKeyIdentifier
*/
diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp
index c2a22e7f4..88fd578b2 100644
--- a/src/lib/x509/x509path.cpp
+++ b/src/lib/x509/x509path.cpp
@@ -1,18 +1,23 @@
/*
* X.509 Certificate Path Validation
* (C) 2010,2011,2012,2014,2016 Jack Lloyd
+* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/x509path.h>
#include <botan/x509_ext.h>
+#include <botan/x509_dn_ub.h>
#include <botan/pk_keys.h>
#include <botan/ocsp.h>
+#include <botan/oids.h>
#include <algorithm>
#include <chrono>
#include <vector>
#include <set>
+#include <string>
+#include <sstream>
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
#include <future>
@@ -81,6 +86,22 @@ PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& ce
status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
}
+ // Check the serial number
+ if(subject->is_serial_negative())
+ {
+ status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
+ }
+
+ // Check the subject's DN components' length
+ for(const auto& dn_pair : subject->subject_dn().get_attributes())
+ {
+ // dn_pair = <OID,str>
+ if(lookup_ub(dn_pair.first) < dn_pair.second.size())
+ {
+ status.insert(Certificate_Status_Code::DN_TOO_LONG);
+ }
+ }
+
// Check all certs for valid time range
if(validation_time < subject->not_before())
status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
@@ -94,34 +115,52 @@ PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& ce
std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key());
- if(!issuer_key)
+ // Check the signature algorithm
+ if(OIDS::lookup(subject->signature_algorithm().oid).empty())
{
- status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
+ status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
}
+ // only perform the following checks if the signature algorithm is known
else
{
- const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key);
+ if(!issuer_key)
+ {
+ status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
+ }
+ else
+ {
+ const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key);
- if(sig_status != Certificate_Status_Code::VERIFIED)
- status.insert(sig_status);
+ if(sig_status != Certificate_Status_Code::VERIFIED)
+ status.insert(sig_status);
- if(issuer_key->estimated_strength() < min_signature_algo_strength)
- status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
- }
+ if(issuer_key->estimated_strength() < min_signature_algo_strength)
+ status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
+ }
- // Ignore untrusted hashes on self-signed roots
- if(trusted_hashes.size() > 0 && !at_self_signed_root)
- {
- if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
- status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
+ // Ignore untrusted hashes on self-signed roots
+ if(trusted_hashes.size() > 0 && !at_self_signed_root)
+ {
+ if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
+ status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
+ }
}
// Check cert extensions
Extensions extensions = subject->v3_extensions();
- for(auto& extension : extensions.extensions())
+ const auto& extensions_vec = extensions.extensions();
+ if(subject->x509_version() < 3 && !extensions_vec.empty())
+ {
+ status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
+ }
+ for(auto& extension : extensions_vec)
{
extension.first->validate(*subject, *issuer, cert_path, cert_status, i);
}
+ if(extensions.extensions().size() != extensions.get_extension_oids().size())
+ {
+ status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
+ }
}
// path len check
@@ -245,6 +284,28 @@ PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert
if(crls[i]->is_revoked(*subject))
status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+
+ std::string dp = subject->crl_distribution_point();
+ if(!dp.empty())
+ {
+ if(dp != crls[i]->crl_issuing_distribution_point())
+ {
+ status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
+ }
+ }
+
+ for(const auto& extension : crls[i]->extensions().extensions())
+ {
+ // is the extension critical and unknown?
+ if(extension.second && OIDS::lookup(extension.first->oid_of()) == "")
+ {
+ /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
+ * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
+ */
+ status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+ }
+ }
+
}
}
@@ -514,6 +575,176 @@ PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>
}
}
+/**
+ * utilities for PKIX::build_all_certificate_paths
+ */
+namespace
+{
+// <certificate, trusted?>
+using cert_maybe_trusted = std::pair<std::shared_ptr<const X509_Certificate>,bool>;
+}
+
+/**
+ * Build all possible certificate paths from the end certificate to self-signed trusted roots.
+ *
+ * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
+ * one of the encountered errors is returned arbitrarily.
+ *
+ * todo add a path building function that returns detailed information on errors encountered while building
+ * the potentially numerous path candidates.
+ *
+ * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
+ * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
+ * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
+ * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
+ * authority key id need not be unique among the certificates used for building the path. In such a case,
+ * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
+ *
+ */
+Certificate_Status_Code
+PKIX::build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths_out,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ const std::shared_ptr<const X509_Certificate>& end_entity,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra)
+ {
+ if(!cert_paths_out.empty())
+ {
+ throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
+ }
+
+ if(end_entity->is_self_signed())
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
+
+ /*
+ * Pile up error messages
+ */
+ std::vector<Certificate_Status_Code> stats;
+
+ Certificate_Store_In_Memory ee_extras;
+ for(size_t i = 0; i != end_entity_extra.size(); ++i)
+ {
+ ee_extras.add_certificate(end_entity_extra[i]);
+ }
+
+ /*
+ * This is an inelegant but functional way of preventing path loops
+ * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
+ * fingerprints in the path. If there is a duplicate, we error out.
+ * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
+ */
+ std::set<std::string> certs_seen;
+
+ // new certs are added and removed from the path during the DFS
+ // it is copied into cert_paths_out when we encounter a trusted root
+ std::vector<std::shared_ptr<const X509_Certificate>> path_so_far;
+
+ // todo can we assume that the end certificate is not trusted?
+ std::vector<cert_maybe_trusted> stack = { {end_entity, false} };
+
+ while(!stack.empty())
+ {
+ // found a deletion marker that guides the DFS, backtracing
+ if(stack.back().first == nullptr)
+ {
+ stack.pop_back();
+ std::string fprint = path_so_far.back()->fingerprint("SHA-256");
+ certs_seen.erase(fprint);
+ path_so_far.pop_back();
+ }
+ // process next cert on the path
+ else
+ {
+ std::shared_ptr<const X509_Certificate> last = stack.back().first;
+ bool trusted = stack.back().second;
+ stack.pop_back();
+
+ // certificate already seen?
+ const std::string fprint = last->fingerprint("SHA-256");
+ if(certs_seen.count(fprint) == 1)
+ {
+ stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
+ // the current path ended in a loop
+ continue;
+ }
+
+ // the current path ends here
+ if(last->is_self_signed())
+ {
+ // found a trust anchor
+ if(trusted)
+ {
+ cert_paths_out.push_back(path_so_far);
+ cert_paths_out.back().push_back(last);
+
+ continue;
+ }
+ // found an untrustworthy root
+ else
+ {
+ stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
+ continue;
+ }
+ }
+
+ const X509_DN issuer_dn = last->issuer_dn();
+ const std::vector<uint8_t> auth_key_id = last->authority_key_id();
+
+ // search for trusted issuers
+ std::vector<std::shared_ptr<const X509_Certificate>> trusted_issuers;
+ for(Certificate_Store* store : trusted_certstores)
+ {
+ auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
+ trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
+ }
+
+ // search the supplemental certs
+ std::vector<std::shared_ptr<const X509_Certificate>> misc_issuers =
+ ee_extras.find_all_certs(issuer_dn, auth_key_id);
+
+ // if we could not find any issuers, the current path ends here
+ if(trusted_issuers.size() + misc_issuers.size() == 0)
+ {
+ stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
+ continue;
+ }
+
+ // push the latest certificate onto the path_so_far
+ path_so_far.push_back(last);
+ certs_seen.emplace(fprint);
+
+ // push a deletion marker on the stack for backtracing later
+ stack.push_back({std::shared_ptr<const X509_Certificate>(nullptr),false});
+
+ for(const auto trusted : trusted_issuers)
+ {
+ stack.push_back({trusted,true});
+ }
+
+ for(const auto misc : misc_issuers)
+ {
+ stack.push_back({misc,false});
+ }
+ }
+ }
+
+ // could not construct any potentially valid path
+ if(cert_paths_out.empty())
+ {
+ if(stats.empty())
+ throw Exception("X509 path building failed for unknown reasons");
+ else
+ // arbitrarily return the first error
+ return stats[0];
+ }
+ else
+ {
+ return Certificate_Status_Code::OK;
+ }
+ }
+
+
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
const CertificatePathStatusCodes& crl,
const CertificatePathStatusCodes& ocsp,
@@ -597,7 +828,9 @@ Path_Validation_Result x509_path_validate(
const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp)
{
if(end_certs.empty())
+ {
throw Invalid_Argument("x509_path_validate called with no subjects");
+ }
std::shared_ptr<const X509_Certificate> end_entity(std::make_shared<const X509_Certificate>(end_certs[0]));
std::vector<std::shared_ptr<const X509_Certificate>> end_entity_extra;
@@ -606,9 +839,8 @@ Path_Validation_Result x509_path_validate(
end_entity_extra.push_back(std::make_shared<const X509_Certificate>(end_certs[i]));
}
- std::vector<std::shared_ptr<const X509_Certificate>> cert_path;
- Certificate_Status_Code path_building_result =
- PKIX::build_certificate_path(cert_path, trusted_roots, end_entity, end_entity_extra);
+ std::vector<std::vector<std::shared_ptr<const X509_Certificate>>> cert_paths;
+ Certificate_Status_Code path_building_result = PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
// If we cannot successfully build a chain to a trusted self-signed root, stop now
if(path_building_result != Certificate_Status_Code::OK)
@@ -616,38 +848,52 @@ Path_Validation_Result x509_path_validate(
return Path_Validation_Result(path_building_result);
}
- CertificatePathStatusCodes status =
- PKIX::check_chain(cert_path, ref_time,
- hostname, usage,
- restrictions.minimum_key_strength(),
- restrictions.trusted_hashes());
+ std::vector<Path_Validation_Result> error_results;
+ // Try validating all the potentially valid paths and return the first one to validate properly
+ for(auto cert_path : cert_paths)
+ {
+ CertificatePathStatusCodes status =
+ PKIX::check_chain(cert_path, ref_time,
+ hostname, usage,
+ restrictions.minimum_key_strength(),
+ restrictions.trusted_hashes());
- CertificatePathStatusCodes crl_status =
- PKIX::check_crl(cert_path, trusted_roots, ref_time);
+ CertificatePathStatusCodes crl_status =
+ PKIX::check_crl(cert_path, trusted_roots, ref_time);
- CertificatePathStatusCodes ocsp_status;
+ CertificatePathStatusCodes ocsp_status;
- if(ocsp_resp.size() > 0)
- {
- ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time);
- }
+ if(ocsp_resp.size() > 0)
+ {
+ ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time);
+ }
- if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0))
- {
+ if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0))
+ {
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
- ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time,
- ocsp_timeout, restrictions.ocsp_all_intermediates());
+ ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time,
+ ocsp_timeout, restrictions.ocsp_all_intermediates());
#else
- ocsp_status.resize(1);
- ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
+ ocsp_status.resize(1);
+ ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
#endif
- }
+ }
- PKIX::merge_revocation_status(status, crl_status, ocsp_status,
- restrictions.require_revocation_information(),
- restrictions.ocsp_all_intermediates());
+ PKIX::merge_revocation_status(status, crl_status, ocsp_status,
+ restrictions.require_revocation_information(),
+ restrictions.ocsp_all_intermediates());
- return Path_Validation_Result(status, std::move(cert_path));
+ Path_Validation_Result pvd(status, std::move(cert_path));
+ if(pvd.successful_validation())
+ {
+ return pvd;
+ }
+ else
+ {
+ error_results.push_back(std::move(pvd));
+ }
+ }
+ return error_results[0];
}
Path_Validation_Result x509_path_validate(
@@ -716,9 +962,31 @@ Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
m_trusted_hashes.insert("SHA-512");
}
+namespace {
+CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses)
+ {
+ CertificatePathStatusCodes warnings;
+ for(const auto& status_set_i : all_statuses)
+ {
+ std::set<Certificate_Status_Code> warning_set_i;
+ for(const auto& code : status_set_i)
+ {
+ if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
+ code < Certificate_Status_Code::FIRST_ERROR_STATUS)
+ {
+ warning_set_i.insert(code);
+ }
+ }
+ warnings.push_back(warning_set_i);
+ }
+ return warnings;
+ }
+}
+
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) :
m_all_status(status),
+ m_warnings(find_warnings(m_all_status)),
m_cert_path(cert_chain),
m_overall(PKIX::overall_status(m_all_status))
{
@@ -749,6 +1017,16 @@ bool Path_Validation_Result::successful_validation() const
result() == Certificate_Status_Code::VALID_CRL_CHECKED);
}
+bool Path_Validation_Result::no_warnings() const
+ {
+ return m_warnings.empty();
+ }
+
+CertificatePathStatusCodes Path_Validation_Result::warnings() const
+ {
+ return m_warnings;
+ }
+
std::string Path_Validation_Result::result_string() const
{
return status_string(result());
diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h
index 6898d0679..12a924873 100644
--- a/src/lib/x509/x509path.h
+++ b/src/lib/x509/x509path.h
@@ -136,6 +136,11 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Result final
bool successful_validation() const;
/**
+ * @return true iff no warnings occured during validation
+ */
+ bool no_warnings() const;
+
+ /**
* @return overall validation result code
*/
Certificate_Status_Code result() const { return m_overall; }
@@ -147,6 +152,11 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Result final
{ return m_all_status; }
/**
+ * @return the subset of status codes that are warnings
+ */
+ CertificatePathStatusCodes warnings() const;
+
+ /**
* @return string representation of the validation result
*/
std::string result_string() const;
@@ -173,6 +183,7 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Result final
private:
CertificatePathStatusCodes m_all_status;
+ CertificatePathStatusCodes m_warnings;
std::vector<std::shared_ptr<const X509_Certificate>> m_cert_path;
Certificate_Status_Code m_overall;
};
@@ -274,6 +285,13 @@ Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate(
*/
namespace PKIX {
+Certificate_Status_Code
+build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ const std::shared_ptr<const X509_Certificate>& end_entity,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra);
+
+
/**
* Build certificate path
* @param cert_path_out output parameter, cert_path will be appended to this vector