Botan  2.15.0
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-2020 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::vector<uint8_t> normalizeAndSerialize(const X509_DN& dn)
122  {
123  std::vector<uint8_t> result_dn;
124  DER_Encoder encoder(result_dn);
125  normalize(dn).encode_into(encoder);
126  return result_dn;
127  }
128 
129 std::string to_string(const CFStringRef cfstring)
130  {
131  const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
132 
133  if(ccstr != nullptr)
134  {
135  return std::string(ccstr);
136  }
137 
138  auto utf16_pairs = CFStringGetLength(cfstring);
139  auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
140 
141  std::vector<char> cstr(max_utf8_bytes, '\0');
142  auto result = CFStringGetCString(cfstring,
143  cstr.data(), cstr.size(),
144  kCFStringEncodingUTF8);
145 
146  return (result) ? std::string(cstr.data()) : std::string();
147  }
148 
149 std::string to_string(const OSStatus status)
150  {
151  scoped_CFType<CFStringRef> eCFString(
152  SecCopyErrorMessageString(status, nullptr));
153  return to_string(eCFString.get());
154  }
155 
156 void check_success(const OSStatus status, const std::string context)
157  {
158  if(errSecSuccess == status)
159  {
160  return;
161  }
162 
163  throw Internal_Error(
164  std::string("failed to " + context + ": " + to_string(status)));
165  }
166 
167 template <typename T>
168 void check_notnull(const T& value, const std::string context)
169  {
170  if(value)
171  {
172  return;
173  }
174 
175  throw Internal_Error(std::string("failed to ") + context);
176  }
177 
178 } // namespace
179 
180 /**
181  * Internal class implementation (i.e. Pimpl) to keep the required platform-
182  * dependent members of Certificate_Store_MacOS contained in this compilation
183  * unit.
184  */
185 class Certificate_Store_MacOS_Impl
186  {
187  private:
188  static constexpr const char* system_roots =
189  "/System/Library/Keychains/SystemRootCertificates.keychain";
190  static constexpr const char* system_keychain =
191  "/Library/Keychains/System.keychain";
192 
193  public:
194  /**
195  * Wraps a list of search query parameters that are later passed into
196  * Apple's certifificate store API. The class provides some convenience
197  * functionality and handles the query paramenter's data lifetime.
198  */
199  class Query
200  {
201  public:
202  Query() = default;
203  ~Query() = default;
204  Query(Query&& other) = default;
205  Query& operator=(Query&& other) = default;
206 
207  Query(const Query& other) = delete;
208  Query& operator=(const Query& other) = delete;
209 
210  public:
211  void addParameter(CFStringRef key, CFTypeRef value)
212  {
213  m_keys.emplace_back(key);
214  m_values.emplace_back(value);
215  }
216 
217  void addParameter(CFStringRef key, std::vector<uint8_t> value)
218  {
219  // TODO C++17: std::vector::emplace_back will return the reference
220  // to the inserted object straight away.
221  m_data_store.emplace_back(std::move(value));
222  const auto& data = m_data_store.back();
223 
224  m_data_refs.emplace_back(CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
225  data.data(),
226  data.size(),
227  kCFAllocatorNull));
228  const auto& data_ref = m_data_refs.back();
229  check_notnull(data_ref, "create CFDataRef of search object failed");
230 
231  addParameter(key, data_ref.get());
232  }
233 
234  /**
235  * Amends the user-provided search query with generic filter rules
236  * for the associated system keychains and transforms it into a
237  * representation that can be passed to the Apple keychain API.
238  */
239  scoped_CFType<CFDictionaryRef> prepare(const CFArrayRef& keychains,
240  const SecPolicyRef& policy)
241  {
242  addParameter(kSecClass, kSecClassCertificate);
243  addParameter(kSecReturnRef, kCFBooleanTrue);
244  addParameter(kSecMatchLimit, kSecMatchLimitAll);
245  addParameter(kSecMatchTrustedOnly, kCFBooleanTrue);
246  addParameter(kSecMatchSearchList, keychains);
247  addParameter(kSecMatchPolicy, policy);
248 
249  BOTAN_ASSERT_EQUAL(m_keys.size(), m_values.size(), "valid key-value pairs");
250 
251  auto query = scoped_CFType<CFDictionaryRef>(CFDictionaryCreate(
252  kCFAllocatorDefault, (const void**)m_keys.data(),
253  (const void**)m_values.data(), m_keys.size(),
254  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
255  check_notnull(query, "create search query");
256 
257  return query;
258  }
259 
260  private:
261  using Data = std::vector<std::vector<uint8_t>>;
262  using DataRefs = std::vector<scoped_CFType<CFDataRef>>;
263  using Keys = std::vector<CFStringRef>;
264  using Values = std::vector<CFTypeRef>;
265 
266  Data m_data_store; //! makes sure that data parameters are kept alive
267  DataRefs m_data_refs; //! keeps track of CFDataRef objects refering into \p m_data_store
268  Keys m_keys; //! ordered list of search parameter keys
269  Values m_values; //! ordered list of search parameter values
270  };
271 
272  public:
273  Certificate_Store_MacOS_Impl() :
274  m_policy(SecPolicyCreateBasicX509()),
275  m_system_roots(nullptr),
276  m_system_chain(nullptr),
277  m_keychains(nullptr)
278  {
279  check_success(SecKeychainOpen(system_roots, &m_system_roots.get()),
280  "open system root certificates");
281  check_success(SecKeychainOpen(system_keychain, &m_system_chain.get()),
282  "open system keychain");
283  check_notnull(m_system_roots, "open system root certificate chain");
284  check_notnull(m_system_chain, "open system certificate chain");
285 
286  // m_keychains is merely a convenience list view into all open keychain
287  // objects. This list is required in prepareQuery().
288  std::array<const void*, 2> keychains{{
289  m_system_roots.get(),
290  m_system_chain.get()
291  }};
292 
293  m_keychains.assign(
294  CFArrayCreate(kCFAllocatorDefault,
295  keychains.data(),
296  keychains.size(),
297  &kCFTypeArrayCallBacks));
298  check_notnull(m_keychains, "initialize keychain array");
299  }
300 
301  std::shared_ptr<const X509_Certificate> findOne(Query query) const
302  {
303  query.addParameter(kSecMatchLimit, kSecMatchLimitOne);
304 
305  scoped_CFType<CFTypeRef> result(nullptr);
306  search(std::move(query), &result.get());
307 
308  return (result) ? readCertificate(result.get()) : nullptr;
309  }
310 
311  std::vector<std::shared_ptr<const X509_Certificate>> findAll(Query query) const
312  {
313  query.addParameter(kSecMatchLimit, kSecMatchLimitAll);
314 
315  scoped_CFType<CFArrayRef> result(nullptr);
316  search(std::move(query), (CFTypeRef*)&result.get());
317 
318  std::vector<std::shared_ptr<const X509_Certificate>> output;
319 
320  if(result)
321  {
322  const auto count = CFArrayGetCount(result.get());
323  BOTAN_ASSERT(count > 0, "certificate result list contains data");
324 
325  for(unsigned int i = 0; i < count; ++i)
326  {
327  auto cert = CFArrayGetValueAtIndex(result.get(), i);
328  output.emplace_back(readCertificate(cert));
329  }
330  }
331 
332  return output;
333  }
334 
335  protected:
336  void search(Query query, CFTypeRef* result) const
337  {
338  scoped_CFType<CFDictionaryRef> fullQuery(query.prepare(keychains(), policy()));
339 
340  auto status = SecItemCopyMatching(fullQuery.get(), result);
341 
342  if(errSecItemNotFound == status)
343  {
344  return; // no matches
345  }
346 
347  check_success(status, "look up certificate");
348  check_notnull(result, "look up certificate (invalid result value)");
349  }
350 
351  /**
352  * Convert a CFTypeRef object into a Botan::X509_Certificate
353  */
354  std::shared_ptr<const X509_Certificate> readCertificate(CFTypeRef object) const
355  {
356  if(!object || CFGetTypeID(object) != SecCertificateGetTypeID())
357  {
358  throw Internal_Error("cannot convert CFTypeRef to SecCertificateRef");
359  }
360 
361  auto cert = static_cast<SecCertificateRef>(const_cast<void*>(object));
362 
363  scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
364  check_notnull(derData, "read extracted certificate");
365 
366  const auto data = CFDataGetBytePtr(derData.get());
367  const auto length = CFDataGetLength(derData.get());
368 
369  DataSource_Memory ds(data, length);
370  return std::make_shared<Botan::X509_Certificate>(ds);
371  }
372 
373  CFArrayRef keychains() const { return m_keychains.get(); }
374  SecPolicyRef policy() const { return m_policy.get(); }
375 
376  private:
377  scoped_CFType<SecPolicyRef> m_policy;
378  scoped_CFType<SecKeychainRef> m_system_roots;
379  scoped_CFType<SecKeychainRef> m_system_chain;
380  scoped_CFType<CFArrayRef> m_keychains;
381  };
382 
383 //
384 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
385 //
386 // Implementation of Botan::Certificate_Store interface ...
387 //
388 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
389 //
390 
392  m_impl(std::make_shared<Certificate_Store_MacOS_Impl>())
393  {
394  }
395 
396 std::vector<X509_DN> Certificate_Store_MacOS::all_subjects() const
397  {
398  // Note: This fetches and parses all certificates in the trust store.
399  // Apple's API provides SecCertificateCopyNormalizedSubjectSequence
400  // which facilitates reading the certificate DN without parsing the
401  // entire certificate via Botan::X509_Certificate. However, this
402  // function applies the same DN "normalization" as stated above.
403  const auto certificates = m_impl->findAll({});
404 
405  std::vector<X509_DN> output;
406  std::transform(certificates.cbegin(), certificates.cend(),
407  std::back_inserter(output),
408  [](const std::shared_ptr<const X509_Certificate> cert)
409  {
410  return cert->subject_dn();
411  });
412 
413  return output;
414  }
415 
416 std::shared_ptr<const X509_Certificate>
418  const std::vector<uint8_t>& key_id) const
419  {
420  Certificate_Store_MacOS_Impl::Query query;
421  query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
422 
423  if(!key_id.empty())
424  {
425  query.addParameter(kSecAttrSubjectKeyID, key_id);
426  }
427 
428  return m_impl->findOne(std::move(query));
429  }
430 
431 std::vector<std::shared_ptr<const X509_Certificate>> Certificate_Store_MacOS::find_all_certs(
432  const X509_DN& subject_dn,
433  const std::vector<uint8_t>& key_id) const
434  {
435  Certificate_Store_MacOS_Impl::Query query;
436  query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
437 
438  if(!key_id.empty())
439  {
440  query.addParameter(kSecAttrSubjectKeyID, key_id);
441  }
442 
443  return m_impl->findAll(std::move(query));
444  }
445 
446 std::shared_ptr<const X509_Certificate>
447 Certificate_Store_MacOS::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const
448  {
449  if(key_hash.size() != 20)
450  {
451  throw Invalid_Argument("Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
452  }
453 
454  Certificate_Store_MacOS_Impl::Query query;
455  query.addParameter(kSecAttrPublicKeyHash, key_hash);
456 
457  return m_impl->findOne(std::move(query));
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 } // namespace Botan
std::shared_ptr< const X509_CRL > find_crl_for(const X509_Certificate &subject) const override
std::shared_ptr< const X509_Certificate > find_cert_by_raw_subject_dn_sha256(const std::vector< uint8_t > &subject_hash) const override
#define transform(B0, B1, B2, B3)
Definition: bigint.h:1142
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