Skip to content
This repository was archived by the owner on Apr 30, 2025. It is now read-only.

Commit 6d963fb

Browse files
yersegSergey Kazmin
andauthored
Support loading system certs from Keychein on MacOS (yhirose#1474)
* Support loading system certs from Keychein on MacOS * review improvements: add deps to meson.build and improve conditional expressions in cmake * fix tabs * fix tabs * review improvements * fix after review * additionally load root certs from the system root keychain * cmake fix * fix * small refactoring * small refactoring --------- Co-authored-by: Sergey Kazmin <[email protected]>
1 parent 88f6245 commit 6d963fb

File tree

4 files changed

+115
-6
lines changed

4 files changed

+115
-6
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
206206
$<$<PLATFORM_ID:Windows>:ws2_32>
207207
$<$<PLATFORM_ID:Windows>:crypt32>
208208
$<$<PLATFORM_ID:Windows>:cryptui>
209+
# Needed for API from MacOS Security framework
210+
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
209211
# Can't put multiple targets in a single generator expression or it bugs out.
210212
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
211213
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>

httplib.h

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,10 @@ using socket_t = int;
239239
#pragma comment(lib, "crypt32.lib")
240240
#pragma comment(lib, "cryptui.lib")
241241
#endif
242-
#endif //_WIN32
242+
#elif defined(__APPLE__) // _WIN32
243+
#include <CoreFoundation/CoreFoundation.h>
244+
#include <Security/Security.h>
245+
#endif // __APPLE__
243246

244247
#include <openssl/err.h>
245248
#include <openssl/evp.h>
@@ -4388,15 +4391,15 @@ inline std::string SHA_512(const std::string &s) {
43884391
}
43894392
#endif
43904393

4391-
#ifdef _WIN32
43924394
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4395+
#ifdef _WIN32
43934396
// NOTE: This code came up with the following stackoverflow post:
43944397
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
43954398
inline bool load_system_certs_on_windows(X509_STORE *store) {
43964399
auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
4397-
43984400
if (!hStore) { return false; }
43994401

4402+
auto result = false;
44004403
PCCERT_CONTEXT pContext = NULL;
44014404
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
44024405
nullptr) {
@@ -4407,16 +4410,107 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
44074410
if (x509) {
44084411
X509_STORE_add_cert(store, x509);
44094412
X509_free(x509);
4413+
result = true;
44104414
}
44114415
}
44124416

44134417
CertFreeCertificateContext(pContext);
44144418
CertCloseStore(hStore, 0);
44154419

4420+
return result;
4421+
}
4422+
#elif defined(__APPLE__)
4423+
template <typename T>
4424+
using CFObjectPtr =
4425+
std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
4426+
4427+
inline void cf_object_ptr_deleter(CFTypeRef obj) {
4428+
if (obj) { CFRelease(obj); }
4429+
}
4430+
4431+
inline bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
4432+
CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
4433+
CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
4434+
kCFBooleanTrue};
4435+
4436+
CFObjectPtr<CFDictionaryRef> query(
4437+
CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
4438+
sizeof(keys) / sizeof(keys[0]),
4439+
&kCFTypeDictionaryKeyCallBacks,
4440+
&kCFTypeDictionaryValueCallBacks),
4441+
cf_object_ptr_deleter);
4442+
4443+
if (!query) { return false; }
4444+
4445+
CFTypeRef security_items = nullptr;
4446+
if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
4447+
CFArrayGetTypeID() != CFGetTypeID(security_items)) {
4448+
return false;
4449+
}
4450+
4451+
certs.reset(reinterpret_cast<CFArrayRef>(security_items));
4452+
return true;
4453+
}
4454+
4455+
inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
4456+
CFArrayRef root_security_items = nullptr;
4457+
if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
4458+
return false;
4459+
}
4460+
4461+
certs.reset(root_security_items);
44164462
return true;
44174463
}
4464+
4465+
inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
4466+
auto result = false;
4467+
for (int i = 0; i < CFArrayGetCount(certs); ++i) {
4468+
const auto cert = reinterpret_cast<const __SecCertificate *>(
4469+
CFArrayGetValueAtIndex(certs, i));
4470+
4471+
if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
4472+
4473+
CFDataRef cert_data = nullptr;
4474+
if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
4475+
errSecSuccess) {
4476+
continue;
4477+
}
4478+
4479+
CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
4480+
4481+
auto encoded_cert = static_cast<const unsigned char *>(
4482+
CFDataGetBytePtr(cert_data_ptr.get()));
4483+
4484+
auto x509 =
4485+
d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
4486+
4487+
if (x509) {
4488+
X509_STORE_add_cert(store, x509);
4489+
X509_free(x509);
4490+
result = true;
4491+
}
4492+
}
4493+
4494+
return result;
4495+
}
4496+
4497+
inline bool load_system_certs_on_apple(X509_STORE *store) {
4498+
auto result = false;
4499+
CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
4500+
if (retrieve_certs_from_keychain(certs) && certs) {
4501+
result = add_certs_to_x509_store(certs.get(), store);
4502+
}
4503+
4504+
if (retrieve_root_certs_from_keychain(certs) && certs) {
4505+
result = add_certs_to_x509_store(certs.get(), store) || result;
4506+
}
4507+
4508+
return result;
4509+
}
4510+
#endif
44184511
#endif
44194512

4513+
#ifdef _WIN32
44204514
class WSInit {
44214515
public:
44224516
WSInit() {
@@ -7842,11 +7936,14 @@ inline bool SSLClient::load_certs() {
78427936
ret = false;
78437937
}
78447938
} else {
7939+
auto loaded = false;
78457940
#ifdef _WIN32
7846-
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
7847-
#else
7848-
SSL_CTX_set_default_verify_paths(ctx_);
7941+
loaded =
7942+
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
7943+
#elif defined(__APPLE__)
7944+
loaded = detail::load_system_certs_on_apple(SSL_CTX_get_cert_store(ctx_));
78497945
#endif
7946+
if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
78507947
}
78517948
});
78527949

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cp
3434
if openssl_dep.found()
3535
deps += openssl_dep
3636
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
37+
if host_machine.system() == 'darwin'
38+
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
39+
endif
3740
endif
3841

3942
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))

test/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ OPENSSL_DIR = $(PREFIX)/opt/[email protected]
88
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
99
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
1010

11+
ifneq ($(OS), Windows_NT)
12+
UNAME_S := $(shell uname -s)
13+
ifeq ($(UNAME_S), Darwin)
14+
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
15+
endif
16+
endif
17+
1118
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
1219

1320
BROTLI_DIR = $(PREFIX)/opt/brotli

0 commit comments

Comments
 (0)