Botan  2.12.1
Crypto and TLS for C++11
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 
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>
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 }
Definition: bigint.h:1135
std::string to_string(ErrorType type)
Convert an ErrorType to string.
Definition: exceptn.cpp:11
#define BOTAN_ASSERT(expr, assertion_made)
Definition: assert.h:55
#define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made)
Definition: assert.h:81
std::shared_ptr< const X509_Certificate > find_cert_by_pubkey_sha1(const std::vector< uint8_t > &key_hash) const override
Definition: alg_id.cpp:13
#define BOTAN_UNUSED(...)
Definition: assert.h:142
std::shared_ptr< const X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
fe T
Definition: ge.cpp:37
std::vector< X509_DN > all_subjects() 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