Skip to content

Commit ec092a5

Browse files
authored
Merge pull request #5510 from randombit/jack/certstore-search
Improve search and indexing in Certificate_Store implementations
2 parents 87d9feb + f28dbf4 commit ec092a5

File tree

11 files changed

+88
-43
lines changed

11 files changed

+88
-43
lines changed

src/bogo_shim/bogo_shim.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,7 @@ class Shim_Callbacks final : public Botan::TLS::Callbacks {
15121512

15131513
if(!cert_chain.empty() && cert_chain.front().is_self_signed()) {
15141514
for(auto* const roots : trusted_roots) {
1515-
if(roots->certificate_known(cert_chain.front())) {
1515+
if(roots->contains(cert_chain.front())) {
15161516
shim_log("Trusting self-signed certificate");
15171517
return;
15181518
}

src/lib/x509/certstor.cpp

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
#include <botan/certstor.h>
1010

1111
#include <botan/asn1_time.h>
12+
#include <botan/assert.h>
1213
#include <botan/data_src.h>
13-
#include <botan/hash.h>
1414
#include <botan/pkix_types.h>
1515
#include <botan/internal/filesystem.h>
1616
#include <algorithm>
@@ -19,7 +19,11 @@ namespace Botan {
1919

2020
Certificate_Store::~Certificate_Store() = default;
2121

22-
bool Certificate_Store::certificate_known(const X509_Certificate& searching) const {
22+
bool Certificate_Store::certificate_known(const X509_Certificate& cert) const {
23+
return contains(cert);
24+
}
25+
26+
bool Certificate_Store::contains(const X509_Certificate& searching) const {
2327
for(const auto& cert : find_all_certs(searching.subject_dn(), searching.subject_key_id())) {
2428
if(cert == searching) {
2529
return true;
@@ -46,13 +50,13 @@ std::optional<X509_CRL> Certificate_Store::find_crl_for(const X509_Certificate&
4650
}
4751

4852
void Certificate_Store_In_Memory::add_certificate(const X509_Certificate& cert) {
49-
for(const auto& c : m_certs) {
50-
if(c == cert) {
51-
return;
52-
}
53+
const auto tag = cert.tag();
54+
if(!m_cert_tags.contains(tag)) {
55+
m_cert_tags.insert(tag);
56+
const size_t idx = m_certs.size();
57+
m_certs.push_back(cert);
58+
m_dn_to_indices[cert.subject_dn()].push_back(idx);
5359
}
54-
55-
m_certs.push_back(cert);
5660
}
5761

5862
std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const {
@@ -66,19 +70,23 @@ std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const {
6670

6771
std::optional<X509_Certificate> Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn,
6872
const std::vector<uint8_t>& key_id) const {
69-
for(const auto& cert : m_certs) {
70-
// Only compare key ids if set in both call and in the cert
73+
const auto it = m_dn_to_indices.find(subject_dn);
74+
if(it == m_dn_to_indices.end()) {
75+
return std::nullopt;
76+
}
77+
78+
for(const size_t idx : it->second) {
79+
const auto& cert = m_certs[idx];
80+
BOTAN_ASSERT_NOMSG(cert.subject_dn() == subject_dn);
81+
7182
if(!key_id.empty()) {
7283
const std::vector<uint8_t>& skid = cert.subject_key_id();
73-
7484
if(!skid.empty() && skid != key_id) { // no match
7585
continue;
7686
}
7787
}
7888

79-
if(cert.subject_dn() == subject_dn) {
80-
return cert;
81-
}
89+
return cert;
8290
}
8391

8492
return std::nullopt;
@@ -88,18 +96,23 @@ std::vector<X509_Certificate> Certificate_Store_In_Memory::find_all_certs(const
8896
const std::vector<uint8_t>& key_id) const {
8997
std::vector<X509_Certificate> matches;
9098

91-
for(const auto& cert : m_certs) {
99+
const auto it = m_dn_to_indices.find(subject_dn);
100+
if(it == m_dn_to_indices.end()) {
101+
return matches;
102+
}
103+
104+
for(const size_t idx : it->second) {
105+
const auto& cert = m_certs[idx];
106+
BOTAN_ASSERT_NOMSG(cert.subject_dn() == subject_dn);
107+
92108
if(!key_id.empty()) {
93109
const std::vector<uint8_t>& skid = cert.subject_key_id();
94-
95110
if(!skid.empty() && skid != key_id) { // no match
96111
continue;
97112
}
98113
}
99114

100-
if(cert.subject_dn() == subject_dn) {
101-
matches.push_back(cert);
102-
}
115+
matches.push_back(cert);
103116
}
104117

105118
return matches;
@@ -111,11 +124,8 @@ std::optional<X509_Certificate> Certificate_Store_In_Memory::find_cert_by_pubkey
111124
throw Invalid_Argument("Certificate_Store_In_Memory::find_cert_by_pubkey_sha1 invalid hash");
112125
}
113126

114-
auto hash = HashFunction::create_or_throw("SHA-1");
115-
116127
for(const auto& cert : m_certs) {
117-
hash->update(cert.subject_public_key_bitstring());
118-
if(key_hash == hash->final_stdvec()) { //final_stdvec also clears the hash to initial state
128+
if(key_hash == cert.subject_public_key_bitstring_sha1()) {
119129
return cert;
120130
}
121131
}
@@ -129,11 +139,8 @@ std::optional<X509_Certificate> Certificate_Store_In_Memory::find_cert_by_raw_su
129139
throw Invalid_Argument("Certificate_Store_In_Memory::find_cert_by_raw_subject_dn_sha256 invalid hash");
130140
}
131141

132-
auto hash = HashFunction::create_or_throw("SHA-256");
133-
134142
for(const auto& cert : m_certs) {
135-
hash->update(cert.raw_subject_dn());
136-
if(subject_hash == hash->final_stdvec()) { //final_stdvec also clears the hash to initial state
143+
if(subject_hash == cert.raw_subject_dn_sha256()) {
137144
return cert;
138145
}
139146
}
@@ -190,6 +197,10 @@ std::optional<X509_CRL> Certificate_Store_In_Memory::find_crl_for(const X509_Cer
190197
return {};
191198
}
192199

200+
bool Certificate_Store_In_Memory::contains(const X509_Certificate& cert) const {
201+
return m_cert_tags.contains(cert.tag());
202+
}
203+
193204
Certificate_Store_In_Memory::Certificate_Store_In_Memory(const X509_Certificate& cert) {
194205
add_certificate(cert);
195206
}
@@ -216,8 +227,7 @@ Certificate_Store_In_Memory::Certificate_Store_In_Memory(std::string_view dir) {
216227
DataSource_Stream src(cert_file, true);
217228
while(!src.end_of_data()) {
218229
try {
219-
const X509_Certificate cert(src);
220-
m_certs.push_back(cert);
230+
add_certificate(X509_Certificate(src));
221231
} catch(std::exception&) {
222232
// stop searching for other certificate at first exception
223233
break;

src/lib/x509/certstor.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
#ifndef BOTAN_CERT_STORE_H_
99
#define BOTAN_CERT_STORE_H_
1010

11+
#include <botan/pkix_types.h>
1112
#include <botan/x509_crl.h>
1213
#include <botan/x509cert.h>
14+
#include <map>
1315
#include <optional>
16+
#include <set>
1417

1518
namespace Botan {
1619

@@ -76,6 +79,13 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store /* NOLINT(*-special-member-functi
7679
/**
7780
* @return whether this certificate is contained within the store
7881
* @param cert certificate to be searched
82+
*
83+
* Default implementation uses find_all_certs
84+
*/
85+
virtual bool contains(const X509_Certificate& cert) const;
86+
87+
/**
88+
* Old version of contains
7989
*/
8090
bool certificate_known(const X509_Certificate& cert) const;
8191

@@ -155,9 +165,12 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_Memory final : public Certific
155165
*/
156166
std::optional<X509_CRL> find_crl_for(const X509_Certificate& subject) const override;
157167

168+
bool contains(const X509_Certificate& cert) const override;
169+
158170
private:
159-
// TODO: Add indexing on the DN and key id to avoid linear search
160171
std::vector<X509_Certificate> m_certs;
172+
std::set<X509_Certificate::Tag> m_cert_tags;
173+
std::map<X509_DN, std::vector<size_t>> m_dn_to_indices;
161174
std::vector<X509_CRL> m_crls;
162175
};
163176

src/lib/x509/certstor_flatfile/certstor_flatfile.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,17 @@ Flatfile_Certificate_Store::Flatfile_Certificate_Store(std::string_view file, bo
5555
* but we cannot fix the trust store. So instead just ignore any such certificate.
5656
*/
5757
if(cert.is_self_signed() && cert.is_CA_cert()) {
58-
m_all_subjects.push_back(cert.subject_dn());
59-
m_dn_to_cert[cert.subject_dn()].push_back(cert);
60-
m_pubkey_sha1_to_cert.emplace(cert.subject_public_key_bitstring_sha1(), cert);
61-
m_subject_dn_sha256_to_cert.emplace(cert.raw_subject_dn_sha256(), cert);
62-
m_issuer_dn_to_cert[cert.issuer_dn()].push_back(cert);
58+
const auto tag = cert.tag();
59+
60+
// dedup
61+
if(!m_cert_tags.contains(tag)) {
62+
m_cert_tags.insert(tag);
63+
m_all_subjects.push_back(cert.subject_dn());
64+
m_dn_to_cert[cert.subject_dn()].push_back(cert);
65+
m_pubkey_sha1_to_cert.emplace(cert.subject_public_key_bitstring_sha1(), cert);
66+
m_subject_dn_sha256_to_cert.emplace(cert.raw_subject_dn_sha256(), cert);
67+
m_issuer_dn_to_cert[cert.issuer_dn()].push_back(cert);
68+
}
6369
} else if(!ignore_non_ca) {
6470
throw Invalid_Argument("Flatfile_Certificate_Store received non CA cert " + cert.subject_dn().to_string());
6571
}
@@ -74,6 +80,10 @@ std::vector<X509_DN> Flatfile_Certificate_Store::all_subjects() const {
7480
return m_all_subjects;
7581
}
7682

83+
bool Flatfile_Certificate_Store::contains(const X509_Certificate& cert) const {
84+
return m_cert_tags.contains(cert.tag());
85+
}
86+
7787
std::vector<X509_Certificate> Flatfile_Certificate_Store::find_all_certs(const X509_DN& subject_dn,
7888
const std::vector<uint8_t>& key_id) const {
7989
if(!m_dn_to_cert.contains(subject_dn)) {

src/lib/x509/certstor_flatfile/certstor_flatfile.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <botan/pkix_types.h>
1414

1515
#include <map>
16+
#include <set>
1617
#include <vector>
1718

1819
namespace Botan {
@@ -68,8 +69,11 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific
6869
*/
6970
std::optional<X509_CRL> find_crl_for(const X509_Certificate& subject) const override;
7071

72+
bool contains(const X509_Certificate& cert) const override;
73+
7174
private:
7275
std::vector<X509_DN> m_all_subjects;
76+
std::set<X509_Certificate::Tag> m_cert_tags;
7377
std::map<X509_DN, std::vector<X509_Certificate>> m_dn_to_cert;
7478
std::map<std::vector<uint8_t>, std::optional<X509_Certificate>> m_pubkey_sha1_to_cert;
7579
std::map<std::vector<uint8_t>, std::optional<X509_Certificate>> m_subject_dn_sha256_to_cert;

src/lib/x509/certstor_sql/certstor_sql.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ bool Certificate_Store_In_SQL::insert_cert(const X509_Certificate& cert) {
165165
return true;
166166
}
167167

168+
bool Certificate_Store_In_SQL::contains(const X509_Certificate& cert) const {
169+
auto stmt = m_database->new_statement("SELECT 1 FROM " + m_prefix + "certificates WHERE fingerprint == ?1");
170+
stmt->bind(1, cert.fingerprint("SHA-256"));
171+
return stmt->step();
172+
}
173+
168174
bool Certificate_Store_In_SQL::remove_cert(const X509_Certificate& cert) {
169175
if(!find_cert(cert.subject_dn(), cert.subject_key_id())) {
170176
return false;

src/lib/x509/certstor_sql/certstor_sql.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_SQL : public Certificate_Store
108108
*/
109109
std::optional<X509_CRL> find_crl_for(const X509_Certificate& issuer) const override;
110110

111+
bool contains(const X509_Certificate& cert) const override;
112+
111113
private:
112114
RandomNumberGenerator& m_rng;
113115
std::shared_ptr<SQL_Database> m_database;

src/lib/x509/x509cert.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,14 +634,14 @@ std::unique_ptr<Public_Key> X509_Certificate::load_subject_public_key() const {
634634
return this->subject_public_key();
635635
}
636636

637-
std::vector<uint8_t> X509_Certificate::raw_issuer_dn_sha256() const {
637+
const std::vector<uint8_t>& X509_Certificate::raw_issuer_dn_sha256() const {
638638
if(data().m_issuer_dn_bits_sha256.empty()) {
639639
throw Encoding_Error("X509_Certificate::raw_issuer_dn_sha256 called but SHA-256 disabled in build");
640640
}
641641
return data().m_issuer_dn_bits_sha256;
642642
}
643643

644-
std::vector<uint8_t> X509_Certificate::raw_subject_dn_sha256() const {
644+
const std::vector<uint8_t>& X509_Certificate::raw_subject_dn_sha256() const {
645645
if(data().m_subject_dn_bits_sha256.empty()) {
646646
throw Encoding_Error("X509_Certificate::raw_subject_dn_sha256 called but SHA-256 disabled in build");
647647
}

src/lib/x509/x509cert.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class BOTAN_PUBLIC_API(2, 0) X509_Certificate : public X509_Object {
126126
/**
127127
* SHA-256 of Raw issuer DN
128128
*/
129-
std::vector<uint8_t> raw_issuer_dn_sha256() const;
129+
const std::vector<uint8_t>& raw_issuer_dn_sha256() const;
130130

131131
/**
132132
* Raw subject DN
@@ -136,7 +136,7 @@ class BOTAN_PUBLIC_API(2, 0) X509_Certificate : public X509_Object {
136136
/**
137137
* SHA-256 of Raw subject DN
138138
*/
139-
std::vector<uint8_t> raw_subject_dn_sha256() const;
139+
const std::vector<uint8_t>& raw_subject_dn_sha256() const;
140140

141141
/**
142142
* Get the notBefore of the certificate as X509_Time

src/lib/x509/x509path.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing
272272
//
273273
// 1. Matches a local configuration of OCSP signing authority
274274
// for the certificate in question, or
275-
if(restrictions.trusted_ocsp_responders()->certificate_known(signing_cert)) {
275+
if(restrictions.trusted_ocsp_responders()->contains(signing_cert)) {
276276
return Certificate_Status_Code::OK;
277277
}
278278

@@ -691,7 +691,7 @@ Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vecto
691691

692692
auto cert_in_any_trusted_store = [&](const X509_Certificate& cert) {
693693
return std::ranges::any_of(trusted_certstores,
694-
[&](const Certificate_Store* store) { return store->certificate_known(cert); });
694+
[&](const Certificate_Store* store) { return store->contains(cert); });
695695
};
696696

697697
/*

0 commit comments

Comments
 (0)