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