Botan 2.13.0 Crypto and TLS for C++11

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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