9#include <botan/certstor_macos.h>
11#include <botan/assert.h>
12#include <botan/ber_dec.h>
13#include <botan/data_src.h>
14#include <botan/exceptn.h>
15#include <botan/pkix_types.h>
20#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
21#include <CoreFoundation/CoreFoundation.h>
22#include <CoreServices/CoreServices.h>
35 explicit scoped_CFType(T value) : m_value(value) {}
37 scoped_CFType(
const scoped_CFType<T>& rhs) =
delete;
39 scoped_CFType(scoped_CFType<T>&& rhs) : m_value(std::move(rhs.m_value)) { rhs.m_value =
nullptr; }
47 operator bool()
const {
return m_value !=
nullptr; }
49 void assign(T value) {
50 BOTAN_ASSERT(m_value ==
nullptr,
"scoped_CFType was not set yet");
54 T& get() {
return m_value; }
56 const T& get()
const {
return m_value; }
67X509_DN normalize(
const X509_DN& dn) {
70 for(
const auto& [oid, str] : dn.dn_info()) {
72 std::string normalized;
73 normalized.reserve(str.value().size());
75 for(
const char c : str.value()) {
78 const char up = (c >=
'a' && c <= 'z') ? static_cast<char>(c - (
'a' -
'A')) : c;
79 normalized.push_back(up);
80 }
else if(!normalized.empty() && normalized.back() !=
' ') {
82 normalized.push_back(c);
86 if(!normalized.empty() && normalized.back() ==
' ') {
88 normalized.pop_back();
91 result.add_attribute(oid, ASN1_String(normalized, str.tagging()));
93 result.add_attribute(oid, str);
99std::vector<uint8_t> normalizeAndSerialize(
const X509_DN& dn) {
100 return normalize(dn).DER_encode();
103std::string
to_string(
const CFStringRef cfstring) {
104 const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
106 if(ccstr !=
nullptr) {
107 return std::string(ccstr);
110 auto utf16_pairs = CFStringGetLength(cfstring);
111 auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
113 std::vector<char> cstr(max_utf8_bytes,
'\0');
114 auto result = CFStringGetCString(cfstring, cstr.data(), cstr.size(), kCFStringEncodingUTF8);
116 return (result) ? std::string(cstr.data()) : std::string();
119std::string
to_string(
const OSStatus status) {
120 scoped_CFType<CFStringRef> eCFString(SecCopyErrorMessageString(status,
nullptr));
124void check_success(
const OSStatus status,
const std::string context) {
125 if(errSecSuccess == status) {
129 throw Internal_Error(std::string(
"failed to " + context +
": " +
to_string(status)));
133void check_notnull(
const T& value,
const std::string context) {
138 throw Internal_Error(std::string(
"failed to ") + context);
148class Certificate_Store_MacOS_Impl {
150 static constexpr const char* system_roots =
"/System/Library/Keychains/SystemRootCertificates.keychain";
151 static constexpr const char* system_keychain =
"/Library/Keychains/System.keychain";
163 Query(Query&& other) =
default;
164 Query& operator=(Query&& other) =
default;
166 Query(
const Query& other) =
delete;
167 Query& operator=(
const Query& other) =
delete;
170 void addParameter(CFStringRef key, CFTypeRef value) {
171 m_keys.emplace_back(key);
172 m_values.emplace_back(value);
175 void addParameter(CFStringRef key, std::vector<uint8_t> value) {
176 const auto& data = m_data_store.emplace_back(std::move(value));
178 const auto& data_ref = m_data_refs.emplace_back(
179 CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, data.data(), data.size(), kCFAllocatorNull));
180 check_notnull(data_ref,
"create CFDataRef of search object failed");
182 addParameter(key, data_ref.get());
190 scoped_CFType<CFDictionaryRef> prepare(
const CFArrayRef& keychains,
const SecPolicyRef& policy) {
191 addParameter(kSecClass, kSecClassCertificate);
192 addParameter(kSecReturnRef, kCFBooleanTrue);
193 addParameter(kSecMatchLimit, kSecMatchLimitAll);
194 addParameter(kSecMatchTrustedOnly, kCFBooleanTrue);
195 addParameter(kSecMatchSearchList, keychains);
196 addParameter(kSecMatchPolicy, policy);
200 auto query = scoped_CFType<CFDictionaryRef>(CFDictionaryCreate(kCFAllocatorDefault,
201 (
const void**)m_keys.data(),
202 (
const void**)m_values.data(),
204 &kCFTypeDictionaryKeyCallBacks,
205 &kCFTypeDictionaryValueCallBacks));
206 check_notnull(query,
"create search query");
212 using Data = std::vector<std::vector<uint8_t>>;
213 using DataRefs = std::vector<scoped_CFType<CFDataRef>>;
214 using Keys = std::vector<CFStringRef>;
215 using Values = std::vector<CFTypeRef>;
218 DataRefs m_data_refs;
224 Certificate_Store_MacOS_Impl() :
225 m_policy(SecPolicyCreateBasicX509()),
226 m_system_roots(
nullptr),
227 m_system_chain(
nullptr),
228 m_keychains(
nullptr) {
234 check_success(SecKeychainOpen(system_roots, &m_system_roots.get()),
"open system root certificates");
235 check_success(SecKeychainOpen(system_keychain, &m_system_chain.get()),
"open system keychain");
237 check_notnull(m_system_roots,
"open system root certificate chain");
238 check_notnull(m_system_chain,
"open system certificate chain");
242 std::array<const void*, 2> keychains{{m_system_roots.get(), m_system_chain.get()}};
245 CFArrayCreate(kCFAllocatorDefault, keychains.data(), keychains.size(), &kCFTypeArrayCallBacks));
246 check_notnull(m_keychains,
"initialize keychain array");
249 std::optional<X509_Certificate> findOne(Query query)
const {
250 query.addParameter(kSecMatchLimit, kSecMatchLimitOne);
252 scoped_CFType<CFTypeRef> result(
nullptr);
253 search(std::move(query), &result.get());
256 return readCertificate(result.get());
261 std::vector<X509_Certificate> findAll(Query query)
const {
262 query.addParameter(kSecMatchLimit, kSecMatchLimitAll);
264 scoped_CFType<CFArrayRef> result(
nullptr);
265 search(std::move(query), (CFTypeRef*)&result.get());
267 std::vector<X509_Certificate> output;
270 const auto count = CFArrayGetCount(result.get());
271 BOTAN_ASSERT(count > 0,
"certificate result list contains data");
273 for(
unsigned int i = 0; i < count; ++i) {
274 auto cert = CFArrayGetValueAtIndex(result.get(), i);
275 output.emplace_back(readCertificate(cert));
283 void search(Query query, CFTypeRef* result)
const {
284 scoped_CFType<CFDictionaryRef> fullQuery(query.prepare(keychains(), policy()));
286 auto status = SecItemCopyMatching(fullQuery.get(), result);
288 if(errSecItemNotFound == status) {
292 check_success(status,
"look up certificate");
293 check_notnull(result,
"look up certificate (invalid result value)");
299 X509_Certificate readCertificate(CFTypeRef
object)
const {
300 if(!
object || CFGetTypeID(
object) != SecCertificateGetTypeID()) {
301 throw Internal_Error(
"cannot convert CFTypeRef to SecCertificateRef");
304 auto cert =
static_cast<SecCertificateRef
>(
const_cast<void*
>(object));
306 scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
307 check_notnull(derData,
"read extracted certificate");
309 const auto data = CFDataGetBytePtr(derData.get());
310 const auto length = CFDataGetLength(derData.get());
312 DataSource_Memory ds(data, length);
313 return X509_Certificate(ds);
316 CFArrayRef keychains()
const {
return m_keychains.get(); }
318 SecPolicyRef policy()
const {
return m_policy.get(); }
321 scoped_CFType<SecPolicyRef> m_policy;
322 scoped_CFType<SecKeychainRef> m_system_roots;
323 scoped_CFType<SecKeychainRef> m_system_chain;
324 scoped_CFType<CFArrayRef> m_keychains;
343 const auto certificates = m_impl->findAll({});
345 std::vector<X509_DN> output;
346 std::transform(certificates.cbegin(),
348 std::back_inserter(output),
349 [](
const std::optional<X509_Certificate> cert) { return cert->subject_dn(); });
355 const std::vector<uint8_t>& key_id)
const {
356 Certificate_Store_MacOS_Impl::Query query;
357 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
359 if(!key_id.empty()) {
360 query.addParameter(kSecAttrSubjectKeyID, key_id);
363 return m_impl->findOne(std::move(query));
367 const std::vector<uint8_t>& key_id)
const {
368 Certificate_Store_MacOS_Impl::Query query;
369 query.addParameter(kSecAttrSubject, normalizeAndSerialize(subject_dn));
371 if(!key_id.empty()) {
372 query.addParameter(kSecAttrSubjectKeyID, key_id);
375 return m_impl->findAll(std::move(query));
379 const std::vector<uint8_t>& key_hash)
const {
380 if(key_hash.size() != 20) {
381 throw Invalid_Argument(
"Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
384 Certificate_Store_MacOS_Impl::Query query;
385 query.addParameter(kSecAttrPublicKeyHash, key_hash);
387 return m_impl->findOne(std::move(query));
391 const std::vector<uint8_t>& subject_hash)
const {
393 throw Not_Implemented(
"Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256");
397 const X509_DN& issuer_dn, std::span<const uint8_t> serial_number)
const {
398 Certificate_Store_MacOS_Impl::Query query;
405 query.addParameter(kSecAttrIssuer, normalizeAndSerialize(issuer_dn));
411 for(
const auto& cert : m_impl->findAll(std::move(query))) {
412 if(std::ranges::equal(cert.serial_number(), serial_number)) {
#define BOTAN_DIAGNOSTIC_POP
#define BOTAN_DIAGNOSTIC_PUSH
#define BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS
#define BOTAN_ASSERT_EQUAL(expr1, expr2, assertion_made)
#define BOTAN_ASSERT(expr, assertion_made)
std::optional< X509_Certificate > find_cert_by_pubkey_sha1(const std::vector< uint8_t > &key_hash) const override
std::optional< X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
std::vector< X509_Certificate > find_all_certs(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
std::optional< X509_Certificate > find_cert_by_issuer_dn_and_serial_number(const X509_DN &issuer_dn, std::span< const uint8_t > serial_number) const override
std::vector< X509_DN > all_subjects() const override
std::optional< X509_Certificate > find_cert_by_raw_subject_dn_sha256(const std::vector< uint8_t > &subject_hash) const override
Certificate_Store_MacOS()
std::optional< X509_CRL > find_crl_for(const X509_Certificate &subject) const override
std::string to_string(ErrorType type)
Convert an ErrorType to string.