9 #include <botan/build.h>
14 #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
15 #include <CoreFoundation/CoreFoundation.h>
16 #include <CoreServices/CoreServices.h>
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>
38 explicit scoped_CFType(
T value)
43 scoped_CFType(
const scoped_CFType<T>& rhs) =
delete;
44 scoped_CFType(scoped_CFType<T>&& rhs) :
47 rhs.m_value =
nullptr;
58 operator bool()
const {
return m_value !=
nullptr; }
67 const T&
get()
const {
return m_value; }
78 X509_DN normalize(
const X509_DN& dn)
82 for(
const auto& rdn : dn.dn_info())
85 const auto oid = rdn.first;
86 auto str = rdn.second;
90 std::string normalized;
91 normalized.reserve(str.value().size());
92 for(
const char c : str.value())
97 normalized.push_back(::toupper(c));
99 else if(!normalized.empty() && normalized.back() !=
' ')
102 normalized.push_back(c);
106 if(normalized.back() ==
' ')
109 normalized.erase(normalized.end() - 1);
112 str = ASN1_String(normalized, str.tagging());
115 result.add_attribute(oid, str);
121 std::string
to_string(
const CFStringRef cfstring)
123 const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
127 return std::string(ccstr);
130 auto utf16_pairs = CFStringGetLength(cfstring);
131 auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
133 std::vector<char> cstr(max_utf8_bytes,
'\0');
134 auto result = CFStringGetCString(cfstring,
135 cstr.data(), cstr.size(),
136 kCFStringEncodingUTF8);
138 return (result) ? std::string(cstr.data()) : std::string();
141 std::string
to_string(
const OSStatus status)
143 scoped_CFType<CFStringRef> eCFString(
144 SecCopyErrorMessageString(status,
nullptr));
148 void check_success(
const OSStatus status,
const std::string context)
150 if(errSecSuccess == status)
156 std::string(
"failed to " + context +
": " +
to_string(status)));
159 template <
typename T>
160 void check_notnull(
const scoped_CFType<T>& value,
const std::string context)
170 SecCertificateRef to_SecCertificateRef(CFTypeRef
object)
172 if(!
object || CFGetTypeID(
object) != SecCertificateGetTypeID())
174 throw Internal_Error(
"cannot convert CFTypeRef to SecCertificateRef");
177 return static_cast<SecCertificateRef
>(
const_cast<void*
>(object));
186 scoped_CFType<CFDataRef> createCFDataView(
const std::vector<uint8_t>& data)
188 return scoped_CFType<CFDataRef>(
189 CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
198 std::shared_ptr<const X509_Certificate> readCertificate(SecCertificateRef cert)
200 scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
201 check_notnull(derData,
"read extracted certificate");
205 const auto data = CFDataGetBytePtr(derData.get());
206 const auto length = CFDataGetLength(derData.get());
208 DataSource_Memory ds(data, length);
209 return std::make_shared<Botan::X509_Certificate>(ds);
219 class Certificate_Store_MacOS_Impl
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";
228 using Query = std::vector<std::pair<CFStringRef, CFTypeRef>>;
231 Certificate_Store_MacOS_Impl() :
232 m_policy(SecPolicyCreateBasicX509()),
233 m_system_roots(nullptr),
234 m_system_chain(nullptr),
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");
246 std::array<const void*, 2> keychains{{
247 m_system_roots.get(),
252 CFArrayCreate(kCFAllocatorDefault,
255 &kCFTypeArrayCallBacks));
256 check_notnull(m_keychains,
"initialize keychain array");
259 CFArrayRef keychains()
const {
return m_keychains.get(); }
260 SecPolicyRef policy()
const {
return m_policy.get(); }
271 scoped_CFType<CFArrayRef> search(Query query = Query())
const
273 scoped_CFType<CFDictionaryRef> fullQuery(
274 prepareQuery(std::move(query)));
275 check_notnull(fullQuery,
"create search query");
277 scoped_CFType<CFArrayRef> result(
nullptr);
278 auto status = SecItemCopyMatching(fullQuery.get(),
279 (CFTypeRef*)&result.get());
280 if(errSecItemNotFound == status)
282 return scoped_CFType<CFArrayRef>(
nullptr);
285 check_success(status,
"look up certificate");
286 check_notnull(result,
"look up certificate (invalid result value)");
296 scoped_CFType<CFDictionaryRef> prepareQuery(Query pairs)
const
298 std::vector<CFStringRef> keys({kSecClass,
301 kSecMatchTrustedOnly,
304 std::vector<CFTypeRef> values({kSecClassCertificate,
310 keys.reserve(pairs.size() + keys.size());
311 values.reserve(pairs.size() + values.size());
313 for(
const auto& pair : pairs)
315 keys.push_back(pair.first);
316 values.push_back(pair.second);
321 return scoped_CFType<CFDictionaryRef>(CFDictionaryCreate(
322 kCFAllocatorDefault, (
const void**)keys.data(),
323 (
const void**)values.data(), keys.size(),
324 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
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;
336 m_impl(
std::make_shared<Certificate_Store_MacOS_Impl>())
342 scoped_CFType<CFArrayRef> result(m_impl->search());
349 const auto count = CFArrayGetCount(result.get());
350 BOTAN_ASSERT(count > 0,
"subject result list contains data");
352 std::vector<X509_DN> output;
353 output.reserve(count);
354 for(
unsigned int i = 0; i < count; ++i)
360 auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), i));
361 auto cert = readCertificate(cfCert);
362 output.emplace_back(cert->subject_dn());
368 std::shared_ptr<const X509_Certificate>
370 const std::vector<uint8_t>& key_id)
const
379 if(certs.size() != 1)
384 return certs.front();
389 const std::vector<uint8_t>& key_id)
const
391 std::vector<uint8_t> dn_data;
393 normalize(subject_dn).encode_into(encoder);
395 scoped_CFType<CFDataRef> dn_cfdata(createCFDataView(dn_data));
396 check_notnull(dn_cfdata,
"create DN search object");
398 Certificate_Store_MacOS_Impl::Query query_params(
400 {kSecAttrSubject, dn_cfdata.get()}
403 scoped_CFType<CFDataRef> keyid_cfdata(createCFDataView(key_id));
404 check_notnull(keyid_cfdata,
"create key ID search object");
407 query_params.push_back({kSecAttrSubjectKeyID, keyid_cfdata.get()});
410 scoped_CFType<CFArrayRef> result(m_impl->search(std::move(query_params)));
417 const auto count = CFArrayGetCount(result.get());
418 BOTAN_ASSERT(count > 0,
"certificate result list contains data");
420 std::vector<std::shared_ptr<const X509_Certificate>> output;
421 output.reserve(count);
422 for(
unsigned int i = 0; i < count; ++i)
424 auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), i));
425 output.emplace_back(readCertificate(cfCert));
431 std::shared_ptr<const X509_Certificate>
434 if(key_hash.size() != 20)
436 throw Invalid_Argument(
"Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
439 scoped_CFType<CFDataRef> key_hash_cfdata(createCFDataView(key_hash));
440 check_notnull(key_hash_cfdata,
"create key hash search object");
442 scoped_CFType<CFArrayRef> result(m_impl->search(
444 {kSecAttrPublicKeyHash, key_hash_cfdata.get()},
452 const auto count = CFArrayGetCount(result.get());
453 BOTAN_ASSERT(count > 0,
"certificate result list contains an object");
456 auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), 0));
457 return readCertificate(cfCert);
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
464 throw Not_Implemented(
"Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256");
467 std::shared_ptr<const X509_CRL> Certificate_Store_MacOS::find_crl_for(
const X509_Certificate& subject)
const
std::string to_string(ErrorType type)
Convert an ErrorType to string.
#define BOTAN_ASSERT(expr, assertion_made)
Certificate_Store_MacOS()
#define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made)
std::shared_ptr< const X509_Certificate > find_cert_by_pubkey_sha1(const std::vector< uint8_t > &key_hash) const override
#define BOTAN_UNUSED(...)
std::shared_ptr< const X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
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