diff --git a/CMakeLists.txt b/CMakeLists.txt index e5e2ae3..618fb5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} $<$:ws2_32> $<$:crypt32> $<$:cryptui> + # Needed for API from MacOS Security framework + "$<$,$>:-framework CoreFoundation -framework Security>" # Can't put multiple targets in a single generator expression or it bugs out. $<$:Brotli::common> $<$:Brotli::encoder> diff --git a/httplib.h b/httplib.h index 528c838..8266ec8 100644 --- a/httplib.h +++ b/httplib.h @@ -239,7 +239,10 @@ using socket_t = int; #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "cryptui.lib") #endif -#endif //_WIN32 +#elif defined(__APPLE__) // _WIN32 +#include +#include +#endif // __APPLE__ #include #include @@ -4388,15 +4391,15 @@ inline std::string SHA_512(const std::string &s) { } #endif -#ifdef _WIN32 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store inline bool load_system_certs_on_windows(X509_STORE *store) { auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); - if (!hStore) { return false; } + auto result = false; PCCERT_CONTEXT pContext = NULL; while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr) { @@ -4407,16 +4410,107 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); + result = true; } } CertFreeCertificateContext(pContext); CertCloseStore(hStore, 0); + return result; +} +#elif defined(__APPLE__) +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); return true; } + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_apple(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif #endif +#ifdef _WIN32 class WSInit { public: WSInit() { @@ -7842,11 +7936,14 @@ inline bool SSLClient::load_certs() { ret = false; } } else { + auto loaded = false; #ifdef _WIN32 - detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#else - SSL_CTX_set_default_verify_paths(ctx_); + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(__APPLE__) + loaded = detail::load_system_certs_on_apple(SSL_CTX_get_cert_store(ctx_)); #endif + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } }); diff --git a/meson.build b/meson.build index c879e25..cc81eb1 100644 --- a/meson.build +++ b/meson.build @@ -34,6 +34,9 @@ openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cp if openssl_dep.found() deps += openssl_dep args += '-DCPPHTTPLIB_OPENSSL_SUPPORT' + if host_machine.system() == 'darwin' + deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security']) + endif endif zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib')) diff --git a/test/Makefile b/test/Makefile index 4e72360..cb7605c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -8,6 +8,13 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1 #OPENSSL_DIR = $(PREFIX)/opt/openssl@3 OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto +ifneq ($(OS), Windows_NT) + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S), Darwin) + OPENSSL_SUPPORT += -framework CoreFoundation -framework Security + endif +endif + ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz BROTLI_DIR = $(PREFIX)/opt/brotli