-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Support loading system certs from Keychein on MacOS #1474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
b29819c
to
3a50ea7
Compare
Could you please also update the meson.build file? It should be enough to add the following under the Brotli checks: if host_machine.system() == 'darwin'
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
endif Thanks! |
…expressions in cmake
@Tachi107 thank you for review! I added the necessary deps to meson.build as you said |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor feedback
httplib.h
Outdated
@@ -7834,10 +7898,11 @@ inline bool SSLClient::load_certs() { | |||
ret = false; | |||
} | |||
} else { | |||
SSL_CTX_set_default_verify_paths(ctx_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call should be in #else
block. Please see the original code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very familiar with openssl, but I've come to the following.
If we fail to load certs from the system storage, then it would be correct to call SSL_CTX_set_default_verify_paths.
Am I wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Windows maybe it didn't matter, but for MacOS it has value at all I guess…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the link to SSL_CTX_set_default_verify_paths
:
https://www.openssl.org/docs/man3.1/man3/SSL_CTX_set_default_verify_paths.html
So if we successfully load the OS certs, we shouldn't call SSL_CTX_set_default_verify_paths
.
You probably could do as below:
auto loaded = false;
#ifdef _WIN32
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_); }
This means, always SSL_CTX_set_default_verify_paths
gets called on linux. As for Windows and MacOS, if the detail::load_system_certs_on_???
fails, then SSL_CTX_set_default_verify_paths
will be called as the last resort function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. But when we first use SSL_CTX_set_default_verify_paths, it means that if something screws up, the system will at least try to find certificates in the default places. As far as I can tell, adding extra certificates using X509_STORE_add_cert after setting the default paths shouldn't cause any issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To put it another way, I believe that calling SSL_CTX_set_default_verify_paths first and then load_system_certs will either result in the same or an even better state than if load_system_certs were called first and then set_default_verify_paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the original code for Windows, we don't have to call SSL_CTX_set_default_verify_paths
before/after load_system_certs_on_windows
. I simply would like to do the same for MacOS since it is absolutely unnecessary to call SSL_CTX_set_default_verify_paths
if load_system_certs_on_apple
succeeds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to set the default path before loading certs, just call it after initializing httplib::SSLClient.
httplib::SSLClient client;
SSL_CTX_set_default_verify_paths(client.ssl_context());
@yerseg I have just approved the change, but the CI test on GitHub Actions reported a number of failures... Could you run the unit tests on your local machine by |
CMakeLists.txt
Outdated
@@ -206,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} | |||
$<$<PLATFORM_ID:Windows>:ws2_32> | |||
$<$<PLATFORM_ID:Windows>:crypt32> | |||
$<$<PLATFORM_ID:Windows>:cryptui> | |||
# Needed for API from MacOS Security framework | |||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:"-framework CoreFoundation -framework Security"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quoting the inside of generator expression is not enough when it contains whitespaces. The whole expression should be quoted.
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed it, thanks
@yhirose It turned out that there is another keychain from which we need to load certificates. Now all the tests pass. |
httplib.h
Outdated
if (!query) { return false; } | ||
|
||
CFTypeRef security_items = nullptr; | ||
OSStatus err = SecItemCopyMatching(query.get(), &security_items); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use auto
.
httplib.h
Outdated
|
||
inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) { | ||
CFArrayRef root_security_items = nullptr; | ||
OSStatus err = SecTrustCopyAnchorCertificates(&root_security_items); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use auto
.
httplib.h
Outdated
} | ||
|
||
inline void add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { | ||
OSStatus err = errSecSuccess; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line can be removed.
httplib.h
Outdated
err = SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data); | ||
CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter); | ||
|
||
if (err != errSecSuccess) { continue; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above 4 lines can be as below:
CFDataRef cert_data = nullptr;
if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != errSecSuccess) { continue; }
CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
httplib.h
Outdated
CFObjectPtr<CFArrayRef> security_items_ptr( | ||
reinterpret_cast<CFArrayRef>(security_items), cf_object_ptr_deleter); | ||
|
||
if (err != errSecSuccess) { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line should be moved above the previous line.
httplib.h
Outdated
@@ -7834,11 +7927,14 @@ inline bool SSLClient::load_certs() { | |||
ret = false; | |||
} | |||
} else { | |||
bool loaded = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use auto
.
@yhirose fixed your comments |
@yerseg, It has been merged. Thanks for your fine contribution! |
@yhirose thank you too! |
@yerseg please see #1522. it seems like a number of users on MacOS will encounter the link problem from now on. I am now thinking to make this feature 'opt in' with Or do you have any solution to reduce the expected user support caused by this? At least we have to mention Thanks! |
@yhirose I have been thinking about this problem, and I’m agree that we should make this feature optional. |
I added note for macOS on README for now. d1b6162 |
* 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]>
Added support for loading certificates from the Keychain on MacOS, similar to Windows.
I have tested it in my project and it works.
I am not sure how to properly add the necessary MacOS SDK frameworks to cmake and make, I will be glad to comments.