Skip to content

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

Merged
merged 11 commits into from
Feb 17, 2023

Conversation

yerseg
Copy link
Contributor

@yerseg yerseg commented Jan 24, 2023

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.

@yerseg yerseg force-pushed the support_loading_certs_macos branch from b29819c to 3a50ea7 Compare January 24, 2023 16:46
@Tachi107
Copy link
Contributor

Tachi107 commented Feb 13, 2023

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!

@yerseg
Copy link
Contributor Author

yerseg commented Feb 13, 2023

@Tachi107 thank you for review! I added the necessary deps to meson.build as you said

Sergey Kazmin added 2 commits February 13, 2023 18:21
Copy link
Contributor

@Tachi107 Tachi107 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor feedback

@yerseg yerseg requested a review from Tachi107 February 14, 2023 14:41
httplib.h Outdated
@@ -7834,10 +7898,11 @@ inline bool SSLClient::load_certs() {
ret = false;
}
} else {
SSL_CTX_set_default_verify_paths(ctx_);
Copy link
Owner

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.

Copy link
Contributor Author

@yerseg yerseg Feb 14, 2023

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?

Copy link
Contributor Author

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…

Copy link
Owner

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
image
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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Owner

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, done

Copy link
Contributor

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());

@yhirose
Copy link
Owner

yhirose commented Feb 15, 2023

@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 cd test; make? Thanks!

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">
Copy link
Contributor

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>"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed it, thanks

@yerseg
Copy link
Contributor Author

yerseg commented Feb 16, 2023

@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);
Copy link
Owner

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);
Copy link
Owner

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;
Copy link
Owner

@yhirose yhirose Feb 16, 2023

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; }
Copy link
Owner

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; }
Copy link
Owner

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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use auto.

@yerseg
Copy link
Contributor Author

yerseg commented Feb 17, 2023

@yhirose fixed your comments

@yhirose yhirose merged commit 6d963fb into yhirose:master Feb 17, 2023
@yhirose
Copy link
Owner

yhirose commented Feb 17, 2023

@yerseg, It has been merged. Thanks for your fine contribution!

@yerseg yerseg deleted the support_loading_certs_macos branch February 17, 2023 17:35
@yerseg
Copy link
Contributor Author

yerseg commented Feb 17, 2023

@yhirose thank you too!

@yhirose
Copy link
Owner

yhirose commented Mar 10, 2023

@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 CPPHTTPLIB_USE_MAC_OS_SYSTEM_CERTS or somthing, since SSL_CTX_set_default_verify_paths has been working anyway with no problem on MacOS for long time.

Or do you have any solution to reduce the expected user support caused by this? At least we have to mention CoreFoundation and Security need to be linked on README. (On Windows, we embed #pragma comment(lib, "ws2_32.lib") in httplib.h, so that we can force the compiler to link the system library.)

Thanks!

@yerseg
Copy link
Contributor Author

yerseg commented Mar 10, 2023

@yhirose I have been thinking about this problem, and I’m agree that we should make this feature optional.
It will be great in case Unix toolchains provide some stuff like “#pragma comment lib” in MSVC, but we have not.
So I will add the macro in new Pull request soon.

@yhirose
Copy link
Owner

yhirose commented Mar 10, 2023

I added note for macOS on README for now. d1b6162

ExclusiveOrange pushed a commit to ExclusiveOrange/cpp-httplib-exor that referenced this pull request May 2, 2023
* 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants