summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/libs/trusted-server.pfxbin0 -> 2533 bytes
-rw-r--r--jstests/ssl/ssl_cert_selector_apple.js63
-rw-r--r--jstests/ssl/ssl_with_system_ca.js55
-rw-r--r--src/mongo/db/startup_warnings_common.cpp8
-rw-r--r--src/mongo/util/net/ssl_manager_apple.cpp348
-rw-r--r--src/mongo/util/net/ssl_options.cpp2
-rw-r--r--src/mongo/util/net/ssl_options.h3
7 files changed, 356 insertions, 123 deletions
diff --git a/jstests/libs/trusted-server.pfx b/jstests/libs/trusted-server.pfx
new file mode 100644
index 00000000000..f68d75f88b9
--- /dev/null
+++ b/jstests/libs/trusted-server.pfx
Binary files differ
diff --git a/jstests/ssl/ssl_cert_selector_apple.js b/jstests/ssl/ssl_cert_selector_apple.js
new file mode 100644
index 00000000000..ae65612a98d
--- /dev/null
+++ b/jstests/ssl/ssl_cert_selector_apple.js
@@ -0,0 +1,63 @@
+/**
+ * Validate that the server can load certificates from the
+ * Secure Transport certificate store.
+ *
+ * Don't actually try to connect via SSL, because without interactivity,
+ * we won't be able to click on the "Allow" button that Apple insists on presenting.
+ *
+ * Just verify that we can startup when we select a valid cert,
+ * and fail when we do not.
+ */
+
+load('jstests/ssl/libs/ssl_helpers.js');
+
+requireSSLProvider('apple', function() {
+ 'use strict';
+
+ const CLIENT =
+ 'C=US,ST=New York,L=New York City,O=MongoDB,OU=Kernel,CN=Trusted Kernel Test Client';
+ const SERVER =
+ 'C=US,ST=New York,L=New York City,O=MongoDB,OU=Kernel,CN=Trusted Kernel Test Server';
+ const INVALID = null;
+
+ const testCases = [
+ {selector: 'thumbprint=D7421F7442CA313821E19EE0509721F4D60B25A8', name: SERVER},
+ {selector: 'subject=Trusted Kernel Test Server', name: SERVER},
+ {selector: 'thumbprint=9CA511552F14D3FC2009D425873599BF77832238', name: CLIENT},
+ {selector: 'subject=Trusted Kernel Test Client', name: CLIENT},
+ {selector: 'thumbprint=D7421F7442CA313821E19EE0509721F4D60B25A9', name: INVALID},
+ {selector: 'subject=Unknown Test Client', name: INVALID}
+ ];
+
+ function test(cert, cluster) {
+ const opts = {
+ sslMode: 'requireSSL',
+ sslCertificateSelector: cert.selector,
+ sslClusterCertificateSelector: cluster.selector,
+ waitForConnect: false
+ };
+ clearRawMongoProgramOutput();
+ const mongod = MongoRunner.runMongod(opts);
+
+ assert.soon(function() {
+ const log = rawMongoProgramOutput();
+ if ((cert.name === null) || (cluster.name === null)) {
+ // Invalid search criteria should fail.
+ return log.search('Certificate selector returned no results') >= 0;
+ }
+ // Valid search criteria should show our Subject Names.
+ const certOK = log.search('Server Certificate Name: ' + cert.name) >= 0;
+ const clusOK = log.search('Client Certificate Name: ' + cluster.name) >= 0;
+ return certOK && clusOK;
+ }, "Starting Mongod with " + tojson(opts), 10000);
+
+ const killOpts = {allowedExitCode: MongoRunner.EXIT_SIGKILL};
+ MongoRunner.stopMongod(mongod, undefined, killOpts);
+ }
+
+ testCases.forEach(cert => {
+ testCases.forEach(cluster => {
+ test(cert, cluster);
+ });
+ });
+});
diff --git a/jstests/ssl/ssl_with_system_ca.js b/jstests/ssl/ssl_with_system_ca.js
index 812ebef43c6..570f55c1424 100644
--- a/jstests/ssl/ssl_with_system_ca.js
+++ b/jstests/ssl/ssl_with_system_ca.js
@@ -2,14 +2,12 @@
// certificate to the login keychain of the evergreen user. See,
// https://github.com/10gen/buildslave-cookbooks/commit/af7cabe5b6e0885902ebd4902f7f974b64cc8961
// for details.
-((function() {
+// To install trusted-ca.pem for local testing on OSX, invoke the following at a console:
+// security add-trusted-cert -d jstests/libs/trusted-ca.pem
+(function() {
'use strict';
- // Do not fail if this test leaves unterminated processes because testWithCerts
- // is expected to throw before it calls stopMongod.
- TestData.failIfUnterminatedProcesses = false;
const HOST_TYPE = getBuildInfo().buildEnvironment.target_os;
-
if (HOST_TYPE == "windows") {
// OpenSSL backed imports Root CA and intermediate CA
runProgram(
@@ -20,34 +18,37 @@
runProgram("certutil.exe", "-addstore", "-f", "Root", "jstests\\libs\\trusted-ca.pem");
}
- var testWithCerts = function(serverPem) {
+ function testWithCerts(prefix) {
jsTest.log(`Testing with SSL certs $ {
- serverPem
+ clientPem connecting to serverPem
}`);
- // allowSSL instead of requireSSL so that the non-SSL connection succeeds.
- var conn = MongoRunner.runMongod(
- {sslMode: 'requireSSL', sslPEMKeyFile: "jstests/libs/" + serverPem});
-
- // Should not be able to authenticate with x509.
- // Authenticate call will return 1 on success, 0 on error.
- var argv =
- ['./mongo', '--ssl', '--port', conn.port, '--eval', ('db.runCommand({buildInfo: 1})')];
+
+ // allowSSL to get a non-SSL control connection.
+ const conn = MongoRunner.runMongod(
+ {sslMode: 'allowSSL', sslPEMKeyFile: 'jstests/libs/' + prefix + 'server.pem'});
+
+ let argv = [
+ './mongo',
+ '--ssl',
+ '--port',
+ conn.port,
+ '--sslPEMKeyFile',
+ 'jstests/libs/' + prefix + 'client.pem',
+ '--eval',
+ ';'
+ ];
+
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");
}
- var exitStatus = runMongoProgram.apply(null, argv);
- assert.eq(exitStatus, 0, "successfully connected with SSL");
+ const exitCode = runMongoProgram.apply(null, argv);
MongoRunner.stopMongod(conn);
- };
-
- assert.throws(function() {
- // Note: this leaves a running mongod process.
- testWithCerts("server.pem", "client.pem");
- });
- assert.doesNotThrow(function() {
- testWithCerts("trusted-server.pem", "trusted-client.pem");
- });
-})());
+ return exitCode;
+ }
+
+ assert.neq(0, testWithCerts(''), 'Certs signed with untrusted CA');
+ assert.eq(0, testWithCerts('trusted-'), 'Certs signed with trusted CA');
+})();
diff --git a/src/mongo/db/startup_warnings_common.cpp b/src/mongo/db/startup_warnings_common.cpp
index 15a8de61823..d82ea0427bd 100644
--- a/src/mongo/db/startup_warnings_common.cpp
+++ b/src/mongo/db/startup_warnings_common.cpp
@@ -35,6 +35,7 @@
#include <boost/filesystem/operations.hpp>
#include <fstream>
+#include "mongo/config.h"
#include "mongo/db/server_options.h"
#include "mongo/util/log.h"
#include "mongo/util/net/ssl_options.h"
@@ -83,11 +84,16 @@ void logCommonStartupWarnings(const ServerGlobalParams& serverParams) {
* specify a sslCAFile parameter from the shell
*/
if (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled &&
+#ifdef MONGO_CONFIG_SSL_CERTIFICATE_SELECTORS
+ sslGlobalParams.sslCertificateSelector.empty() &&
+#endif
sslGlobalParams.sslCAFile.empty()) {
log() << "";
log() << "** WARNING: No SSL certificate validation can be performed since"
" no CA file has been provided";
-
+#ifdef MONGO_CONFIG_SSL_CERTIFICATE_SELECTORS
+ log() << "** and no sslCertificateSelector has been specified.";
+#endif
log() << "** Please specify an sslCAFile parameter.";
}
diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp
index b33da8e8270..9554add04f4 100644
--- a/src/mongo/util/net/ssl_manager_apple.cpp
+++ b/src/mongo/util/net/ssl_manager_apple.cpp
@@ -39,6 +39,8 @@
#include "mongo/base/initializer_context.h"
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
+#include "mongo/crypto/sha1_block.h"
+#include "mongo/crypto/sha256_block.h"
#include "mongo/platform/random.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/base64.h"
@@ -100,6 +102,20 @@ StatusWith<std::string> toString(::OSStatus status) {
return ret;
}
+// Ideally we'd use an operator<< overload,
+// but OSStatus is just a uint32_t.
+// Be explicit about conversion to provide meaningful
+// output in error streams.
+std::string stringFromOSStatus(::OSStatus status) {
+ static_assert(std::is_same<std::int32_t, ::OSStatus>::value,
+ "CoreFoundation OSStatus has changed type");
+ auto ret = toString(status);
+ if (!ret.isOK()) {
+ return str::stream() << "Unknown error: " << static_cast<std::int32_t>(status);
+ }
+ return ret.getValue();
+}
+
// CFTypeRef is actually just `void*`.
// So while we could be polymorphic with the other toString() methods,
// it's basically asking for a hard to diagnose type error.
@@ -124,19 +140,6 @@ std::ostringstream& operator<<(std::ostringstream& ss, ::CFStringRef str) {
return ss;
}
-std::ostringstream& operator<<(std::ostringstream& ss, ::OSStatus status) {
- static_assert(std::is_signed<int>::value == std::is_signed<::OSStatus>::value,
- "Must cast status to same signedness");
- static_assert(sizeof(int) >= sizeof(::OSStatus), "Must cast status to same or wider type");
- auto swStr = toString(status);
- if (swStr.isOK()) {
- ss << swStr.getValue();
- } else {
- ss << "Unknown error: " << static_cast<int>(status);
- }
- return ss;
-}
-
std::ostringstream& operator<<(std::ostringstream& ss, ::CFErrorRef error) {
std::string comma;
@@ -532,7 +535,8 @@ StatusWith<CFUniquePtr<::CFArrayRef>> loadPEM(const std::string& keyfilepath,
"key. Consider using a certificate selector or PKCS#12 instead");
}
if (status != ::errSecSuccess) {
- return retFail(str::stream() << "Failing importing certificate(s): " << status);
+ return retFail(str::stream() << "Failing importing certificate(s): "
+ << stringFromOSStatus(status));
}
auto count = ::CFArrayGetCount(cfcerts.get());
@@ -607,7 +611,8 @@ StatusWith<CFUniquePtr<::CFArrayRef>> loadPEM(const std::string& keyfilepath,
CFUniquePtr<::SecCertificateRef> cfcert(cert);
if (status != ::errSecSuccess) {
return {ErrorCodes::InternalError,
- str::stream() << "Unable to extract certificate from identity: " << status};
+ str::stream() << "Unable to extract certificate from identity: "
+ << stringFromOSStatus(status)};
}
::CFArrayAppendValue(strip.get(), cfcert.get());
}
@@ -625,33 +630,7 @@ StatusWith<CFUniquePtr<::CFArrayRef>> loadPEM(const std::string& keyfilepath,
return std::move(cfcerts);
}
-StatusWith<std::string> loadAndValidatePEM(const std::string& key,
- const std::string& pass,
- Date_t* expire = nullptr) {
- auto swCerts = loadPEM(key, pass);
- if (!swCerts.isOK()) {
- return swCerts.getStatus();
- }
-
- auto certs = std::move(swCerts.getValue());
- if (::CFArrayGetCount(certs.get()) <= 0) {
- return {ErrorCodes::InvalidSSLConfiguration, "No certificates in certificate list"};
- }
-
- auto root = ::CFArrayGetValueAtIndex(certs.get(), 0);
- if (!root || (::CFGetTypeID(root) != ::SecIdentityGetTypeID())) {
- return {ErrorCodes::InvalidSSLConfiguration, "Root certificate not an identity pair"};
- }
-
- ::SecCertificateRef idcert = nullptr;
- auto status = ::SecIdentityCopyCertificate(
- static_cast<::SecIdentityRef>(const_cast<void*>(root)), &idcert);
- if (status != ::errSecSuccess) {
- return {ErrorCodes::InvalidSSLConfiguration,
- str::stream() << "Unable to get certificate from identity: " << status};
- }
- CFUniquePtr<::SecCertificateRef> cert(idcert);
-
+StatusWith<std::string> certificateGetSubject(::SecCertificateRef cert, Date_t* expire = nullptr) {
// Fetch expiry range and full subject name.
CFUniquePtr<::CFMutableArrayRef> oids(
::CFArrayCreateMutable(nullptr, expire ? 3 : 1, &::kCFTypeArrayCallBacks));
@@ -662,8 +641,7 @@ StatusWith<std::string> loadAndValidatePEM(const std::string& key,
}
::CFErrorRef cferror = nullptr;
- CFUniquePtr<::CFDictionaryRef> cfdict(
- ::SecCertificateCopyValues(cert.get(), oids.get(), &cferror));
+ CFUniquePtr<::CFDictionaryRef> cfdict(::SecCertificateCopyValues(cert, oids.get(), &cferror));
if (cferror) {
CFUniquePtr<::CFErrorRef> deleter(cferror);
return {ErrorCodes::InvalidSSLConfiguration,
@@ -697,7 +675,174 @@ StatusWith<std::string> loadAndValidatePEM(const std::string& key,
}
return subject;
-};
+}
+
+StatusWith<std::string> certificateGetSubject(::CFArrayRef certs, Date_t* expire = nullptr) {
+ if (::CFArrayGetCount(certs) <= 0) {
+ return {ErrorCodes::InvalidSSLConfiguration, "No certificates in certificate list"};
+ }
+
+ auto root = ::CFArrayGetValueAtIndex(certs, 0);
+ if (!root || (::CFGetTypeID(root) != ::SecIdentityGetTypeID())) {
+ return {ErrorCodes::InvalidSSLConfiguration, "Root certificate not an identity pair"};
+ }
+
+ ::SecCertificateRef idcert = nullptr;
+ auto status = ::SecIdentityCopyCertificate(
+ static_cast<::SecIdentityRef>(const_cast<void*>(root)), &idcert);
+ if (status != ::errSecSuccess) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ str::stream() << "Unable to get certificate from identity: "
+ << stringFromOSStatus(status)};
+ }
+ CFUniquePtr<::SecCertificateRef> cert(idcert);
+ return certificateGetSubject(cert.get(), expire);
+}
+
+StatusWith<CFUniquePtr<::CFArrayRef>> copyMatchingCertificate(
+ const SSLParams::CertificateSelector& selector,
+ SSLManagerInterface::ConnectionDirection direction) {
+ if (selector.subject.empty() && selector.thumbprint.empty()) {
+ // In practice, this should never occur, thanks to the selector.empty()
+ // checks at the callsites of this function.
+ return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector has no values"};
+ }
+ if (!selector.subject.empty() && !selector.thumbprint.empty()) {
+ // This can only happen if the parsing logic in ssl_options.cpp changes.
+ // Guard against it to play it safe.
+ return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector has multiple values"};
+ }
+
+ const bool isServer = (direction == SSLManagerInterface::ConnectionDirection::kIncoming);
+ CFUniquePtr<::SecPolicyRef> cfpolicy(::SecPolicyCreateSSL(isServer, nullptr));
+
+ CFUniquePtr<::CFMutableDictionaryRef> cfquery(::CFDictionaryCreateMutable(
+ nullptr, 5, &::kCFTypeDictionaryKeyCallBacks, &::kCFTypeDictionaryValueCallBacks));
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecClass, ::kSecClassIdentity);
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecReturnRef, ::kCFBooleanTrue);
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecMatchLimit, ::kSecMatchLimitAll);
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecMatchPolicy, cfpolicy.get());
+
+ // Note: These search terms don't ACTUALLY work.
+ // We should be able to specify kSecMatchLimitOne, but instead we have to get
+ // extra (sometimes duplicate) results, and manually filter them below.
+ if (!selector.subject.empty()) {
+ invariant(selector.thumbprint.empty());
+ CFUniquePtr<::CFStringRef> cfsubject(::CFStringCreateWithCString(
+ nullptr, selector.subject.c_str(), ::kCFStringEncodingUTF8));
+ if (!cfsubject) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ str::stream() << "Certificate subject name specified is not UTF-8"
+ << selector.subject};
+ }
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecAttrLabel, cfsubject.get());
+ } else {
+ invariant(!selector.thumbprint.empty());
+ CFUniquePtr<::CFDataRef> cfdigest(
+ ::CFDataCreate(nullptr,
+ static_cast<const uint8_t*>(selector.thumbprint.data()),
+ selector.thumbprint.size()));
+ if (!cfdigest) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ "Unable to create Public Key Hash from certificate thumbprint selector value"};
+ }
+ // Don't be fooled by the name.
+ // "Public Key Hash" is actually referring to the digest of the entire Certificate.
+ ::CFDictionaryAddValue(cfquery.get(), ::kSecAttrPublicKeyHash, cfdigest.get());
+ }
+
+ ::CFTypeRef identities = nullptr;
+ auto status = ::SecItemCopyMatching(cfquery.get(), &identities);
+ CFUniquePtr<::CFArrayRef> cfident(static_cast<::CFArrayRef>(identities));
+ if (status != ::errSecSuccess) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ str::stream() << "Failure querying system keychain for certificate selector: "
+ << stringFromOSStatus(status)};
+ }
+
+ if (::CFGetTypeID(cfident.get()) != ::CFArrayGetTypeID()) {
+ return {ErrorCodes::InvalidSSLConfiguration, "System keychain returned invalid result"};
+ }
+
+ // We should be able to return the results at this point,
+ // but the search criteria above will return non-matching results in OSX 10.12 and later.
+ CFUniquePtr<::CFMutableArrayRef> cfresult(
+ ::CFArrayCreateMutable(nullptr, 1, &::kCFTypeArrayCallBacks));
+ for (::CFIndex i = 0; i < ::CFArrayGetCount(cfident.get()); ++i) {
+ auto ident = static_cast<::SecIdentityRef>(
+ const_cast<void*>(::CFArrayGetValueAtIndex(cfident.get(), i)));
+ if (::CFGetTypeID(ident) != ::SecIdentityGetTypeID()) {
+ continue;
+ }
+
+ ::SecCertificateRef cert = nullptr;
+ status = ::SecIdentityCopyCertificate(ident, &cert);
+ CFUniquePtr<::SecCertificateRef> cfcert(cert);
+ if (status != ::errSecSuccess) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ "Unable to retreive certificate from identity"};
+ }
+
+ if (!selector.subject.empty()) {
+ // Try matching subject name to short (common name) portion of subject.
+ CFUniquePtr<::CFStringRef> certSubject(
+ ::SecCertificateCopySubjectSummary(cfcert.get()));
+ if (!certSubject) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ "Unable to retreive subject summary from identity"};
+ }
+ auto swSubjectSummary = toString(certSubject.get());
+ if (!swSubjectSummary.isOK()) {
+ return swSubjectSummary.getStatus();
+ }
+ if (swSubjectSummary.getValue() == selector.subject) {
+ ::CFArrayAppendValue(cfresult.get(), ident);
+ break;
+ }
+
+ // Try matching full subject name instead.
+ auto swCertSubject = certificateGetSubject(cfcert.get());
+ if (!swCertSubject.isOK()) {
+ return swCertSubject.getStatus();
+ }
+ if (swCertSubject.getValue() == selector.subject) {
+ ::CFArrayAppendValue(cfresult.get(), ident);
+ break;
+ }
+ }
+
+ if (!selector.thumbprint.empty()) {
+ CFUniquePtr<::CFDataRef> cfCertData(::SecCertificateCopyData(cfcert.get()));
+ ConstDataRange certData(
+ reinterpret_cast<const char*>(::CFDataGetBytePtr(cfCertData.get())),
+ ::CFDataGetLength(cfCertData.get()));
+
+ // Attempt to match SHA1 digest.
+ if (SHA1Block::kHashLength == selector.thumbprint.size()) {
+ const auto certSha1 = SHA1Block::computeHash({certData});
+ if (!memcmp(certSha1.data(), selector.thumbprint.data(), certSha1.size())) {
+ ::CFArrayAppendValue(cfresult.get(), ident);
+ break;
+ }
+ }
+
+ // Attempt to match SHA256 digest.
+ if (SHA256Block::kHashLength == selector.thumbprint.size()) {
+ const auto certSha256 = SHA256Block::computeHash({certData});
+ if (!memcmp(certSha256.data(), selector.thumbprint.data(), certSha256.size())) {
+ ::CFArrayAppendValue(cfresult.get(), ident);
+ break;
+ }
+ }
+ }
+ }
+
+ if (::CFArrayGetCount(cfresult.get()) == 0) {
+ return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector returned no results"};
+ }
+
+ return CFUniquePtr<::CFArrayRef>(cfresult.release());
+}
} // namespace
@@ -858,8 +1003,6 @@ public:
int SSL_shutdown(SSLConnectionInterface* conn) final;
private:
- Status _validatePEMs(const SSLParams& params, bool isServer);
-
bool _weakValidation;
bool _allowInvalidCertificates;
bool _allowInvalidHostnames;
@@ -873,54 +1016,41 @@ SSLManagerApple::SSLManagerApple(const SSLParams& params, bool isServer)
: _weakValidation(params.sslWeakCertificateValidation),
_allowInvalidCertificates(params.sslAllowInvalidCertificates),
_allowInvalidHostnames(params.sslAllowInvalidHostnames) {
+
uassertStatusOK(initSSLContext(&_clientCtx, params, ConnectionDirection::kOutgoing));
+ if (_clientCtx.certs) {
+ _sslConfiguration.clientSubjectName =
+ uassertStatusOK(certificateGetSubject(_clientCtx.certs.get()));
+ }
+
if (isServer) {
uassertStatusOK(initSSLContext(&_serverCtx, params, ConnectionDirection::kIncoming));
+ if (_serverCtx.certs) {
+ _sslConfiguration.serverSubjectName = uassertStatusOK(certificateGetSubject(
+ _serverCtx.certs.get(), &_sslConfiguration.serverCertificateExpirationDate));
+ static auto task =
+ CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
+ }
}
- uassertStatusOK(_validatePEMs(params, isServer));
if (!params.sslCAFile.empty()) {
auto ca = uassertStatusOK(loadPEM(params.sslCAFile, "", kLoadPEMStripKeys));
_ca = std::move(ca);
_sslConfiguration.hasCA = _ca && ::CFArrayGetCount(_ca.get());
}
-}
-Status SSLManagerApple::_validatePEMs(const SSLParams& params, bool isServer) {
- // pick the certificate for use in outgoing connections,
- std::string clientPEM, clientPassword;
- if (!isServer || params.sslClusterFile.empty()) {
- // We are either a client, or a server without a cluster key,
- // so use the PEM key file, if specified.
- clientPEM = params.sslPEMKeyFile;
- clientPassword = params.sslPEMKeyPassword;
- } else {
- // We are a server with a cluster key, so use the cluster key file
- clientPEM = params.sslClusterFile;
- clientPassword = params.sslClusterPassword;
- }
- if (!clientPEM.empty()) {
- auto swSubject = loadAndValidatePEM(clientPEM, clientPassword);
- if (!swSubject.isOK()) {
- return swSubject.getStatus();
- }
- _sslConfiguration.clientSubjectName = swSubject.getValue();
+ if (!params.sslCertificateSelector.empty() || !params.sslClusterCertificateSelector.empty()) {
+ // By using the system keychain, we acknowledge it exists.
+ _sslConfiguration.hasCA = true;
}
- if (isServer) {
- auto swSubject = loadAndValidatePEM(params.sslPEMKeyFile,
- params.sslPEMKeyPassword,
- &_sslConfiguration.serverCertificateExpirationDate);
- if (!swSubject.isOK()) {
- return swSubject.getStatus();
- }
- _sslConfiguration.serverSubjectName = swSubject.getValue();
-
- static auto task =
- CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
+ if (!_ca) {
+ // No explicit CA was specified, use the Keychain CA explicitly on client connects,
+ // even though we're going to pretend it doesn't exist on server.
+ ::CFArrayRef certs = nullptr;
+ uassertOSStatusOK(SecTrustCopyAnchorCertificates(&certs));
+ _ca.reset(certs);
}
-
- return Status::OK();
}
StatusWith<std::pair<::SSLProtocol, ::SSLProtocol>> parseProtocolRange(const SSLParams& params) {
@@ -965,21 +1095,40 @@ Status SSLManagerApple::initSSLContext(asio::ssl::apple::Context* context,
context->protoMax = proto.second;
// Certificate.
- if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) {
- auto swCertificates = loadPEM(params.sslClusterFile, params.sslClusterPassword);
- if (!swCertificates.isOK()) {
- return swCertificates.getStatus();
+ const auto selectCertificate = [&context,
+ direction](const SSLParams::CertificateSelector& selector,
+ const std::string& PEMFile,
+ const std::string& PEMPass) -> Status {
+ if (!selector.empty()) {
+ auto swCerts = copyMatchingCertificate(selector, direction);
+ if (!swCerts.isOK()) {
+ return swCerts.getStatus();
+ }
+ context->certs = std::move(swCerts.getValue());
+ return Status::OK();
}
- context->certs = std::move(swCertificates.getValue());
- } else if (!params.sslPEMKeyFile.empty()) {
- auto swCertificates = loadPEM(params.sslPEMKeyFile, params.sslPEMKeyPassword);
- if (!swCertificates.isOK()) {
- return swCertificates.getStatus();
+ if (!PEMFile.empty()) {
+ auto swCerts = loadPEM(PEMFile, PEMPass);
+ if (!swCerts.isOK()) {
+ return swCerts.getStatus();
+ }
+ context->certs = std::move(swCerts.getValue());
+ return Status::OK();
}
- context->certs = std::move(swCertificates.getValue());
+ return Status::OK();
+ };
+
+ if (direction == ConnectionDirection::kOutgoing) {
+ const auto status = selectCertificate(
+ params.sslClusterCertificateSelector, params.sslClusterFile, params.sslClusterPassword);
+ if (context->certs || !status.isOK()) {
+ return status;
+ }
+ // Fallthrough...
}
- return Status::OK();
+ return selectCertificate(
+ params.sslCertificateSelector, params.sslPEMKeyFile, params.sslPEMKeyPassword);
}
SSLConnectionInterface* SSLManagerApple::connect(Socket* socket) {
@@ -1037,7 +1186,8 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerApple::parseAndValidatePeerCe
const auto status = ::SSLCopyPeerTrust(ssl, &trust);
CFUniquePtr<::SecTrustRef> cftrust(trust);
if ((status != ::errSecSuccess) || (!cftrust)) {
- return badCert(str::stream() << "Unable to retreive SSL trust from peer: " << status,
+ return badCert(str::stream() << "Unable to retreive SSL trust from peer: "
+ << stringFromOSStatus(status),
_weakValidation);
}
@@ -1047,7 +1197,8 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerApple::parseAndValidatePeerCe
status = ::SecTrustSetAnchorCertificatesOnly(cftrust.get(), true);
}
if (status != ::errSecSuccess) {
- return badCert(str::stream() << "Unable to bind CA to trust chain: " << status,
+ return badCert(str::stream() << "Unable to bind CA to trust chain: "
+ << stringFromOSStatus(status),
_weakValidation);
}
}
@@ -1213,6 +1364,15 @@ MONGO_INITIALIZER(SSLManager)(InitializerContext*) {
stdx::lock_guard<SimpleMutex> lck(sslManagerMtx);
if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) {
theSSLManager = new SSLManagerApple(sslGlobalParams, isSSLServer);
+ const auto& config = theSSLManager->getSSLConfiguration();
+ log() << "Secure Transport Initialized";
+ if (!config.clientSubjectName.empty()) {
+ log() << "Client Certificate Name: " << config.clientSubjectName;
+ }
+ if (!config.serverSubjectName.empty()) {
+ log() << "Server Certificate Name: " << config.serverSubjectName;
+ log() << "Server Certificate Expiration: " << config.serverCertificateExpirationDate;
+ }
}
return Status::OK();
}
diff --git a/src/mongo/util/net/ssl_options.cpp b/src/mongo/util/net/ssl_options.cpp
index 1db33b0344f..e6c0a8d21c9 100644
--- a/src/mongo/util/net/ssl_options.cpp
+++ b/src/mongo/util/net/ssl_options.cpp
@@ -422,7 +422,7 @@ Status storeSSLServerOptions(const moe::Environment& params) {
return status;
}
}
- if (params.count("net.ssl.ClusterCertificateSelector")) {
+ if (params.count("net.ssl.clusterCertificateSelector")) {
const auto status = parseCertificateSelector(
&sslGlobalParams.sslClusterCertificateSelector,
"net.ssl.clusterCertificateSelector",
diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h
index 9a2c076ed5a..721d713e24b 100644
--- a/src/mongo/util/net/ssl_options.h
+++ b/src/mongo/util/net/ssl_options.h
@@ -62,6 +62,9 @@ struct SSLParams {
struct CertificateSelector {
std::string subject;
std::vector<uint8_t> thumbprint;
+ bool empty() const {
+ return subject.empty() && thumbprint.empty();
+ }
};
#ifdef MONGO_CONFIG_SSL_CERTIFICATE_SELECTORS
CertificateSelector sslCertificateSelector; // --sslCertificateSelector