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>
19#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
20#include <CoreFoundation/CoreFoundation.h>
21#include <CoreServices/CoreServices.h>
35 explicit scoped_CFType(
T value)
40 scoped_CFType(
const scoped_CFType<T>& rhs) =
delete;
41 scoped_CFType(scoped_CFType<T>&& rhs) :
42 m_value(std::move(rhs.m_value))
44 rhs.m_value =
nullptr;
55 operator bool()
const {
return m_value !=
nullptr; }
59 BOTAN_ASSERT(m_value ==
nullptr,
"scoped_CFType was not set yet");
63 T& get() {
return m_value; }
64 const T& get()
const {
return m_value; }
75X509_DN normalize(
const X509_DN& dn)
79 for(
const auto& rdn : dn.dn_info())
82 const auto oid = rdn.first;
83 auto str = rdn.second;
87 std::string normalized;
88 normalized.reserve(str.value().size());
89 for(
const char c : str.value())
94 normalized.push_back(::toupper(c));
96 else if(!normalized.empty() && normalized.back() !=
' ')
99 normalized.push_back(c);
103 if(normalized.back() ==
' ')
106 normalized.erase(normalized.end() - 1);
109 str = ASN1_String(normalized, str.tagging());
112 result.add_attribute(oid, str);
118std::vector<uint8_t> normalizeAndSerialize(
const X509_DN& dn)
120 std::vector<uint8_t> result_dn;
121 DER_Encoder encoder(result_dn);
122 normalize(dn).encode_into(encoder);
126std::string
to_string(
const CFStringRef cfstring)
128 const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
132 return std::string(ccstr);
135 auto utf16_pairs = CFStringGetLength(cfstring);
136 auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
138 std::vector<char> cstr(max_utf8_bytes,
'\0');
139 auto result = CFStringGetCString(cfstring,
140 cstr.data(), cstr.size(),
141 kCFStringEncodingUTF8);
143 return (result) ? std::string(cstr.data()) : std::string();
146std::string
to_string(
const OSStatus status)
148 scoped_CFType<CFStringRef> eCFString(
149 SecCopyErrorMessageString(status,
nullptr));
153void check_success(
const OSStatus status,
const std::string context)
155 if(errSecSuccess == status)
160 throw Internal_Error(
161 std::string(
"failed to " + context +
": " +
to_string(status)));
165void check_notnull(
const T& value,
const std::string context)
172 throw Internal_Error(std::string(
"failed to ") + context);
182class Certificate_Store_MacOS_Impl
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";
201 Query(Query&& other) =
default;
202 Query& operator=(Query&& other) =
default;
204 Query(
const Query& other) =
delete;
205 Query& operator=(
const Query& other) =
delete;
208 void addParameter(CFStringRef key, CFTypeRef value)
210 m_keys.emplace_back(key);
211 m_values.emplace_back(value);
214 void addParameter(CFStringRef key, std::vector<uint8_t> value)
218 m_data_store.emplace_back(std::move(value));
219 const auto& data = m_data_store.back();
221 m_data_refs.emplace_back(CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
225 const auto& data_ref = m_data_refs.back();
226 check_notnull(data_ref,
"create CFDataRef of search object failed");
228 addParameter(key, data_ref.get());
236 scoped_CFType<CFDictionaryRef> prepare(
const CFArrayRef& keychains,
237 const SecPolicyRef& policy)
239 addParameter(kSecClass, kSecClassCertificate);
240 addParameter(kSecReturnRef, kCFBooleanTrue);
241 addParameter(kSecMatchLimit, kSecMatchLimitAll);
242 addParameter(kSecMatchTrustedOnly, kCFBooleanTrue);
243 addParameter(kSecMatchSearchList, keychains);
244 addParameter(kSecMatchPolicy, policy);
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");
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>;
264 DataRefs m_data_refs;
270 Certificate_Store_MacOS_Impl() :
271 m_policy(SecPolicyCreateBasicX509()),
272 m_system_roots(
nullptr),
273 m_system_chain(
nullptr),
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");
285 std::array<const void*, 2> keychains{{
286 m_system_roots.get(),
291 CFArrayCreate(kCFAllocatorDefault,
294 &kCFTypeArrayCallBacks));
295 check_notnull(m_keychains,
"initialize keychain array");
298 std::shared_ptr<const X509_Certificate> findOne(Query query)
const
300 query.addParameter(kSecMatchLimit, kSecMatchLimitOne);
302 scoped_CFType<CFTypeRef> result(
nullptr);
303 search(std::move(query), &result.get());
305 return (result) ? readCertificate(result.get()) :
nullptr;
308 std::vector<std::shared_ptr<const X509_Certificate>> findAll(Query query)
const
310 query.addParameter(kSecMatchLimit, kSecMatchLimitAll);
312 scoped_CFType<CFArrayRef> result(
nullptr);
313 search(std::move(query), (CFTypeRef*)&result.get());
315 std::vector<std::shared_ptr<const X509_Certificate>> output;
319 const auto count = CFArrayGetCount(result.get());
320 BOTAN_ASSERT(count > 0,
"certificate result list contains data");
322 for(
unsigned int i = 0; i < count; ++i)
324 auto cert = CFArrayGetValueAtIndex(result.get(), i);
325 output.emplace_back(readCertificate(cert));
333 void search(Query query, CFTypeRef* result)
const
335 scoped_CFType<CFDictionaryRef> fullQuery(query.prepare(keychains(), policy()));
337 auto status = SecItemCopyMatching(fullQuery.get(), result);
339 if(errSecItemNotFound == status)
344 check_success(status,
"look up certificate");
345 check_notnull(result,
"look up certificate (invalid result value)");
351 std::shared_ptr<const X509_Certificate> readCertificate(CFTypeRef
object)
const
353 if(!
object || CFGetTypeID(
object) != SecCertificateGetTypeID())
355 throw Internal_Error(
"cannot convert CFTypeRef to SecCertificateRef");
358 auto cert =
static_cast<SecCertificateRef
>(
const_cast<void*
>(object));
360 scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
361 check_notnull(derData,
"read extracted certificate");
363 const auto data = CFDataGetBytePtr(derData.get());
364 const auto length = CFDataGetLength(derData.get());
366 DataSource_Memory ds(data, length);
367 return std::make_shared<Botan::X509_Certificate>(ds);
370 CFArrayRef keychains()
const {
return m_keychains.get(); }
371 SecPolicyRef policy()
const {
return m_policy.get(); }
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;
389 m_impl(
std::make_shared<Certificate_Store_MacOS_Impl>())
400 const auto certificates = m_impl->findAll({});
402 std::vector<X509_DN> output;
404 std::back_inserter(output),
405 [](
const std::shared_ptr<const X509_Certificate> cert)
407 return cert->subject_dn();
413std::shared_ptr<const X509_Certificate>
415 const std::vector<uint8_t>& key_id)
const
417 Certificate_Store_MacOS_Impl::Query query;
418 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
422 query.addParameter(kSecAttrSubjectKeyID, key_id);
425 return m_impl->findOne(std::move(query));
430 const std::vector<uint8_t>& key_id)
const
432 Certificate_Store_MacOS_Impl::Query query;
433 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
437 query.addParameter(kSecAttrSubjectKeyID, key_id);
440 return m_impl->findAll(std::move(query));
443std::shared_ptr<const X509_Certificate>
446 if(key_hash.size() != 20)
448 throw Invalid_Argument(
"Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
451 Certificate_Store_MacOS_Impl::Query query;
452 query.addParameter(kSecAttrPublicKeyHash, key_hash);
454 return m_impl->findOne(std::move(query));
457std::shared_ptr<const X509_Certificate>
461 throw Not_Implemented(
"Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256");
#define BOTAN_UNUSED(...)
#define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made)
#define BOTAN_ASSERT(expr, assertion_made)
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
Certificate_Store_MacOS()
std::string to_string(ErrorType type)
Convert an ErrorType to string.
#define transform(B0, B1, B2, B3)