summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2018-02-15 15:30:46 -0500
committerSpencer Jackson <spencer.jackson@mongodb.com>2018-05-03 19:41:54 -0400
commit56e653fdd204e1ad091e0736454aefc005b5ce3f (patch)
tree1ec4f279862aeea0061d04619e418b3913de2aa3 /src
parentfb710fbfcbe9f3479c8ef6bf636f89cc58bfc2be (diff)
downloadmongo-56e653fdd204e1ad091e0736454aefc005b5ce3f.tar.gz
SERVER-33329: Make server and shell emit TLS protocol_version alerts
(cherry picked from commit 51af489a86f1862de87b51f26a9e818ec3b5df04)
Diffstat (limited to 'src')
-rw-r--r--src/mongo/transport/session_asio.h17
-rw-r--r--src/mongo/util/net/message_port.cpp12
-rw-r--r--src/mongo/util/net/ssl_manager.cpp156
-rw-r--r--src/mongo/util/net/ssl_manager.h7
4 files changed, 184 insertions, 8 deletions
diff --git a/src/mongo/transport/session_asio.h b/src/mongo/transport/session_asio.h
index 6128d1515d6..59b8f8f57c8 100644
--- a/src/mongo/transport/session_asio.h
+++ b/src/mongo/transport/session_asio.h
@@ -283,6 +283,23 @@ private:
false);
}
+ auto tlsAlert = checkTLSRequest(
+ ConstDataRange(asio::buffer_cast<const char*>(buffer), asio::buffer_size(buffer)));
+ if (tlsAlert) {
+ return opportunisticWrite(
+ sync,
+ _socket,
+ asio::buffer(tlsAlert->data(), tlsAlert->size()),
+ [ this, onComplete = std::move(onComplete) ](const std::error_code& ec,
+ size_t size) {
+ return onComplete(
+ {ErrorCodes::SSLHandshakeFailed,
+ "SSL handshake failed, as client requested disabled protocol"},
+ false);
+ });
+ }
+
+
_sslSocket.emplace(std::move(_socket), *_tl->_sslContext);
auto handshakeCompleteCb = [ this, onComplete = std::move(onComplete) ](
diff --git a/src/mongo/util/net/message_port.cpp b/src/mongo/util/net/message_port.cpp
index 17fb8a30497..e5bc637af4d 100644
--- a/src/mongo/util/net/message_port.cpp
+++ b/src/mongo/util/net/message_port.cpp
@@ -126,6 +126,18 @@ bool MessagingPort::recv(Message& m) {
uassert(17132,
"SSL handshake received but server is started without SSL support",
sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled);
+
+ auto tlsAlert = checkTLSRequest(
+ ConstDataRange(reinterpret_cast<const char*>(&header), sizeof(header)));
+
+ if (tlsAlert) {
+ _psock->send(reinterpret_cast<const char*>(tlsAlert->data()),
+ tlsAlert->size(),
+ "tls protocol mismatch");
+ log() << "SSL handshake failed, as client requested disabled protocol";
+ return false;
+ }
+
setX509PeerInfo(
_psock->doSSLHandshake(reinterpret_cast<const char*>(&header), sizeof(header)));
LOG(1) << "new ssl connection, SNI server name [" << _psock->getSNIServerName()
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index c1c68e31926..c7a023a7753 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -422,7 +422,7 @@ private:
* Given an error code from an SSL-type IO function, logs an
* appropriate message and throws a SocketException.
*/
- MONGO_COMPILER_NORETURN void _handleSSLError(int code, int ret);
+ MONGO_COMPILER_NORETURN void _handleSSLError(SSLConnection* conn, int ret);
/*
* Init the SSL context using parameters provided in params. This SSL context will
@@ -716,7 +716,7 @@ int SSLManager::SSL_read(SSLConnection* conn, void* buf, int num) {
} while (!_doneWithSSLOp(conn, status));
if (status <= 0)
- _handleSSLError(SSL_get_error(conn, status), status);
+ _handleSSLError(conn, status);
return status;
}
@@ -727,7 +727,7 @@ int SSLManager::SSL_write(SSLConnection* conn, const void* buf, int num) {
} while (!_doneWithSSLOp(conn, status));
if (status <= 0)
- _handleSSLError(SSL_get_error(conn, status), status);
+ _handleSSLError(conn, status);
return status;
}
@@ -750,7 +750,7 @@ int SSLManager::SSL_shutdown(SSLConnection* conn) {
} while (!_doneWithSSLOp(conn, status));
if (status < 0)
- _handleSSLError(SSL_get_error(conn, status), status);
+ _handleSSLError(conn, status);
return status;
}
@@ -1293,14 +1293,14 @@ SSLConnection* SSLManager::connect(Socket* socket) {
const auto undotted = removeFQDNRoot(socket->remoteAddr().hostOrIp());
int ret = ::SSL_set_tlsext_host_name(sslConn->ssl, undotted.c_str());
if (ret != 1)
- _handleSSLError(SSL_get_error(sslConn.get(), ret), ret);
+ _handleSSLError(sslConn.get(), ret);
do {
ret = ::SSL_connect(sslConn->ssl);
} while (!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
- _handleSSLError(SSL_get_error(sslConn.get(), ret), ret);
+ _handleSSLError(sslConn.get(), ret);
return sslConn.release();
}
@@ -1315,7 +1315,7 @@ SSLConnection* SSLManager::accept(Socket* socket, const char* initialBytes, int
} while (!_doneWithSSLOp(sslConn.get(), ret));
if (ret != 1)
- _handleSSLError(SSL_get_error(sslConn.get(), ret), ret);
+ _handleSSLError(sslConn.get(), ret);
return sslConn.release();
}
@@ -1570,7 +1570,8 @@ std::string SSLManagerInterface::getSSLErrorMessage(int code) {
return msg;
}
-void SSLManager::_handleSSLError(int code, int ret) {
+void SSLManager::_handleSSLError(SSLConnection* conn, int ret) {
+ int code = SSL_get_error(conn, ret);
int err = ERR_get_error();
switch (code) {
@@ -1607,6 +1608,7 @@ void SSLManager::_handleSSLError(int code, int ret) {
error() << "unrecognized SSL error";
break;
}
+ _flushNetworkBIO(conn);
throw SocketException(SocketException::CONNECT_ERROR, "");
}
} // namespace mongo
@@ -1630,6 +1632,144 @@ bool mongo::hostNameMatchForX509Certificates(std::string nameToMatch, std::strin
}
}
+boost::optional<std::array<std::uint8_t, 7>> mongo::checkTLSRequest(ConstDataRange dataRange) {
+ // This method's caller should have read in at least one MSGHEADER::Value's worth of data.
+ // The fragment we are about to examine must be strictly smaller.
+ static const size_t sizeOfTLSFragmentToRead = 11;
+ invariant(dataRange.length() >= sizeOfTLSFragmentToRead);
+
+ static_assert(sizeOfTLSFragmentToRead < sizeof(MSGHEADER::Value),
+ "checkTLSRequest's caller read a MSGHEADER::Value, which must be larger than "
+ "message containing the TLS version");
+
+ ConstDataRangeCursor cdr(dataRange);
+
+ /**
+ * The fragment we are to examine is a record, containing a handshake, containing a
+ * ClientHello. We wish to examine the advertised protocol version in the ClientHello.
+ * The following roughly describes the contents of these structures. Note that we do not
+ * need, or wish to, examine the entire ClientHello, we're looking exclusively for the
+ * client_version.
+ *
+ * Below is a rough description of the payload we will be examining. We shall perform some
+ * basic checks to ensure the payload matches these expectations. If it does not, we should
+ * bail out, and not emit protocol version alerts.
+ *
+ * enum {alert(21), handshake(22)} ContentType;
+ * TLSPlaintext {
+ * ContentType type = handshake(22),
+ * ProtocolVersion version; // Irrelevant. Clients send the real version in ClientHello.
+ * uint16 length;
+ * fragment, see Handshake stuct for contents
+ * ...
+ * }
+ *
+ * enum {client_hello(1)} HandshakeType;
+ * Handshake {
+ * HandshakeType msg_type = client_hello(1);
+ * uint24_t length;
+ * ClientHello body;
+ * }
+ *
+ * ClientHello {
+ * ProtocolVersion client_version; // <- This is the value we want to extract.
+ * }
+ */
+
+ static const std::uint8_t ContentType_handshake = 22;
+ static const std::uint8_t HandshakeType_client_hello = 1;
+
+ using ProtocolVersion = std::array<std::uint8_t, 2>;
+ static const ProtocolVersion tls10VersionBytes{3, 1};
+ static const ProtocolVersion tls11VersionBytes{3, 2};
+
+ // Parse the record header.
+ // Extract the ContentType from the header, and ensure it is a handshake.
+ StatusWith<std::uint8_t> record_ContentType = cdr.readAndAdvance<std::uint8_t>();
+ if (!record_ContentType.isOK() || record_ContentType.getValue() != ContentType_handshake) {
+ return boost::none;
+ }
+ // Skip the record's ProtocolVersion. Clients tend to send TLS 1.0 in
+ // the record, but then their real protocol version in the enclosed ClientHello.
+ StatusWith<ProtocolVersion> record_protocol_version = cdr.readAndAdvance<ProtocolVersion>();
+ if (!record_protocol_version.isOK()) {
+ return boost::none;
+ }
+ // Parse the record length. It should be be larger than the remaining expected payload.
+ auto record_length = cdr.readAndAdvance<BigEndian<std::uint16_t>>();
+ if (!record_length.isOK() || record_length.getValue() < cdr.length()) {
+ return boost::none;
+ }
+
+ // Parse the handshake header.
+ // Extract the HandshakeType, and ensure it is a ClientHello.
+ StatusWith<std::uint8_t> handshake_type = cdr.readAndAdvance<std::uint8_t>();
+ if (!handshake_type.isOK() || handshake_type.getValue() != HandshakeType_client_hello) {
+ return boost::none;
+ }
+ // Extract the handshake length, and ensure it is larger than the remaining expected
+ // payload. This requires a little work because the packet represents it with a uint24_t.
+ StatusWith<std::array<std::uint8_t, 3>> handshake_length_bytes =
+ cdr.readAndAdvance<std::array<std::uint8_t, 3>>();
+ if (!handshake_length_bytes.isOK()) {
+ return boost::none;
+ }
+ std::uint32_t handshake_length = 0;
+ for (std::uint8_t handshake_byte : handshake_length_bytes.getValue()) {
+ handshake_length <<= 8;
+ handshake_length |= handshake_byte;
+ }
+ if (handshake_length < cdr.length()) {
+ return boost::none;
+ }
+ StatusWith<ProtocolVersion> client_version = cdr.readAndAdvance<ProtocolVersion>();
+ if (!client_version.isOK()) {
+ return boost::none;
+ }
+
+ // Invariant: We read exactly as much data as expected.
+ invariant(cdr.data() - dataRange.data() == sizeOfTLSFragmentToRead);
+
+ auto isProtocolDisabled = [](SSLParams::Protocols protocol) {
+ const auto& params = getSSLGlobalParams();
+ return std::find(params.sslDisabledProtocols.begin(),
+ params.sslDisabledProtocols.end(),
+ protocol) != params.sslDisabledProtocols.end();
+ };
+
+ auto makeTLSProtocolVersionAlert =
+ [](const std::array<std::uint8_t, 2>& versionBytes) -> std::array<std::uint8_t, 7> {
+ /**
+ * The structure for this alert packet is as follows:
+ * TLSPlaintext {
+ * ContentType type = alert(21);
+ * ProtocolVersion = versionBytes;
+ * uint16_t length = 2
+ * fragment = AlertDescription {
+ * AlertLevel level = fatal(2);
+ * AlertDescription = protocol_version(70);
+ * }
+ *
+ */
+ return std::array<std::uint8_t, 7>{
+ 0x15, versionBytes[0], versionBytes[1], 0x00, 0x02, 0x02, 0x46};
+ };
+
+ ProtocolVersion version = client_version.getValue();
+ if (version == tls10VersionBytes && isProtocolDisabled(SSLParams::Protocols::TLS1_0)) {
+ return makeTLSProtocolVersionAlert(version);
+ } else if (client_version == tls11VersionBytes &&
+ isProtocolDisabled(SSLParams::Protocols::TLS1_1)) {
+ return makeTLSProtocolVersionAlert(version);
+ }
+ // TLS1.2 cannot be distinguished from TLS1.3, just by looking at the ProtocolVersion bytes.
+ // TLS 1.3 compatible clients advertise a "supported_versions" extension, which we would
+ // have to extract here.
+ // Hopefully by the time this matters, OpenSSL will properly emit protocol_version alerts.
+
+ return boost::none;
+}
+
#else
namespace mongo {
diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h
index 002a0dd6be7..943651a64c6 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -206,5 +206,12 @@ const SSLParams& getSSLGlobalParams();
* x.509 certificate. Matches a remote host name to an x.509 host name, including wildcards.
*/
bool hostNameMatchForX509Certificates(std::string nameToMatch, std::string certHostName);
+
+/**
+ * Peeks at a fragment of a client issued TLS handshake packet. Returns a TLS alert
+ * packet if the client has selected a protocol which has been disabled by the server.
+ */
+boost::optional<std::array<std::uint8_t, 7>> checkTLSRequest(ConstDataRange cdr);
+
} // namespace mongo
#endif // #ifdef MONGO_CONFIG_SSL