Botan  2.19.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-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 
23 namespace Botan {
24 
25 namespace {
26 
27 /**
28  * Abstract RAII wrapper for CFTypeRef-style object handles
29  * All of those xxxRef types are eventually typedefs to void*
30  */
31 template<typename T>
32 class 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:
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  */
75 X509_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 
118 std::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 
126 std::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 
146 std::string to_string(const OSStatus status)
147  {
148  scoped_CFType<CFStringRef> eCFString(
149  SecCopyErrorMessageString(status, nullptr));
150  return to_string(eCFString.get());
151  }
152 
153 void 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 
164 template <typename T>
165 void 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  */
182 class 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 
393 std::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 
413 std::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 
428 std::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 
443 std::shared_ptr<const X509_Certificate>
444 Certificate_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 
457 std::shared_ptr<const X509_Certificate>
458 Certificate_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 
464 std::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
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:1143
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
T m_value
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