summaryrefslogtreecommitdiff
path: root/src/mongo/util/net/ssl_manager_openssl.cpp
diff options
context:
space:
mode:
authorAndrew Shuvalov <andrew.shuvalov@mongodb.com>2020-11-15 00:22:54 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-15 01:46:45 +0000
commitfe88fed31376d6e2dc95af46342fb3c87c164ab1 (patch)
treea5eef28d9370d8743dbf0186aebd52fb712e1074 /src/mongo/util/net/ssl_manager_openssl.cpp
parent828266b8c7b99e54aa172ccb89aadcdb7d1e6a37 (diff)
downloadmongo-fe88fed31376d6e2dc95af46342fb3c87c164ab1.tar.gz
SERVER-51599: Allow creating an SSLConnectionContext from in-memory certificates
Diffstat (limited to 'src/mongo/util/net/ssl_manager_openssl.cpp')
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp314
1 files changed, 262 insertions, 52 deletions
diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp
index 9fb60e83f6e..d42474657a3 100644
--- a/src/mongo/util/net/ssl_manager_openssl.cpp
+++ b/src/mongo/util/net/ssl_manager_openssl.cpp
@@ -1128,7 +1128,7 @@ class SSLManagerOpenSSL : public SSLManagerInterface,
public std::enable_shared_from_this<SSLManagerOpenSSL> {
public:
explicit SSLManagerOpenSSL(const SSLParams& params, bool isServer);
- ~SSLManagerOpenSSL() {
+ ~SSLManagerOpenSSL() final {
stopJobs();
}
@@ -1138,6 +1138,7 @@ public:
*/
Status initSSLContext(SSL_CTX* context,
const SSLParams& params,
+ const TransientSSLParams& transientParams,
ConnectionDirection direction) final;
SSLConnectionInterface* connect(Socket* socket) final;
@@ -1241,6 +1242,19 @@ private:
return StringData(_password->c_str());
}
+ /**
+ * This method can only return a cached password and never prompts.
+ * @returns cached password if available, error if password is not cached.
+ */
+ StatusWith<StringData> fetchCachedPasswordNoPrompt() {
+ stdx::lock_guard<Latch> lock(_mutex);
+ if (_password->size()) {
+ return StringData(_password->c_str());
+ }
+ return Status(ErrorCodes::UnknownError,
+ "Failed to return a cached password, cannot prompt.");
+ }
+
private:
Mutex _mutex = MONGO_MAKE_LATCH("PasswordFetcher::_mutex");
SecureString _password; // Protected by _mutex
@@ -1299,7 +1313,10 @@ private:
* @param info as a pointer to the CertInformationToLog struct to populate
* with the information.
*/
- void _getX509CertInfo(UniqueX509& x509, CertInformationToLog* info) const;
+ static void _getX509CertInfo(UniqueX509& x509,
+ CertInformationToLog* info,
+ std::optional<StringData> keyFile,
+ std::optional<StringData> targetClusterURI);
/*
* Retrieve and store CRL information from the provided CRL filename.
@@ -1316,6 +1333,42 @@ private:
/** @return true if was successful, otherwise false */
bool _setupPEM(SSL_CTX* context, const std::string& keyFile, PasswordFetcher* password);
+ /**
+ * @param payload in-memory payload of a PEM file
+ * @return true if was successful, otherwise false
+ */
+ bool _setupPEMFromMemoryPayload(SSL_CTX* context,
+ const std::string& payload,
+ PasswordFetcher* password,
+ StringData targetClusterURI);
+
+ /**
+ * Setup PEM from BIO, which could be file or memory input abstraction.
+ * @param inBio input BIO, where smart pointer is created with a custom deleter to call
+ * 'BIO_free()'.
+ * @param keyFile if the certificate was loaded from file, this is the file name. If empty,
+ * it means the certificate came from memory payload.
+ * @param targetClusterURI If the certificate targets a particular cluster, this is cluster URI.
+ * If empty, it means the certificate is the default one for the local cluster.
+ * @return true if was successful, otherwise false
+ */
+ bool _setupPEMFromBIO(SSL_CTX* context,
+ UniqueBIO inBio,
+ PasswordFetcher* password,
+ std::optional<StringData> keyFile,
+ std::optional<StringData> targetClusterURI);
+
+ /**
+ * Loads a certificate chain from memory into context.
+ * This method is intended to be a repalcement of API call SSL_CTX_use_certificate_chain_file()
+ * but using memory instead of file.
+ * @return true if was successful, otherwise false
+ */
+ static bool _readCertificateChainFromMemory(SSL_CTX* context,
+ const std::string& payload,
+ PasswordFetcher* password,
+ std::optional<StringData> targetClusterURI);
+
/*
* Set up an SSL context for certificate validation by loading a CA
*/
@@ -1342,10 +1395,25 @@ private:
*/
void _flushNetworkBIO(SSLConnectionOpenSSL* conn);
+ /*
+ * Utility method to process the result returned by password Fetcher.
+ */
+ static int _processPasswordFetcherOutput(StatusWith<StringData>* fetcherResult,
+ char* buf,
+ int num,
+ int rwflag);
+
/**
* Callbacks for SSL functions.
*/
+
static int password_cb(char* buf, int num, int rwflag, void* userdata);
+
+ /**
+ * Special flawor of password callback, which always fails.
+ * @return -1.
+ */
+ static int always_error_password_cb(char* buf, int num, int rwflag, void* userdata);
static int servername_cb(SSL* s, int* al, void* arg);
static int verify_cb(int ok, X509_STORE_CTX* ctx);
};
@@ -1514,20 +1582,28 @@ SSLManagerOpenSSL::SSLManagerOpenSSL(const SSLParams& params, bool isServer)
}
int SSLManagerOpenSSL::password_cb(char* buf, int num, int rwflag, void* userdata) {
- // Unless OpenSSL misbehaves, num should always be positive
- fassert(17314, num > 0);
invariant(userdata);
-
auto pwFetcher = static_cast<PasswordFetcher*>(userdata);
auto swPassword = pwFetcher->fetchPassword();
- if (!swPassword.isOK()) {
- LOGV2_ERROR(23239,
- "Unable to fetch password: {error}",
- "Unable to fetch password",
- "error"_attr = swPassword.getStatus());
+ return _processPasswordFetcherOutput(&swPassword, buf, num, rwflag);
+}
+
+int SSLManagerOpenSSL::always_error_password_cb(char* buf, int num, int rwflag, void* userdata) {
+ return -1;
+}
+
+int SSLManagerOpenSSL::_processPasswordFetcherOutput(StatusWith<StringData>* swPassword,
+ char* buf,
+ int num,
+ int rwflag) {
+ // Unless OpenSSL misbehaves, num should always be positive
+ fassert(17314, num > 0);
+
+ if (!swPassword->isOK()) {
+ LOGV2_ERROR(23239, "Unable to fetch password", "error"_attr = swPassword->getStatus());
return -1;
}
- StringData password = std::move(swPassword.getValue());
+ StringData password = std::move(swPassword->getValue());
const size_t copyCount = std::min(password.size(), static_cast<size_t>(num));
std::copy_n(password.begin(), copyCount, buf);
@@ -2036,6 +2112,7 @@ Milliseconds SSLManagerOpenSSL::updateOcspStaplingContextWithResponse(
Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context,
const SSLParams& params,
+ const TransientSSLParams& transientParams,
ConnectionDirection direction) {
// SSL_OP_ALL - Activate all bug workaround options, to support buggy client SSL's.
// SSL_OP_NO_SSLv2 - Disable SSL v2 support
@@ -2097,7 +2174,21 @@ Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context,
<< getSSLErrorMessage(ERR_get_error()));
}
- if (direction == ConnectionDirection::kOutgoing && params.tlsWithholdClientCertificate) {
+
+ if (direction == ConnectionDirection::kOutgoing &&
+ !transientParams.sslClusterPEMPayload.empty()) {
+
+ // Transient params for outgoing connection have priority over global params.
+ if (!_setupPEMFromMemoryPayload(
+ context,
+ transientParams.sslClusterPEMPayload,
+ &_clusterPEMPassword,
+ transientParams.targetedClusterConnectionString.toString())) {
+ return Status(ErrorCodes::InvalidSSLConfiguration,
+ str::stream() << "Can not set up transient ssl cluster certificate for "
+ << transientParams.targetedClusterConnectionString);
+ }
+ } else if (direction == ConnectionDirection::kOutgoing && params.tlsWithholdClientCertificate) {
// Do not send a client certificate if they have been suppressed.
} else if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) {
@@ -2195,7 +2286,7 @@ bool SSLManagerOpenSSL::_initSynchronousSSLContext(UniqueSSLContext* contextPtr,
ConnectionDirection direction) {
*contextPtr = UniqueSSLContext(SSL_CTX_new(SSLv23_method()));
- uassertStatusOK(initSSLContext(contextPtr->get(), params, direction));
+ uassertStatusOK(initSSLContext(contextPtr->get(), params, TransientSSLParams(), direction));
// If renegotiation is needed, don't return from recv() or send() until it's successful.
// Note: this is for blocking sockets only.
@@ -2211,7 +2302,6 @@ bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
BIO* inBIO = BIO_new(BIO_s_file());
if (inBIO == nullptr) {
LOGV2_ERROR(23243,
- "failed to allocate BIO object: {error}",
"Failed to allocate BIO object",
"error"_attr = getSSLErrorMessage(ERR_get_error()));
return false;
@@ -2220,7 +2310,6 @@ bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
ON_BLOCK_EXIT([&] { BIO_free(inBIO); });
if (BIO_read_filename(inBIO, keyFile.c_str()) <= 0) {
LOGV2_ERROR(23244,
- "cannot read key file when setting subject name: {keyFile} {error}",
"Cannot read key file when setting subject name",
"keyFile"_attr = keyFile,
"error"_attr = getSSLErrorMessage(ERR_get_error()));
@@ -2231,7 +2320,6 @@ bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
inBIO, nullptr, &SSLManagerOpenSSL::password_cb, static_cast<void*>(&keyPassword));
if (x509 == nullptr) {
LOGV2_ERROR(23245,
- "cannot retrieve certificate from keyfile: {keyFile} {error}",
"Cannot retrieve certificate from keyfile",
"keyFile"_attr = keyFile,
"error"_attr = getSSLErrorMessage(ERR_get_error()));
@@ -2263,66 +2351,177 @@ bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
return true;
}
+// static
+bool SSLManagerOpenSSL::_readCertificateChainFromMemory(
+ SSL_CTX* context,
+ const std::string& payload,
+ PasswordFetcher* password,
+ std::optional<StringData> targetClusterURI) {
+
+ logv2::DynamicAttributes errorAttrs;
+ if (targetClusterURI) {
+ errorAttrs.add("targetClusterURI", *targetClusterURI);
+ }
+
+ ERR_clear_error(); // Clear error stack for SSL_CTX_use_certificate().
+
+ // Note: old versions of SSL take (void*) here but it's still R/O.
+#if OPENSSL_VERSION_NUMBER <= 0x1000114fL
+ UniqueBIO inBio(BIO_new_mem_buf(const_cast<char*>(payload.c_str()), payload.length()));
+#else
+ UniqueBIO inBio(BIO_new_mem_buf(payload.c_str(), payload.length()));
+#endif
+
+ if (!inBio) {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(5159905, "Failed to allocate BIO from in memory payload", errorAttrs);
+ return false;
+ }
+
+ auto password_cb =
+ &SSLManagerOpenSSL::always_error_password_cb; // We don't expect a password to be required.
+ void* userdata = static_cast<void*>(password);
+ UniqueX509 x509cert(PEM_read_bio_X509_AUX(inBio.get(), NULL, password_cb, userdata));
+
+ if (!x509cert) {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(5159906, "Failed to read the X509 certificate from memory", errorAttrs);
+ return false;
+ }
+
+ CertInformationToLog debugInfo;
+ _getX509CertInfo(x509cert, &debugInfo, std::nullopt, targetClusterURI);
+ logCert(debugInfo, "", 5159903);
+
+ // SSL_CTX_use_certificate increments the refcount on cert.
+ if (1 != SSL_CTX_use_certificate(context, x509cert.get())) {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(5159907, "Failed to use the X509 certificate loaded from memory", errorAttrs);
+ return false;
+ }
+
+ // If we could set up our certificate, now proceed to the CA certificates.
+ UniqueX509 ca;
+#if OPENSSL_VERSION_NUMBER >= 0x100010fFL
+ SSL_CTX_clear_chain_certs(context);
+#else
+ SSL_CTX_clear_extra_chain_certs(context);
+#endif
+ while ((ca = UniqueX509(PEM_read_bio_X509(inBio.get(), NULL, password_cb, userdata)))) {
+#if OPENSSL_VERSION_NUMBER >= 0x100010fFL
+ if (1 != SSL_CTX_add1_chain_cert(context, ca.get())) {
+#else
+ if (1 != SSL_CTX_add_extra_chain_cert(context, ca.release())) {
+#endif
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(
+ 5159908, "Failed to use the CA X509 certificate loaded from memory", errorAttrs);
+ return false;
+ }
+ _getX509CertInfo(ca, &debugInfo, std::nullopt, targetClusterURI);
+ logCert(debugInfo, "", 5159902);
+ }
+ // When the while loop ends, it's usually just EOF.
+ auto err = ERR_peek_last_error();
+ if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
+ ERR_clear_error();
+ } else {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(
+ 5159909, "Error remained after scanning all X509 certificates from memory", errorAttrs);
+ return false; // Some real error.
+ }
+
+ return true;
+}
+
bool SSLManagerOpenSSL::_setupPEM(SSL_CTX* context,
const std::string& keyFile,
PasswordFetcher* password) {
+ logv2::DynamicAttributes errorAttrs;
+ errorAttrs.add("keyFile", keyFile);
+
if (SSL_CTX_use_certificate_chain_file(context, keyFile.c_str()) != 1) {
- LOGV2_ERROR(23248,
- "cannot read certificate file: {keyFile} {error}",
- "Cannot read certificate file",
- "keyFile"_attr = keyFile,
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23248, "Cannot read certificate file", errorAttrs);
return false;
}
- BIO* inBio = BIO_new(BIO_s_file());
+ UniqueBIO inBio(BIO_new(BIO_s_file()));
+
if (!inBio) {
- LOGV2_ERROR(23249,
- "failed to allocate BIO object: {error}",
- "Failed to allocate BIO object",
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23249, "Failed to allocate BIO object", errorAttrs);
return false;
}
- const auto bioGuard = makeGuard([&inBio]() { BIO_free(inBio); });
- if (BIO_read_filename(inBio, keyFile.c_str()) <= 0) {
- LOGV2_ERROR(23250,
- "cannot read PEM key file: {keyFile} {error}",
- "Cannot read PEM key file",
- "keyFile"_attr = keyFile,
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ if (BIO_read_filename(inBio.get(), keyFile.c_str()) <= 0) {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23250, "Cannot read PEM key file", errorAttrs);
return false;
}
+ return _setupPEMFromBIO(context, std::move(inBio), password, keyFile, std::nullopt);
+}
+
+bool SSLManagerOpenSSL::_setupPEMFromMemoryPayload(SSL_CTX* context,
+ const std::string& payload,
+ PasswordFetcher* password,
+ StringData targetClusterURI) {
+ logv2::DynamicAttributes errorAttrs;
+ errorAttrs.add("targetClusterURI", targetClusterURI);
+
+ if (!_readCertificateChainFromMemory(context, payload, password, targetClusterURI)) {
+ return false;
+ }
+#if OPENSSL_VERSION_NUMBER <= 0x1000114fL
+ UniqueBIO inBio(BIO_new_mem_buf(const_cast<char*>(payload.c_str()), payload.length()));
+#else
+ UniqueBIO inBio(BIO_new_mem_buf(payload.c_str(), payload.length()));
+#endif
+
+ if (!inBio) {
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(5159901, "Failed to allocate BIO object from in-memory payload", errorAttrs);
+ return false;
+ }
+
+ return _setupPEMFromBIO(context, std::move(inBio), password, std::nullopt, targetClusterURI);
+}
+
+bool SSLManagerOpenSSL::_setupPEMFromBIO(SSL_CTX* context,
+ UniqueBIO inBio,
+ PasswordFetcher* password,
+ std::optional<StringData> keyFile,
+ std::optional<StringData> targetClusterURI) {
+ logv2::DynamicAttributes errorAttrs;
+ if (keyFile) {
+ errorAttrs.add("keyFile", *keyFile);
+ }
+ if (targetClusterURI) {
+ errorAttrs.add("targetClusterURI", *targetClusterURI);
+ }
// Obtain the private key, using our callback to acquire a decryption password if necessary.
- decltype(&SSLManagerOpenSSL::password_cb) password_cb = &SSLManagerOpenSSL::password_cb;
+ auto password_cb = &SSLManagerOpenSSL::password_cb;
void* userdata = static_cast<void*>(password);
- EVP_PKEY* privateKey = PEM_read_bio_PrivateKey(inBio, nullptr, password_cb, userdata);
+ EVP_PKEY* privateKey = PEM_read_bio_PrivateKey(inBio.get(), nullptr, password_cb, userdata);
if (!privateKey) {
- LOGV2_ERROR(23251,
- "cannot read PEM key file: {keyFile} {error}",
- "Cannot read PEM key file",
- "keyFile"_attr = keyFile,
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23251, "Cannot read PEM key", errorAttrs);
return false;
}
const auto privateKeyGuard = makeGuard([&privateKey]() { EVP_PKEY_free(privateKey); });
if (SSL_CTX_use_PrivateKey(context, privateKey) != 1) {
- LOGV2_ERROR(23252,
- "cannot use PEM key file: {keyFile} {error}",
- "Cannot use PEM key file",
- "keyFile"_attr = keyFile,
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23252, "Cannot use PEM key", errorAttrs);
return false;
}
// Verify that the certificate and the key go together.
if (SSL_CTX_check_private_key(context) != 1) {
- LOGV2_ERROR(23253,
- "SSL certificate validation failed: {error}",
- "SSL certificate validation failed",
- "error"_attr = getSSLErrorMessage(ERR_get_error()));
+ CaptureSSLErrorInAttrs capture(errorAttrs);
+ LOGV2_ERROR(23253, "SSL certificate validation failed", errorAttrs);
return false;
}
@@ -2983,13 +3182,18 @@ UniqueX509 SSLManagerOpenSSL::_getX509Object(StringData keyFile,
constexpr size_t kSHA1HashBytes = 20;
-void SSLManagerOpenSSL::_getX509CertInfo(UniqueX509& x509, CertInformationToLog* info) const {
+// static
+void SSLManagerOpenSSL::_getX509CertInfo(UniqueX509& x509,
+ CertInformationToLog* info,
+ std::optional<StringData> keyFile,
+ std::optional<StringData> targetClusterURI) {
info->subject = getCertificateSubjectX509Name(x509.get());
info->issuer = convertX509ToSSLX509Name(X509_get_issuer_name(x509.get()));
info->thumbprint.resize(kSHA1HashBytes);
X509_digest(
x509.get(), EVP_sha1(), reinterpret_cast<unsigned char*>(info->thumbprint.data()), nullptr);
+ info->hexEncodedThumbprint = hexblob::encode(info->thumbprint.data(), info->thumbprint.size());
auto notBeforeMillis = convertASN1ToMillis(X509_get_notBefore(x509.get()));
@@ -3002,6 +3206,11 @@ void SSLManagerOpenSSL::_getX509CertInfo(UniqueX509& x509, CertInformationToLog*
info->validityNotAfter = notAfterMillis;
uassert(4913004, "date conversion failed", notAfterMillis != Date_t());
+
+ if (keyFile)
+ info->keyFile = keyFile->toString();
+ if (targetClusterURI)
+ info->targetClusterURI = targetClusterURI->toString();
}
@@ -3052,14 +3261,15 @@ SSLInformationToLog SSLManagerOpenSSL::getSSLInformationToLog() const {
if (!(sslGlobalParams.sslPEMKeyFile.empty())) {
UniqueX509 serverX509Cert =
_getX509Object(sslGlobalParams.sslPEMKeyFile, &_serverPEMPassword);
- _getX509CertInfo(serverX509Cert, &info.server);
+ _getX509CertInfo(serverX509Cert, &info.server, sslGlobalParams.sslPEMKeyFile, std::nullopt);
}
if (!(sslGlobalParams.sslClusterFile.empty())) {
CertInformationToLog clusterInfo;
UniqueX509 clusterX509Cert =
_getX509Object(sslGlobalParams.sslClusterFile, &_clusterPEMPassword);
- _getX509CertInfo(clusterX509Cert, &clusterInfo);
+ _getX509CertInfo(
+ clusterX509Cert, &clusterInfo, sslGlobalParams.sslClusterFile, std::nullopt);
info.cluster = clusterInfo;
} else {
info.cluster = boost::none;