summaryrefslogtreecommitdiff
path: root/src/mongo/util/net
diff options
context:
space:
mode:
authorLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
committerLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
commit959575a5ca598bf5f37fb5cebe7ed1d80d3d71f7 (patch)
treeacc8d60aedb12b70048e676e8a7349deb0010db8 /src/mongo/util/net
parent76588293975fc059cf076779e4283e6ffaf8afff (diff)
New upstream version 6.0.20upstream
Diffstat (limited to 'src/mongo/util/net')
-rw-r--r--src/mongo/util/net/SConscript7
-rw-r--r--src/mongo/util/net/sock_test.cpp161
-rw-r--r--src/mongo/util/net/sock_test_utils.cpp199
-rw-r--r--src/mongo/util/net/sock_test_utils.h41
-rw-r--r--src/mongo/util/net/sockaddr.cpp2
-rw-r--r--src/mongo/util/net/ssl/detail/impl/engine_apple.ipp4
-rw-r--r--src/mongo/util/net/ssl_manager_apple.cpp14
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp57
-rw-r--r--src/mongo/util/net/ssl_manager_test.cpp387
-rw-r--r--src/mongo/util/net/ssl_manager_windows.cpp17
10 files changed, 673 insertions, 216 deletions
diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript
index 272cd9740a1..f885a122325 100644
--- a/src/mongo/util/net/SConscript
+++ b/src/mongo/util/net/SConscript
@@ -252,9 +252,10 @@ if get_option('ssl') == 'on':
env.CppUnitTest(
target='util_net_ssl_test',
source=[
- 'ssl_manager_test.cpp',
- 'ssl_options_test.cpp',
- 'sock_test.cpp',
+ "ssl_manager_test.cpp",
+ "ssl_options_test.cpp",
+ "sock_test.cpp",
+ "sock_test_utils.cpp",
],
LIBDEPS=[
'$BUILD_DIR/mongo/client/connection_string',
diff --git a/src/mongo/util/net/sock_test.cpp b/src/mongo/util/net/sock_test.cpp
index ccb751ea2dd..84d91f5ba1b 100644
--- a/src/mongo/util/net/sock_test.cpp
+++ b/src/mongo/util/net/sock_test.cpp
@@ -31,176 +31,17 @@
#include "mongo/util/net/sock.h"
-#ifndef _WIN32
-#include <netdb.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#endif
-
#include "mongo/db/server_options.h"
#include "mongo/stdx/thread.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/concurrency/notification.h"
#include "mongo/util/fail_point.h"
+#include "mongo/util/net/sock_test_utils.h"
#include "mongo/util/net/socket_exception.h"
namespace {
using namespace mongo;
-using std::shared_ptr;
-
-typedef std::shared_ptr<Socket> SocketPtr;
-typedef std::pair<SocketPtr, SocketPtr> SocketPair;
-
-// On UNIX, make a connected pair of PF_LOCAL (aka PF_UNIX) sockets via the native 'socketpair'
-// call. The 'type' parameter should be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, etc.
-// For Win32, we don't have a native socketpair function, so we hack up a connected PF_INET
-// pair on a random port.
-SocketPair socketPair(int type, int protocol = 0);
-
-#if defined(_WIN32)
-namespace detail {
-void awaitAccept(SOCKET* acceptSock, SOCKET listenSock, Notification<void>& notify) {
- *acceptSock = INVALID_SOCKET;
- const SOCKET result = ::accept(listenSock, nullptr, 0);
- if (result != INVALID_SOCKET) {
- *acceptSock = result;
- }
- notify.set();
-}
-
-void awaitConnect(SOCKET* connectSock, const struct addrinfo& where, Notification<void>& notify) {
- *connectSock = INVALID_SOCKET;
- SOCKET newSock = ::socket(where.ai_family, where.ai_socktype, where.ai_protocol);
- if (newSock != INVALID_SOCKET) {
- int result = ::connect(newSock, where.ai_addr, where.ai_addrlen);
- if (result == 0) {
- *connectSock = newSock;
- }
- }
- notify.set();
-}
-} // namespace detail
-
-SocketPair socketPair(const int type, const int protocol) {
- const int domain = PF_INET;
-
- // Create a listen socket and a connect socket.
- const SOCKET listenSock = ::socket(domain, type, protocol);
- if (listenSock == INVALID_SOCKET)
- return SocketPair();
-
- // Bind the listen socket on port zero, it will pick one for us, and start it listening
- // for connections.
- struct addrinfo hints, *res;
- ::memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_INET;
- hints.ai_socktype = type;
- hints.ai_flags = AI_PASSIVE;
-
- int result = ::getaddrinfo(nullptr, "0", &hints, &res);
- if (result != 0) {
- closesocket(listenSock);
- return SocketPair();
- }
-
- result = ::bind(listenSock, res->ai_addr, res->ai_addrlen);
- if (result != 0) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- return SocketPair();
- }
-
- // Read out the port to which we bound.
- sockaddr_in bindAddr;
- ::socklen_t len = sizeof(bindAddr);
- ::memset(&bindAddr, 0, sizeof(bindAddr));
- result = ::getsockname(listenSock, reinterpret_cast<struct sockaddr*>(&bindAddr), &len);
- if (result != 0) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- return SocketPair();
- }
-
- result = ::listen(listenSock, 1);
- if (result != 0) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- return SocketPair();
- }
-
- struct addrinfo connectHints, *connectRes;
- ::memset(&connectHints, 0, sizeof(connectHints));
- connectHints.ai_family = PF_INET;
- connectHints.ai_socktype = type;
- std::stringstream portStream;
- portStream << ntohs(bindAddr.sin_port);
- result = ::getaddrinfo(nullptr, portStream.str().c_str(), &connectHints, &connectRes);
- if (result != 0) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- return SocketPair();
- }
-
- // I'd prefer to avoid trying to do this non-blocking on Windows. Just spin up some
- // threads to do the connect and acccept.
-
- Notification<void> accepted;
- SOCKET acceptSock = INVALID_SOCKET;
- stdx::thread acceptor([&] { detail::awaitAccept(&acceptSock, listenSock, accepted); });
-
- Notification<void> connected;
- SOCKET connectSock = INVALID_SOCKET;
- stdx::thread connector([&] { detail::awaitConnect(&connectSock, *connectRes, connected); });
-
- connected.get();
- connector.join();
- if (connectSock == INVALID_SOCKET) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- ::freeaddrinfo(connectRes);
- closesocket(acceptSock);
- closesocket(connectSock);
- return SocketPair();
- }
-
- accepted.get();
- acceptor.join();
- if (acceptSock == INVALID_SOCKET) {
- closesocket(listenSock);
- ::freeaddrinfo(res);
- ::freeaddrinfo(connectRes);
- closesocket(acceptSock);
- closesocket(connectSock);
- return SocketPair();
- }
-
- closesocket(listenSock);
- ::freeaddrinfo(res);
- ::freeaddrinfo(connectRes);
-
- SocketPtr first(new Socket(static_cast<int>(acceptSock), SockAddr()));
- SocketPtr second(new Socket(static_cast<int>(connectSock), SockAddr()));
-
- return SocketPair(first, second);
-}
-#else
-// We can just use ::socketpair and wrap up the result in a Socket.
-SocketPair socketPair(const int type, const int protocol) {
- // PF_LOCAL is the POSIX name for Unix domain sockets, while PF_UNIX
- // is the name that BSD used. We use the BSD name because it is more
- // widely supported (e.g. Solaris 10).
- const int domain = PF_UNIX;
-
- int socks[2];
- const int result = ::socketpair(domain, type, protocol, socks);
- if (result == 0) {
- return SocketPair(SocketPtr(new Socket(socks[0], SockAddr())),
- SocketPtr(new Socket(socks[1], SockAddr())));
- }
- return SocketPair();
-}
-#endif
// This should match the name of the fail point declared in sock.cpp.
const char kSocketFailPointName[] = "throwSockExcep";
diff --git a/src/mongo/util/net/sock_test_utils.cpp b/src/mongo/util/net/sock_test_utils.cpp
new file mode 100644
index 00000000000..9164b8077ca
--- /dev/null
+++ b/src/mongo/util/net/sock_test_utils.cpp
@@ -0,0 +1,199 @@
+/**
+ * Copyright (C) 2024-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/util/net/sock_test_utils.h"
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif
+
+#include "mongo/stdx/thread.h"
+#include "mongo/util/concurrency/notification.h"
+#include "mongo/util/net/socket_exception.h"
+
+namespace mongo {
+namespace {
+
+#if defined(_WIN32)
+namespace detail {
+void awaitAccept(SOCKET* acceptSock, SOCKET listenSock, Notification<void>& notify) {
+ *acceptSock = INVALID_SOCKET;
+ const SOCKET result = ::accept(listenSock, nullptr, 0);
+ if (result != INVALID_SOCKET) {
+ *acceptSock = result;
+ }
+ notify.set();
+}
+
+void awaitConnect(SOCKET* connectSock, const struct addrinfo& where, Notification<void>& notify) {
+ *connectSock = INVALID_SOCKET;
+ SOCKET newSock = ::socket(where.ai_family, where.ai_socktype, where.ai_protocol);
+ if (newSock != INVALID_SOCKET) {
+ int result = ::connect(newSock, where.ai_addr, where.ai_addrlen);
+ if (result == 0) {
+ *connectSock = newSock;
+ }
+ }
+ notify.set();
+}
+} // namespace detail
+
+SocketPair socketPairImpl(const int type, const int protocol) {
+ const int domain = PF_INET;
+
+ // Create a listen socket and a connect socket.
+ const SOCKET listenSock = ::socket(domain, type, protocol);
+ if (listenSock == INVALID_SOCKET)
+ return SocketPair();
+
+ // Bind the listen socket on port zero, it will pick one for us, and start it listening
+ // for connections.
+ struct addrinfo hints, *res;
+ ::memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = type;
+ hints.ai_flags = AI_PASSIVE;
+
+ int result = ::getaddrinfo(nullptr, "0", &hints, &res);
+ if (result != 0) {
+ closesocket(listenSock);
+ return SocketPair();
+ }
+
+ result = ::bind(listenSock, res->ai_addr, res->ai_addrlen);
+ if (result != 0) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ return SocketPair();
+ }
+
+ // Read out the port to which we bound.
+ sockaddr_in bindAddr;
+ ::socklen_t len = sizeof(bindAddr);
+ ::memset(&bindAddr, 0, sizeof(bindAddr));
+ result = ::getsockname(listenSock, reinterpret_cast<struct sockaddr*>(&bindAddr), &len);
+ if (result != 0) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ return SocketPair();
+ }
+
+ result = ::listen(listenSock, 1);
+ if (result != 0) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ return SocketPair();
+ }
+
+ struct addrinfo connectHints, *connectRes;
+ ::memset(&connectHints, 0, sizeof(connectHints));
+ connectHints.ai_family = PF_INET;
+ connectHints.ai_socktype = type;
+ std::stringstream portStream;
+ portStream << ntohs(bindAddr.sin_port);
+ result = ::getaddrinfo(nullptr, portStream.str().c_str(), &connectHints, &connectRes);
+ if (result != 0) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ return SocketPair();
+ }
+
+ // I'd prefer to avoid trying to do this non-blocking on Windows. Just spin up some
+ // threads to do the connect and acccept.
+
+ Notification<void> accepted;
+ SOCKET acceptSock = INVALID_SOCKET;
+ stdx::thread acceptor([&] { detail::awaitAccept(&acceptSock, listenSock, accepted); });
+
+ Notification<void> connected;
+ SOCKET connectSock = INVALID_SOCKET;
+ stdx::thread connector([&] { detail::awaitConnect(&connectSock, *connectRes, connected); });
+
+ connected.get();
+ connector.join();
+ if (connectSock == INVALID_SOCKET) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ ::freeaddrinfo(connectRes);
+ closesocket(acceptSock);
+ closesocket(connectSock);
+ return SocketPair();
+ }
+
+ accepted.get();
+ acceptor.join();
+ if (acceptSock == INVALID_SOCKET) {
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ ::freeaddrinfo(connectRes);
+ closesocket(acceptSock);
+ closesocket(connectSock);
+ return SocketPair();
+ }
+
+ closesocket(listenSock);
+ ::freeaddrinfo(res);
+ ::freeaddrinfo(connectRes);
+
+ SocketPtr first = std::make_shared<Socket>(static_cast<int>(acceptSock), SockAddr());
+ SocketPtr second = std::make_shared<Socket>(static_cast<int>(connectSock), SockAddr());
+ return SocketPair(first, second);
+}
+#else
+// We can just use ::socketpair and wrap up the result in a Socket.
+SocketPair socketPairImpl(const int type, const int protocol) {
+ // PF_LOCAL is the POSIX name for Unix domain sockets, while PF_UNIX
+ // is the name that BSD used. We use the BSD name because it is more
+ // widely supported (e.g. Solaris 10).
+ const int domain = PF_UNIX;
+
+ int socks[2];
+ const int result = ::socketpair(domain, type, protocol, socks);
+ if (result == 0) {
+ return SocketPair(std::make_shared<Socket>(socks[0], SockAddr()),
+ std::make_shared<Socket>(socks[1], SockAddr()));
+ }
+ return SocketPair();
+}
+#endif
+} // namespace
+
+// On UNIX, make a connected pair of PF_LOCAL (aka PF_UNIX) sockets via the native 'socketpair'
+// call. The 'type' parameter should be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, etc.
+// For Win32, we don't have a native socketpair function, so we hack up a connected PF_INET
+// pair on a random port.
+SocketPair socketPair(int type, int protocol) {
+ return socketPairImpl(type, protocol);
+}
+
+} // namespace mongo
diff --git a/src/mongo/util/net/sock_test_utils.h b/src/mongo/util/net/sock_test_utils.h
new file mode 100644
index 00000000000..f168edf1292
--- /dev/null
+++ b/src/mongo/util/net/sock_test_utils.h
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2024-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/util/net/sock.h"
+
+namespace mongo {
+using SocketPtr = std::shared_ptr<Socket>;
+using SocketPair = std::pair<SocketPtr, SocketPtr>;
+
+// Create a connected pair of sockets for testing purposes.
+SocketPair socketPair(int type, int protocol = 0);
+
+} // namespace mongo
diff --git a/src/mongo/util/net/sockaddr.cpp b/src/mongo/util/net/sockaddr.cpp
index 1a2b058cbf3..5bf2998df6d 100644
--- a/src/mongo/util/net/sockaddr.cpp
+++ b/src/mongo/util/net/sockaddr.cpp
@@ -149,7 +149,7 @@ void SockAddr::initUnixDomainSocket(StringData path, int port) {
uassert(
13079, "path to unix socket too long", path.size() < sizeof(as<sockaddr_un>().sun_path));
as<sockaddr_un>().sun_family = AF_UNIX;
- path.copyTo(as<sockaddr_un>().sun_path, /* includeEndingNull =*/true);
+ str::copyAsCString(as<sockaddr_un>().sun_path, path);
addressSize = sizeof(sockaddr_un);
_isValid = true;
}
diff --git a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp
index 015a44fcee8..b863cb0e75a 100644
--- a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp
+++ b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp
@@ -158,10 +158,6 @@ bool engine::_initSSL(stream_base::handshake_type type, asio::error_code& ec) {
}
if (status == ::errSecSuccess) {
- status = ::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(native_handle_type));
- }
-
- if (status == ::errSecSuccess) {
status = ::SSLSetIOFuncs(_ssl.get(), read_func, write_func);
}
diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp
index c13a4bb8eb8..dcde5542ca9 100644
--- a/src/mongo/util/net/ssl_manager_apple.cpp
+++ b/src/mongo/util/net/ssl_manager_apple.cpp
@@ -1156,7 +1156,6 @@ public:
}
uassertOSStatusOK(::SSLSetConnection(_ssl.get(), static_cast<void*>(this)));
- uassertOSStatusOK(::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(_ssl)));
uassertOSStatusOK(::SSLSetIOFuncs(_ssl.get(), read_func, write_func));
uassertOSStatusOK(::SSLSetProtocolVersionMin(_ssl.get(), ctx->protoMin));
uassertOSStatusOK(::SSLSetProtocolVersionMax(_ssl.get(), ctx->protoMax));
@@ -1654,11 +1653,14 @@ Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate(
return swPeerSubjectName.getStatus();
}
const auto peerSubjectName = std::move(swPeerSubjectName.getValue());
- LOGV2_DEBUG(23207,
- 2,
- "Accepted TLS connection from peer: {peerSubjectName}",
- "Accepted TLS connection from peer",
- "peerSubjectName"_attr = peerSubjectName);
+ // The cipher will be presented as a number.
+ ::SSLCipherSuite cipher;
+ uassertOSStatusOK(::SSLGetNegotiatedCipher(ssl, &cipher));
+
+ LOGV2_INFO(6723803,
+ "Accepted TLS connection from peer",
+ "peerSubjectName"_attr = peerSubjectName,
+ "cipher"_attr = cipher);
// Server side.
if (remoteHost.empty()) {
diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp
index 43c646bc16c..b27676156d6 100644
--- a/src/mongo/util/net/ssl_manager_openssl.cpp
+++ b/src/mongo/util/net/ssl_manager_openssl.cpp
@@ -319,23 +319,6 @@ X509* X509_OBJECT_get0_X509(const X509_OBJECT* a) {
return a->data.x509;
}
-UniqueStackOfX509 SSLgetVerifiedChain(SSL* s) {
- auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s));
- auto* peerChain = SSL_get_peer_cert_chain(s);
- UniqueX509 peer(SSL_get_peer_certificate(s));
-
- UniqueX509StoreCtx ctx(X509_STORE_CTX_new());
- if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) {
- return nullptr;
- }
-
- if (X509_verify_cert(ctx.get()) <= 0) {
- return nullptr;
- }
-
- return UniqueStackOfX509(X509_STORE_CTX_get1_chain(ctx.get()));
-}
-
const OCSP_CERTID* OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP* single) {
return single->certId;
}
@@ -368,16 +351,24 @@ static ASN1OID tlsFeatureOID("1.3.6.1.5.5.7.1.24", "tlsfeature", "TLS Feature");
static int const NID_tlsfeature = OBJ_create(tlsFeatureOID.identifier.c_str(),
tlsFeatureOID.shortDescription.c_str(),
tlsFeatureOID.longDescription.c_str());
+#endif
-#else
UniqueStackOfX509 SSLgetVerifiedChain(SSL* s) {
- auto chain = SSL_get0_verified_chain(s);
+ auto* store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(s));
+ auto* peerChain = SSL_get_peer_cert_chain(s);
+ UniqueX509 peer(SSL_get_peer_certificate(s));
- return UniqueStackOfX509(X509_chain_up_ref(chain));
-}
+ UniqueX509StoreCtx ctx(X509_STORE_CTX_new());
+ if (!X509_STORE_CTX_init(ctx.get(), store, peer.get(), peerChain)) {
+ return nullptr;
+ }
-#endif
+ if (X509_verify_cert(ctx.get()) <= 0) {
+ return nullptr;
+ }
+ return UniqueStackOfX509(X509_STORE_CTX_get1_chain(ctx.get()));
+}
SSLX509Name convertX509ToSSLX509Name(X509_NAME* x509Name) {
std::vector<std::vector<SSLX509Name::Entry>> entries;
@@ -2996,7 +2987,7 @@ bool SSLManagerOpenSSL::_setupCRL(SSL_CTX* context, const std::string& crlFile)
X509_STORE* store = SSL_CTX_get_cert_store(context);
fassert(16583, store);
- X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
+ X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
fassert(16584, lookup);
@@ -3010,15 +3001,7 @@ bool SSLManagerOpenSSL::_setupCRL(SSL_CTX* context, const std::string& crlFile)
return false;
}
- if (status == 1) {
- LOGV2(4652601, "ssl imported 1 revoked certificate from the revocation list.");
- } else {
- LOGV2(4652602,
- "ssl imported {numberCerts} revoked certificates from the revocation list",
- "SSL imported revoked certificates from the revocation list",
- "numberCerts"_attr = status);
- }
-
+ LOGV2(4652602, "SSL imported certificate revocation list(s)", "numberCRLs"_attr = status);
return true;
}
@@ -3280,11 +3263,11 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate(
// TODO: check optional cipher restriction, using cert.
auto peerSubject = getCertificateSubjectX509Name(peerCert.get());
- LOGV2_DEBUG(23229,
- 2,
- "Accepted TLS connection from peer: {peerSubject}",
- "Accepted TLS connection from peer",
- "peerSubject"_attr = peerSubject);
+ const auto cipher = SSL_get_current_cipher(conn);
+ LOGV2_INFO(6723801,
+ "Accepted TLS connection from peer",
+ "peerSubject"_attr = peerSubject,
+ "cipher"_attr = SSL_CIPHER_get_name(cipher));
StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles =
_parsePeerRoles(peerCert.get());
diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp
index 1dd6585f7c1..80e83af242a 100644
--- a/src/mongo/util/net/ssl_manager_test.cpp
+++ b/src/mongo/util/net/ssl_manager_test.cpp
@@ -29,15 +29,20 @@
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
+#include <asio.hpp>
+#include <boost/filesystem.hpp>
#include <fstream>
#include "mongo/config.h"
#include "mongo/platform/basic.h"
+#include "mongo/bson/json.h"
#include "mongo/transport/service_entry_point.h"
#include "mongo/transport/transport_layer_asio.h"
#include "mongo/transport/transport_layer_manager.h"
+#include "mongo/util/net/sock_test_utils.h"
#include "mongo/util/net/ssl/context.hpp"
+#include "mongo/util/net/ssl/stream.hpp"
#include "mongo/util/net/ssl_manager.h"
#include "mongo/util/net/ssl_options.h"
@@ -49,10 +54,36 @@
#include "mongo/util/net/ssl/context_openssl.hpp"
#endif
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
+
+namespace fs = boost::filesystem;
namespace mongo {
namespace {
+#define TEST_CERTS_DIR "jstests/libs/"
+// certs & CRLs rooted in ca.pem
+constexpr const char* caFile = TEST_CERTS_DIR "ca.pem";
+constexpr const char* serverKeyFile = TEST_CERTS_DIR "server.pem";
+constexpr const char* clientKeyFile = TEST_CERTS_DIR "client.pem";
+constexpr const char* revokedClientKeyFile = TEST_CERTS_DIR "client_revoked.pem";
+
+constexpr const char* intermediateACaFile = TEST_CERTS_DIR "intermediate-ca.pem";
+constexpr const char* intermediateALeafKeyFile = TEST_CERTS_DIR "server-intermediate-leaf.pem";
+constexpr const char* intermediateBCaFile = TEST_CERTS_DIR "intermediate-ca-B.pem";
+constexpr const char* intermediateBLeafKeyFile = TEST_CERTS_DIR "intermediate-ca-B-leaf.pem";
+constexpr const char* emptyCRL = TEST_CERTS_DIR "crl.pem";
+constexpr const char* expiredCRL = TEST_CERTS_DIR "crl_expired.pem";
+constexpr const char* clientRevokedCRL = TEST_CERTS_DIR "crl_client_revoked.pem";
+constexpr const char* intermediateBRevokedCRL = TEST_CERTS_DIR "crl_intermediate_ca_B_revoked.pem";
+constexpr const char* intermediateBCRL = TEST_CERTS_DIR "crl_from_intermediate_ca_B.pem";
+
+// certs & CRLs rooted in trusted-ca.pem
+constexpr const char* trustedCaFile = TEST_CERTS_DIR "trusted-ca.pem";
+constexpr const char* trustedServerKeyFile = TEST_CERTS_DIR "trusted-server.pem";
+constexpr const char* trustedClientKeyFile = TEST_CERTS_DIR "trusted-client.pem";
+constexpr const char* trustedEmptyCRL = TEST_CERTS_DIR "crl_from_trusted_ca.pem";
+
// Test implementation needed by ASIO transport.
class ServiceEntryPointUtil : public ServiceEntryPoint {
public:
@@ -103,7 +134,7 @@ public:
}
private:
- mutable Mutex _mutex = MONGO_MAKE_LATCH("::_mutex");
+ mutable Mutex _mutex;
stdx::condition_variable _cv;
std::vector<transport::SessionHandle> _sessions;
transport::TransportLayer* _transport = nullptr;
@@ -115,6 +146,76 @@ std::string loadFile(const std::string& name) {
return str;
}
+// Reads the input stream until EOF or a valid PEM block is encountered.
+// Skips private key PEM blocks if includePrivateKeys is true.
+// Returns the parsed PEM block as a string (with newlines), or an empty
+// string if none is found or a read error occurs.
+std::string readOnePEMBlock(std::ifstream& inputStrm, bool includePrivateKeys) {
+ std::string line;
+ for (;;) {
+ std::stringstream output;
+ bool foundBegin = false;
+ bool foundEnd = false;
+ bool discard = false;
+
+ while (!foundBegin && std::getline(inputStrm, line)) {
+ StringData lineSD(line);
+ foundBegin = (lineSD.startsWith("-----BEGIN ") && lineSD.endsWith("-----"));
+ }
+ if (!foundBegin) {
+ return "";
+ }
+
+ discard = (!includePrivateKeys && line.find("PRIVATE KEY") != std::string::npos);
+ output << line << std::endl;
+
+ while (!foundEnd && std::getline(inputStrm, line)) {
+ StringData lineSD(line);
+ output << line << std::endl;
+ foundEnd = (lineSD.startsWith("-----END ") && lineSD.endsWith("-----"));
+ }
+ if (!foundEnd) {
+ return "";
+ }
+ if (!discard) {
+ return output.str();
+ }
+ }
+}
+
+struct PEMFileSpec {
+ std::string path;
+ bool includePrivateKeys{false};
+ void serialize(BSONObjBuilder* bob) const {
+ bob->append("path", path);
+ bob->append("includePrivateKeys", includePrivateKeys);
+ }
+};
+// Given a list of PEM files, this concatenates the PEM blocks in those files
+// (optionally filtering out private keys) and writes the result into a temporary
+// file. Returns the path to the temp file.
+std::string combinePEMFiles(const std::vector<PEMFileSpec>& pemSpecs) {
+ // make a temp file for the output
+ auto path = fs::temp_directory_path() / fs::unique_path("tmpfile_%%%%_%%%%_%%%%_%%%%.pem");
+ std::ofstream outStream(path.string());
+ invariant(outStream.is_open());
+
+ LOGV2(
+ 9476600, "Combining PEM files", "output"_attr = path.string(), "pemFiles"_attr = pemSpecs);
+
+ // read & parse the PEM files; append PEM blocks to output
+ for (auto& pemSpec : pemSpecs) {
+ std::ifstream input(pemSpec.path);
+ std::string pemBlock;
+ do {
+ pemBlock = readOnePEMBlock(input, pemSpec.includePrivateKeys);
+ outStream << pemBlock;
+ } while (!pemBlock.empty());
+ }
+ outStream.close();
+ return path.string();
+}
+
TEST(SSLManager, matchHostname) {
enum Expected : bool { match = true, mismatch = false };
const struct {
@@ -734,6 +835,8 @@ TEST(SSLManager, TransientSSLParamsStressTestWithManager) {
#endif // MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL
+#ifdef MONGO_CONFIG_SSL
+
static bool isSanWarningWritten(const std::vector<std::string>& logLines) {
for (const auto& line : logLines) {
if (std::string::npos !=
@@ -780,6 +883,288 @@ TEST(SSLManager, InitContextNoSanWarning) {
ASSERT_FALSE(isSanWarningWritten(getCapturedTextFormatLogMessages()));
}
+class SSLTestFixture {
+public:
+ SSLTestFixture(const SSLParams& ingressParams,
+ const SSLParams& egressParams,
+ bool ingressIsServer = true,
+ bool egressIsServer = true,
+ const boost::optional<TransientSSLParams>& transientSSLParams = boost::none) {
+ auto serviceContext = ServiceContext::make();
+ setGlobalServiceContext(std::move(serviceContext));
+
+ // SSLManagerWindows uses this global boolean to decide whether to
+ // use unique key container names when setting up the crypto context.
+ // This must be true in order for the handshake to work.
+ isSSLServer = true;
+
+ serverSSLManager = SSLManagerInterface::create(ingressParams, ingressIsServer);
+ clientSSLManager =
+ SSLManagerInterface::create(egressParams, transientSSLParams, egressIsServer);
+
+ serverSSLContext = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
+ clientSSLContext = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
+ uassertStatusOK(
+ serverSSLManager->initSSLContext(serverSSLContext->native_handle(),
+ ingressParams,
+ SSLManagerInterface::ConnectionDirection::kIncoming));
+ uassertStatusOK(
+ clientSSLManager->initSSLContext(clientSSLContext->native_handle(),
+ egressParams,
+ SSLManagerInterface::ConnectionDirection::kOutgoing));
+ }
+
+ void doHandshake() {
+ auto socks = socketPair(SOCK_STREAM);
+
+ serverConn = std::make_shared<ConnectionContext>(socks.first->rawFD(), *serverSSLContext);
+ clientConn = std::make_shared<ConnectionContext>(socks.second->rawFD(), *clientSSLContext);
+ Status serverStatus = Status::OK();
+ Status clientStatus = Status::OK();
+
+ auto serverThread = stdx::thread([this, &serverStatus]() {
+ try {
+ serverConn->sslSocket->handshake(asio::ssl::stream_base::server);
+ } catch (const DBException& ex) {
+ serverStatus = ex.toStatus().withContext("Server handshake failed");
+ }
+ });
+
+ try {
+ clientConn->sslSocket->handshake(asio::ssl::stream_base::client);
+ } catch (const DBException& ex) {
+ clientStatus = ex.toStatus().withContext("Client handshake failed");
+ }
+ serverThread.join();
+
+ // rethrow any handshake errors with context
+ uassertStatusOK(serverStatus);
+ uassertStatusOK(clientStatus);
+ }
+
+ struct IngressEgressValidationResult {
+ StatusWith<SSLPeerInfo> ingress;
+ StatusWith<SSLPeerInfo> egress;
+ };
+ IngressEgressValidationResult runIngressEgressValidation();
+
+ class ConnectionContext {
+ public:
+ ConnectionContext(int fd, asio::ssl::context& ctx) : io_context() {
+ asio::ip::tcp::socket socket(io_context, asio::ip::tcp::v4(), fd);
+ sslSocket =
+ std::make_unique<asio::ssl::stream<decltype(socket)>>(std::move(socket), ctx, "");
+ }
+ asio::io_context io_context;
+ std::unique_ptr<asio::ssl::stream<asio::ip::tcp::socket>> sslSocket;
+ };
+
+ std::shared_ptr<SSLManagerInterface> clientSSLManager;
+ std::shared_ptr<SSLManagerInterface> serverSSLManager;
+
+ std::shared_ptr<asio::ssl::context> clientSSLContext;
+ std::shared_ptr<asio::ssl::context> serverSSLContext;
+
+ std::shared_ptr<ConnectionContext> clientConn;
+ std::shared_ptr<ConnectionContext> serverConn;
+};
+
+SSLTestFixture::IngressEgressValidationResult SSLTestFixture::runIngressEgressValidation() {
+ static const HostAndPort hostForLogging("hostforlogging");
+
+ // Caller must doHandshake beforehand
+ invariant(serverConn);
+ invariant(clientConn);
+
+ IngressEgressValidationResult result{SSLPeerInfo{}, SSLPeerInfo{}};
+
+ // do ingress (server) first
+ try {
+ result.ingress =
+ serverSSLManager
+ ->parseAndValidatePeerCertificate(serverConn->sslSocket->native_handle(),
+ boost::none,
+ "",
+ hostForLogging,
+ nullptr)
+ .get();
+ } catch (const DBException& ex) {
+ result.ingress = ex.toStatus();
+ }
+
+ // do egress (client) next
+ try {
+ result.egress =
+ clientSSLManager
+ ->parseAndValidatePeerCertificate(clientConn->sslSocket->native_handle(),
+ boost::none,
+ "localhost",
+ hostForLogging,
+ nullptr)
+ .get();
+ } catch (const DBException& ex) {
+ result.egress = ex.toStatus();
+ }
+
+ return result;
+}
+
+struct CertValidationTestCase {
+ std::string cafile;
+ std::string clusterCaFile;
+ bool pass;
+ bool allowInvalidCerts{false};
+
+ void serialize(BSONObjBuilder* bob) const {
+ bob->append("CAFile", cafile);
+ bob->append("clusterCAFile", clusterCaFile);
+ bob->append("expectPass", pass);
+ bob->append("allowInvalidCerts", allowInvalidCerts);
+ }
+};
+
+void checkValidationResults(SSLTestFixture::IngressEgressValidationResult& result,
+ bool expectIngressPass,
+ bool expectEgressPass,
+ ErrorCodes::Error expectIngressCode = ErrorCodes::SSLHandshakeFailed,
+ ErrorCodes::Error expectEgressCode = ErrorCodes::SSLHandshakeFailed) {
+ ASSERT_EQ(result.ingress.isOK(), expectIngressPass)
+ << "Ingress validation status: " << result.ingress.getStatus();
+ ASSERT_EQ(result.egress.isOK(), expectEgressPass)
+ << "Egress validation status: " << result.egress.getStatus();
+ if (!result.ingress.isOK()) {
+ ASSERT_EQ(result.ingress.getStatus().code(), expectIngressCode)
+ << "Ingress validation status: " << result.ingress.getStatus();
+ }
+ if (!result.egress.isOK()) {
+ ASSERT_EQ(result.egress.getStatus().code(), expectEgressCode)
+ << "Egress validation status: " << result.egress.getStatus();
+ }
+}
+
+// Tests that validation fails if configured CRL for the issuer of the peer certificate being
+// validated has expired.
+// Caveats:
+// - Apple: CRL unsupported; test disabled
+// - Windows: validation fails, but with misleading error message
+#if MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE
+TEST(SSLManager, expiredCRLTest) {
+ SSLParams clientParams;
+ clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ clientParams.sslAllowInvalidHostnames = true;
+ clientParams.sslCAFile = caFile;
+ clientParams.sslPEMKeyFile = clientKeyFile;
+ clientParams.sslCRLFile = expiredCRL;
+
+ SSLParams serverParams;
+ serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ serverParams.sslAllowInvalidHostnames = true;
+ serverParams.sslCAFile = caFile;
+ serverParams.sslPEMKeyFile = serverKeyFile;
+ serverParams.sslCRLFile = expiredCRL;
+
+ SSLTestFixture tf(serverParams, clientParams);
+ tf.doHandshake();
+ auto result = tf.runIngressEgressValidation();
+ checkValidationResults(result, false /*expectIngressPass*/, false /*expectEgressPass*/);
+
+#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
+ constexpr const char* cause = "revocation server was offline";
+#else
+ constexpr const char* cause = "expired";
+#endif
+ ASSERT_NE(result.ingress.getStatus().reason().find(cause), std::string::npos);
+ ASSERT_NE(result.egress.getStatus().reason().find(cause), std::string::npos);
+}
+
+// Tests basic CRL revocation works on ingress if the client is configured with a revoked key.
+// Caveats:
+// - Apple: CRL unsupported; test disabled
+TEST(SSLManager, basicCRLRevocationTests) {
+ struct TestCase {
+ std::string serverCRLFile;
+ bool serverPass;
+ void serialize(BSONObjBuilder* bob) const {
+ bob->append("serverCRLFile", serverCRLFile);
+ bob->append("serverPass", serverPass);
+ }
+ };
+
+ SSLParams clientParams;
+ clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ clientParams.sslAllowInvalidHostnames = true;
+ clientParams.sslCAFile = trustedCaFile;
+ clientParams.sslPEMKeyFile = revokedClientKeyFile;
+
+ SSLParams serverParams;
+ serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ serverParams.sslAllowInvalidHostnames = true;
+ serverParams.sslCAFile = caFile;
+ serverParams.sslPEMKeyFile = trustedServerKeyFile;
+
+ {
+ serverParams.sslCRLFile = emptyCRL;
+ LOGV2(9476702, "Running test case", "CRLFile"_attr = emptyCRL, "pass"_attr = true);
+ SSLTestFixture tf(serverParams, clientParams);
+ tf.doHandshake();
+ auto result = tf.runIngressEgressValidation();
+ checkValidationResults(result, true, true /*expectEgressPass*/);
+ }
+ {
+ serverParams.sslCRLFile = clientRevokedCRL;
+ LOGV2(9476703, "Running test case", "CRLFile"_attr = clientRevokedCRL, "pass"_attr = false);
+ SSLTestFixture tf(serverParams, clientParams);
+ tf.doHandshake();
+ auto result = tf.runIngressEgressValidation();
+ checkValidationResults(result, false, true /*expectEgressPass*/);
+ ASSERT_NE(result.ingress.getStatus().reason().find("revoked"), std::string::npos);
+ }
+}
+
+// Tests whether validation passes if an intermediate CA issuer cert is revoked, but
+// the end-entity cert is not.
+// Caveats:
+// - Apple: CRL unsupported; test disabled
+// - Windows: multiple CRLs (root CRL + intermediate CRL) is not allowed
+// TODO: backport SERVER-95583
+TEST(SSLManager, revocationWithCRLsIntermediateTests) {
+ // intermediate-ca-B.pem + intermediate-ca-B-leaf.pem bundle
+ const std::string intermediateBLeafWithIssuerCertKeyFile = combinePEMFiles(
+ {{intermediateBLeafKeyFile, true /*includePrivKey*/}, {intermediateBCaFile}});
+ // crl_from_intermediate_ca_B.pem + crl_intermediate_ca_B_revoked.pem
+ const std::string crlsFromRootAndIntermediateB =
+ combinePEMFiles({{intermediateBRevokedCRL}, {intermediateBCRL}});
+
+ SSLParams clientParams;
+ clientParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ clientParams.sslAllowInvalidHostnames = true;
+ clientParams.sslCAFile = caFile;
+ clientParams.sslPEMKeyFile = clientKeyFile;
+ clientParams.sslCRLFile = crlsFromRootAndIntermediateB;
+
+ SSLParams serverParams;
+ serverParams.sslMode.store(::mongo::sslGlobalParams.SSLMode_requireSSL);
+ serverParams.sslAllowInvalidHostnames = true;
+ serverParams.sslCAFile = caFile;
+ serverParams.sslPEMKeyFile = intermediateBLeafWithIssuerCertKeyFile;
+
+#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
+ ASSERT_THROWS_CODE_AND_WHAT(
+ SSLManagerInterface::create(clientParams, true),
+ DBException,
+ ErrorCodes::InvalidSSLConfiguration,
+ "CertAddCRLContextToStore Failed The object or property already exists.");
+#else
+ SSLTestFixture tf(serverParams, clientParams);
+ tf.doHandshake();
+ auto result = tf.runIngressEgressValidation();
+ checkValidationResults(result, true, false);
+ ASSERT_NE(result.egress.getStatus().reason().find("revoked"), std::string::npos);
+#endif
+}
+
+#endif // MONGO_CONFIG_SSL_PROVIDER != MONGO_CONFIG_SSL_PROVIDER_APPLE
+#endif // MONGO_CONFIG_SSL
} // namespace
} // namespace mongo
diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp
index b88ae85e5b4..7169e1eef1f 100644
--- a/src/mongo/util/net/ssl_manager_windows.cpp
+++ b/src/mongo/util/net/ssl_manager_windows.cpp
@@ -2062,10 +2062,19 @@ Future<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate(
return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni));
}
- LOGV2_DEBUG(23270,
- 2,
- "Accepted TLS connection from peer: {peerSubjectName}",
- "peerSubjectName"_attr = peerSubjectName);
+ SecPkgContext_CipherInfo cipherInfo;
+ SECURITY_STATUS ssCipher = QueryContextAttributes(ssl, SECPKG_ATTR_CIPHER_INFO, &cipherInfo);
+ if (ssCipher != SEC_E_OK) {
+ return Status(ErrorCodes::SSLHandshakeFailed,
+ str::stream()
+ << "QueryContextAttributes for connection info failed with" << ssCipher);
+ }
+ const auto cipher = std::wstring(cipherInfo.szCipherSuite);
+
+ LOGV2_INFO(6723802,
+ "Accepted TLS connection from peer",
+ "peerSubjectName"_attr = peerSubjectName,
+ "cipher"_attr = toUtf8String(cipher));
// If this is a server and client and server certificate are the same, log a warning.
if (remoteHost.empty() && _sslConfiguration.serverSubjectName() == peerSubjectName) {