Botan 3.3.0
Crypto and TLS for C&
certstor_macos.cpp
Go to the documentation of this file.
1/*
2* Certificate Store
3* (C) 1999-2019 Jack Lloyd
4* (C) 2019-2020 René Meusel
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/certstor_macos.h>
10
11#include <botan/ber_dec.h>
12#include <botan/data_src.h>
13#include <botan/exceptn.h>
14#include <botan/pkix_types.h>
15
16#include <algorithm>
17#include <array>
18
19#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
20#include <CoreFoundation/CoreFoundation.h>
21#include <CoreServices/CoreServices.h>
22
23namespace Botan {
24
25namespace {
26
27/**
28 * Abstract RAII wrapper for CFTypeRef-style object handles
29 * All of those xxxRef types are eventually typedefs to void*
30 */
31template <typename T>
32class scoped_CFType {
33 public:
34 explicit scoped_CFType(T value) : m_value(value) {}
35
36 scoped_CFType(const scoped_CFType<T>& rhs) = delete;
37
38 scoped_CFType(scoped_CFType<T>&& rhs) : m_value(std::move(rhs.m_value)) { rhs.m_value = nullptr; }
39
40 ~scoped_CFType() {
41 if(m_value) {
42 CFRelease(m_value);
43 }
44 }
45
46 operator bool() const { return m_value != nullptr; }
47
48 void assign(T value) {
49 BOTAN_ASSERT(m_value == nullptr, "scoped_CFType was not set yet");
50 m_value = value;
51 }
52
53 T& get() { return m_value; }
54
55 const T& get() const { return m_value; }
56
57 private:
58 T m_value;
59};
60
61/**
62 * Apple's DN parser "normalizes" ASN1 'PrintableString' into upper-case values
63 * and strips leading, trailing as well as multiple white spaces.
64 * See: opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html
65 */
66X509_DN normalize(const X509_DN& dn) {
67 X509_DN result;
68
69 for(const auto& rdn : dn.dn_info()) {
70 // TODO: C++14 - use std::get<ASN1_String>(), resp. std::get<OID>()
71 const auto oid = rdn.first;
72 auto str = rdn.second;
73
74 if(str.tagging() == ASN1_Type::PrintableString) {
75 std::string normalized;
76 normalized.reserve(str.value().size());
77 for(const char c : str.value()) {
78 if(c != ' ') {
79 // store all 'normal' characters as upper case
80 normalized.push_back(::toupper(c));
81 } else if(!normalized.empty() && normalized.back() != ' ') {
82 // remove leading and squash multiple white spaces
83 normalized.push_back(c);
84 }
85 }
86
87 if(normalized.back() == ' ') {
88 // remove potential remaining single trailing white space char
89 normalized.erase(normalized.end() - 1);
90 }
91
92 str = ASN1_String(normalized, str.tagging());
93 }
94
95 result.add_attribute(oid, str);
96 }
97
98 return result;
99}
100
101std::vector<uint8_t> normalizeAndSerialize(const X509_DN& dn) {
102 return normalize(dn).DER_encode();
103}
104
105std::string to_string(const CFStringRef cfstring) {
106 const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
107
108 if(ccstr != nullptr) {
109 return std::string(ccstr);
110 }
111
112 auto utf16_pairs = CFStringGetLength(cfstring);
113 auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
114
115 std::vector<char> cstr(max_utf8_bytes, '\0');
116 auto result = CFStringGetCString(cfstring, cstr.data(), cstr.size(), kCFStringEncodingUTF8);
117
118 return (result) ? std::string(cstr.data()) : std::string();
119}
120
121std::string to_string(const OSStatus status) {
122 scoped_CFType<CFStringRef> eCFString(SecCopyErrorMessageString(status, nullptr));
123 return to_string(eCFString.get());
124}
125
126void check_success(const OSStatus status, const std::string context) {
127 if(errSecSuccess == status) {
128 return;
129 }
130
131 throw Internal_Error(std::string("failed to " + context + ": " + to_string(status)));
132}
133
134template <typename T>
135void check_notnull(const T& value, const std::string context) {
136 if(value) {
137 return;
138 }
139
140 throw Internal_Error(std::string("failed to ") + context);
141}
142
143} // namespace
144
145/**
146 * Internal class implementation (i.e. Pimpl) to keep the required platform-
147 * dependent members of Certificate_Store_MacOS contained in this compilation
148 * unit.
149 */
150class Certificate_Store_MacOS_Impl {
151 private:
152 static constexpr const char* system_roots = "/System/Library/Keychains/SystemRootCertificates.keychain";
153 static constexpr const char* system_keychain = "/Library/Keychains/System.keychain";
154
155 public:
156 /**
157 * Wraps a list of search query parameters that are later passed into
158 * Apple's certifificate store API. The class provides some convenience
159 * functionality and handles the query paramenter's data lifetime.
160 */
161 class Query {
162 public:
163 Query() = default;
164 ~Query() = default;
165 Query(Query&& other) = default;
166 Query& operator=(Query&& other) = default;
167
168 Query(const Query& other) = delete;
169 Query& operator=(const Query& other) = delete;
170
171 public:
172 void addParameter(CFStringRef key, CFTypeRef value) {
173 m_keys.emplace_back(key);
174 m_values.emplace_back(value);
175 }
176
177 void addParameter(CFStringRef key, std::vector<uint8_t> value) {
178 const auto& data = m_data_store.emplace_back(std::move(value));
179
180 const auto& data_ref = m_data_refs.emplace_back(
181 CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, data.data(), data.size(), kCFAllocatorNull));
182 check_notnull(data_ref, "create CFDataRef of search object failed");
183
184 addParameter(key, data_ref.get());
185 }
186
187 /**
188 * Amends the user-provided search query with generic filter rules
189 * for the associated system keychains and transforms it into a
190 * representation that can be passed to the Apple keychain API.
191 */
192 scoped_CFType<CFDictionaryRef> prepare(const CFArrayRef& keychains, const SecPolicyRef& policy) {
193 addParameter(kSecClass, kSecClassCertificate);
194 addParameter(kSecReturnRef, kCFBooleanTrue);
195 addParameter(kSecMatchLimit, kSecMatchLimitAll);
196 addParameter(kSecMatchTrustedOnly, kCFBooleanTrue);
197 addParameter(kSecMatchSearchList, keychains);
198 addParameter(kSecMatchPolicy, policy);
199
200 BOTAN_ASSERT_EQUAL(m_keys.size(), m_values.size(), "valid key-value pairs");
201
202 auto query = scoped_CFType<CFDictionaryRef>(CFDictionaryCreate(kCFAllocatorDefault,
203 (const void**)m_keys.data(),
204 (const void**)m_values.data(),
205 m_keys.size(),
206 &kCFTypeDictionaryKeyCallBacks,
207 &kCFTypeDictionaryValueCallBacks));
208 check_notnull(query, "create search query");
209
210 return query;
211 }
212
213 private:
214 using Data = std::vector<std::vector<uint8_t>>;
215 using DataRefs = std::vector<scoped_CFType<CFDataRef>>;
216 using Keys = std::vector<CFStringRef>;
217 using Values = std::vector<CFTypeRef>;
218
219 Data m_data_store; //! makes sure that data parameters are kept alive
220 DataRefs m_data_refs; //! keeps track of CFDataRef objects refering into \p m_data_store
221 Keys m_keys; //! ordered list of search parameter keys
222 Values m_values; //! ordered list of search parameter values
223 };
224
225 public:
226 Certificate_Store_MacOS_Impl() :
227 m_policy(SecPolicyCreateBasicX509()),
228 m_system_roots(nullptr),
229 m_system_chain(nullptr),
230 m_keychains(nullptr) {
233 // macOS 12.0 deprecates 'Custom keychain management', though the API still works.
234 // Ideas for a replacement can be found in the discussion of GH #3122:
235 // https://github.com/randombit/botan/pull/3122
236 check_success(SecKeychainOpen(system_roots, &m_system_roots.get()), "open system root certificates");
237 check_success(SecKeychainOpen(system_keychain, &m_system_chain.get()), "open system keychain");
239 check_notnull(m_system_roots, "open system root certificate chain");
240 check_notnull(m_system_chain, "open system certificate chain");
241
242 // m_keychains is merely a convenience list view into all open keychain
243 // objects. This list is required in prepareQuery().
244 std::array<const void*, 2> keychains{{m_system_roots.get(), m_system_chain.get()}};
245
246 m_keychains.assign(
247 CFArrayCreate(kCFAllocatorDefault, keychains.data(), keychains.size(), &kCFTypeArrayCallBacks));
248 check_notnull(m_keychains, "initialize keychain array");
249 }
250
251 std::optional<X509_Certificate> findOne(Query query) const {
252 query.addParameter(kSecMatchLimit, kSecMatchLimitOne);
253
254 scoped_CFType<CFTypeRef> result(nullptr);
255 search(std::move(query), &result.get());
256
257 if(result)
258 return readCertificate(result.get());
259 else
260 return std::nullopt;
261 }
262
263 std::vector<X509_Certificate> findAll(Query query) const {
264 query.addParameter(kSecMatchLimit, kSecMatchLimitAll);
265
266 scoped_CFType<CFArrayRef> result(nullptr);
267 search(std::move(query), (CFTypeRef*)&result.get());
268
269 std::vector<X509_Certificate> output;
270
271 if(result) {
272 const auto count = CFArrayGetCount(result.get());
273 BOTAN_ASSERT(count > 0, "certificate result list contains data");
274
275 for(unsigned int i = 0; i < count; ++i) {
276 auto cert = CFArrayGetValueAtIndex(result.get(), i);
277 output.emplace_back(readCertificate(cert));
278 }
279 }
280
281 return output;
282 }
283
284 protected:
285 void search(Query query, CFTypeRef* result) const {
286 scoped_CFType<CFDictionaryRef> fullQuery(query.prepare(keychains(), policy()));
287
288 auto status = SecItemCopyMatching(fullQuery.get(), result);
289
290 if(errSecItemNotFound == status) {
291 return; // no matches
292 }
293
294 check_success(status, "look up certificate");
295 check_notnull(result, "look up certificate (invalid result value)");
296 }
297
298 /**
299 * Convert a CFTypeRef object into a X509_Certificate
300 */
301 X509_Certificate readCertificate(CFTypeRef object) const {
302 if(!object || CFGetTypeID(object) != SecCertificateGetTypeID()) {
303 throw Internal_Error("cannot convert CFTypeRef to SecCertificateRef");
304 }
305
306 auto cert = static_cast<SecCertificateRef>(const_cast<void*>(object));
307
308 scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
309 check_notnull(derData, "read extracted certificate");
310
311 const auto data = CFDataGetBytePtr(derData.get());
312 const auto length = CFDataGetLength(derData.get());
313
314 DataSource_Memory ds(data, length);
315 return X509_Certificate(ds);
316 }
317
318 CFArrayRef keychains() const { return m_keychains.get(); }
319
320 SecPolicyRef policy() const { return m_policy.get(); }
321
322 private:
323 scoped_CFType<SecPolicyRef> m_policy;
324 scoped_CFType<SecKeychainRef> m_system_roots;
325 scoped_CFType<SecKeychainRef> m_system_chain;
326 scoped_CFType<CFArrayRef> m_keychains;
327};
328
329//
330// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
331//
332// Implementation of Certificate_Store interface ...
333//
334// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
335//
336
337Certificate_Store_MacOS::Certificate_Store_MacOS() : m_impl(std::make_shared<Certificate_Store_MacOS_Impl>()) {}
338
339std::vector<X509_DN> Certificate_Store_MacOS::all_subjects() const {
340 // Note: This fetches and parses all certificates in the trust store.
341 // Apple's API provides SecCertificateCopyNormalizedSubjectSequence
342 // which facilitates reading the certificate DN without parsing the
343 // entire certificate via X509_Certificate. However, this
344 // function applies the same DN "normalization" as stated above.
345 const auto certificates = m_impl->findAll({});
346
347 std::vector<X509_DN> output;
348 std::transform(certificates.cbegin(),
349 certificates.cend(),
350 std::back_inserter(output),
351 [](const std::optional<X509_Certificate> cert) { return cert->subject_dn(); });
352
353 return output;
354}
355
356std::optional<X509_Certificate> Certificate_Store_MacOS::find_cert(const X509_DN& subject_dn,
357 const std::vector<uint8_t>& key_id) const {
358 Certificate_Store_MacOS_Impl::Query query;
359 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
360
361 if(!key_id.empty()) {
362 query.addParameter(kSecAttrSubjectKeyID, key_id);
363 }
364
365 return m_impl->findOne(std::move(query));
366}
367
368std::vector<X509_Certificate> Certificate_Store_MacOS::find_all_certs(const X509_DN& subject_dn,
369 const std::vector<uint8_t>& key_id) const {
370 Certificate_Store_MacOS_Impl::Query query;
371 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
372
373 if(!key_id.empty()) {
374 query.addParameter(kSecAttrSubjectKeyID, key_id);
375 }
376
377 return m_impl->findAll(std::move(query));
378}
379
381 const std::vector<uint8_t>& key_hash) const {
382 if(key_hash.size() != 20) {
383 throw Invalid_Argument("Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
384 }
385
386 Certificate_Store_MacOS_Impl::Query query;
387 query.addParameter(kSecAttrPublicKeyHash, key_hash);
388
389 return m_impl->findOne(std::move(query));
390}
391
393 const std::vector<uint8_t>& subject_hash) const {
394 BOTAN_UNUSED(subject_hash);
395 throw Not_Implemented("Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256");
396}
397
398std::optional<X509_CRL> Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const {
399 BOTAN_UNUSED(subject);
400 return {};
401}
402
403} // namespace Botan
#define BOTAN_UNUSED
Definition assert.h:118
#define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made)
Definition assert.h:68
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:50
std::optional< X509_Certificate > find_cert_by_pubkey_sha1(const std::vector< uint8_t > &key_hash) const override
std::optional< X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
std::vector< X509_Certificate > find_all_certs(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
std::vector< X509_DN > all_subjects() const override
std::optional< X509_Certificate > find_cert_by_raw_subject_dn_sha256(const std::vector< uint8_t > &subject_hash) const override
std::optional< X509_CRL > find_crl_for(const X509_Certificate &subject) const override
#define BOTAN_DIAGNOSTIC_POP
Definition compiler.h:191
#define BOTAN_DIAGNOSTIC_PUSH
Definition compiler.h:188
#define BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS
Definition compiler.h:189
FE_25519 T
Definition ge.cpp:34
std::string to_string(ErrorType type)
Convert an ErrorType to string.
Definition exceptn.cpp:13