diff options
author | Jonathan Reams <jbreams@mongodb.com> | 2016-04-06 15:47:22 -0400 |
---|---|---|
committer | Jonathan Reams <jbreams@mongodb.com> | 2016-04-18 11:14:56 -0400 |
commit | d30472af46b87614e1af655dbb73d6531f20d843 (patch) | |
tree | 6cf3aed95351f492cc16c63dcd509f5d453569bd | |
parent | 21e94cbb8ae4b348b412aef8e46fd774902ee302 (diff) | |
download | mongo-d30472af46b87614e1af655dbb73d6531f20d843.tar.gz |
SERVER-23044 Add system CA keychain support for OSX
(cherry picked from commit b788596cb6a71fb8fff79e60cf538d58693f8b9f)
-rw-r--r-- | SConstruct | 7 | ||||
-rw-r--r-- | jstests/ssl/ssl_with_system_ca.js | 7 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 136 |
3 files changed, 127 insertions, 23 deletions
diff --git a/SConstruct b/SConstruct index 9c423ff9ba0..c93091071eb 100644 --- a/SConstruct +++ b/SConstruct @@ -2266,6 +2266,13 @@ def doConfigure(myenv): sslLibName = "ssleay32" cryptoLibName = "libeay32" + # Used to import system certificate keychains + if conf.env.TargetOSIs('osx'): + conf.env.AppendUnique(FRAMEWORKS=[ + 'CoreFoundation', + 'Security', + ]) + if not conf.CheckLibWithHeader( sslLibName, ["openssl/ssl.h"], diff --git a/jstests/ssl/ssl_with_system_ca.js b/jstests/ssl/ssl_with_system_ca.js index cdb7f71ea82..88f14c62222 100644 --- a/jstests/ssl/ssl_with_system_ca.js +++ b/jstests/ssl/ssl_with_system_ca.js @@ -5,6 +5,11 @@ if (HOST_TYPE == "windows") { runProgram( "certutil.exe", "-addstore", "-user", "-f", "CA", "jstests\\libs\\trusted-ca.pem"); + } else if (HOST_TYPE == "osx") { + runProgram( + "/bin/sh", + "-c", + "/usr/bin/security add-trusted-cert -k ~/Library/Keychains/login.keychain jstests/libs/trusted-ca.pem"); } var testWithCerts = function(serverPem) { @@ -19,7 +24,7 @@ // Authenticate call will return 1 on success, 0 on error. var argv = ['./mongo', '--ssl', '--port', conn.port, '--eval', ('db.runCommand({buildInfo: 1})')]; - if (HOST_TYPE != "windows") { + if (HOST_TYPE == "linux") { // On Linux we override the default path to the system CA store to point to our // "trusted" CA. On Windows, this CA will have been added to the user's trusted CA list argv.unshift("env", "SSL_CERT_FILE=jstests/libs/trusted-ca.pem"); diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 9ceb7533866..4432788ef3b 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -58,12 +58,13 @@ #ifdef MONGO_CONFIG_SSL #include <openssl/evp.h> -#include <openssl/x509v3.h> - -#ifdef _WIN32 #include <openssl/x509_vfy.h> +#include <openssl/x509v3.h> +#if defined(_WIN32) #include <openssl/pkcs7.h> #include <wincrypt.h> +#elif defined(__APPLE__) +#include <Security/Security.h> #endif #endif @@ -812,7 +813,18 @@ Status SSLManager::_setupCA(SSL_CTX* context, const std::string& caFile) { return Status::OK(); } -#ifdef _WIN32 +inline Status checkX509_STORE_error() { + const auto errCode = ERR_peek_last_error(); + if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || + ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Error adding certificate to X509 store: " + << ERR_reason_error_string(errCode)}; + } + return Status::OK(); +} + +#if defined(_WIN32) // This imports the certificates in a given Windows certificate store into an X509_STORE for // openssl to use during certificate validation. Status importCertStoreToX509_STORE(LPWSTR storeName, DWORD storeLocation, X509_STORE* verifyStore) { @@ -878,26 +890,103 @@ Status importCertStoreToX509_STORE(LPWSTR storeName, DWORD storeLocation, X509_S STACK_OF(X509)* systemCACerts = p7->d.sign->cert; for (auto i = 0; i < sk_X509_num(systemCACerts); i++) { if (X509_STORE_add_cert(verifyStore, sk_X509_value(systemCACerts, i)) != 1) { - const auto errCode = ERR_peek_last_error(); - if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || - ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { - return {ErrorCodes::InvalidSSLConfiguration, - str::stream() << "Error adding certificate to X509 store: " - << ERR_reason_error_string(errCode)}; - } + auto status = checkX509_STORE_error(); + if (!status.isOK()) + return status; } } STACK_OF(X509_CRL)* systemCRLs = p7->d.sign->crl; for (auto i = 0; i < sk_X509_CRL_num(systemCRLs); i++) { if (X509_STORE_add_crl(verifyStore, sk_X509_CRL_value(systemCRLs, i)) != 1) { - const auto errCode = ERR_peek_last_error(); - if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || - ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { - return {ErrorCodes::InvalidSSLConfiguration, - str::stream() << "Error adding CRL to X509 store: " - << ERR_reason_error_string(errCode)}; - } + auto status = checkX509_STORE_error(); + if (!status.isOK()) + return status; + } + } + + return Status::OK(); +} +#elif defined(__APPLE__) + +template <typename T> +class CFTypeRefHolder { +public: + explicit CFTypeRefHolder(T ptr) : ref(static_cast<CFTypeRef>(ptr)) {} + ~CFTypeRefHolder() { + CFRelease(ref); + } + operator T() { + return static_cast<T>(ref); + } + +private: + CFTypeRef ref = nullptr; +}; +template <typename T> +CFTypeRefHolder<T> makeCFTypeRefHolder(T ptr) { + return CFTypeRefHolder<T>(ptr); +} + +std::string OSStatusToString(OSStatus status) { + auto errMsg = makeCFTypeRefHolder(SecCopyErrorMessageString(status, NULL)); + return std::string{CFStringGetCStringPtr(errMsg, kCFStringEncodingUTF8)}; +} + +Status importKeychainToX509_STORE(X509_STORE* verifyStore) { + // First we construct CFDictionary that specifies the search for certificates we want to do. + // These std::arrays make up the dictionary elements. + // kSecClass -> kSecClassCertificates (search for certificates) + // kSecReturnRef -> kCFBooleanTrue (return SecCertificateRefs) + // kSecMatchLimit -> kSecMatchLimitAll (return ALL the certificates). + static std::array<const void*, 3> searchDictKeys = {kSecClass, kSecReturnRef, kSecMatchLimit}; + static std::array<const void*, 3> searchDictValues = { + kSecClassCertificate, kCFBooleanTrue, kSecMatchLimitAll}; + static_assert(searchDictKeys.size() == searchDictValues.size(), + "Sizes of the search keys and values dictionaries should be the same size"); + + auto searchDict = makeCFTypeRefHolder(CFDictionaryCreate(kCFAllocatorDefault, + searchDictKeys.data(), + searchDictValues.data(), + searchDictKeys.size(), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + CFArrayRef result; + OSStatus status; + // Run the search against the default list of keychains and store the result in a CFArrayRef + if ((status = SecItemCopyMatching(searchDict, reinterpret_cast<CFTypeRef*>(&result))) != 0) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Error enumerating certificates: " << OSStatusToString(status)}; + } + const auto resultGuard = makeCFTypeRefHolder(result); + + for (CFIndex i = 0; i < CFArrayGetCount(result); i++) { + SecCertificateRef cert = + static_cast<SecCertificateRef>(const_cast<void*>(CFArrayGetValueAtIndex(result, i))); + + auto rawData = makeCFTypeRefHolder(SecCertificateCopyData(cert)); + if (!rawData) { + return { + ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Error enumerating certificates: " << OSStatusToString(status)}; + } + const uint8_t* rawDataPtr = CFDataGetBytePtr(rawData); + + // Parse an openssl X509 object from each returned certificate + X509* x509Cert = d2i_X509(nullptr, &rawDataPtr, CFDataGetLength(rawData)); + if (!x509Cert) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Error parsing X509 certificate from system keychain: " + << ERR_reason_error_string(ERR_peek_last_error())}; + } + const auto x509CertGuard = MakeGuard([&x509Cert]() { X509_free(x509Cert); }); + + // Add the parsed X509 object to the X509_STORE verification store + if (X509_STORE_add_cert(verifyStore, x509Cert) != 1) { + auto status = checkX509_STORE_error(); + if (!status.isOK()) + return status; } } @@ -906,9 +995,9 @@ Status importCertStoreToX509_STORE(LPWSTR storeName, DWORD storeLocation, X509_S #endif Status SSLManager::_setupSystemCA(SSL_CTX* context) { -#ifndef _WIN32 - // On non-Windows platforms, the OpenSSL libraries should have been configured with default - // locations for CA certificates. +#if !defined(_WIN32) && !defined(__APPLE__) + // On non-Windows/non-Apple platforms, the OpenSSL libraries should have been configured + // with default locations for CA certificates. if (SSL_CTX_set_default_verify_paths(context) != 1) { return { ErrorCodes::InvalidSSLConfiguration, @@ -924,11 +1013,14 @@ Status SSLManager::_setupSystemCA(SSL_CTX* context) { return {ErrorCodes::InvalidSSLConfiguration, "no X509 store found for SSL context while loading system certificates"}; } - +#if defined(_WIN32) auto status = importCertStoreToX509_STORE(L"root", CERT_SYSTEM_STORE_CURRENT_USER, verifyStore); if (!status.isOK()) return status; return importCertStoreToX509_STORE(L"CA", CERT_SYSTEM_STORE_CURRENT_USER, verifyStore); +#elif defined(__APPLE__) + return importKeychainToX509_STORE(verifyStore); +#endif #endif } |