Commit d9b854ef authored by Mingtao Yang's avatar Mingtao Yang Committed by Facebook Github Bot

Make getSSLServerName() return SNI presented in CH, if available

Summary:
If CH parsing is enabled, also parse out the ServerName extension. OpenSSL 1.1.1
changes the behavior of `SSL_get_servername`: an SNI value is stored in
the underlying SESSION if and only if both parties negotiated that SNI.

There are some situations where one would wish to retrieve the original
ServerName that the client sent.

Reviewed By: knekritz

Differential Revision: D17893443

fbshipit-source-id: b29ee42e90629c869dd5e68c93c7cb2abc19745f
parent 615a44f0
......@@ -897,6 +897,9 @@ const char* AsyncSSLSocket::getSSLServerNameFromSSL(SSL* ssl) {
}
const char* AsyncSSLSocket::getSSLServerName() const {
if (clientHelloInfo_ && !clientHelloInfo_->clientHelloSNIHostname_.empty()) {
return clientHelloInfo_->clientHelloSNIHostname_.c_str();
}
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
return getSSLServerNameFromSSL(ssl_.get());
#else
......@@ -906,6 +909,9 @@ const char* AsyncSSLSocket::getSSLServerName() const {
}
const char* AsyncSSLSocket::getSSLServerNameNoThrow() const {
if (clientHelloInfo_ && !clientHelloInfo_->clientHelloSNIHostname_.empty()) {
return clientHelloInfo_->clientHelloSNIHostname_.c_str();
}
return getSSLServerNameFromSSL(ssl_.get());
}
......@@ -1874,6 +1880,34 @@ void AsyncSSLSocket::clientHelloParsingCallback(
cursor.readBE<uint16_t>());
extensionDataLength -= 2;
}
} else if (extensionType == ssl::TLSExtension::SERVER_NAME) {
cursor.skip(2);
extensionDataLength -= 2;
while (extensionDataLength) {
static_assert(
std::is_same<
typename std::underlying_type<ssl::NameType>::type,
uint8_t>::value,
"unexpected underlying type");
ssl::NameType typ =
static_cast<ssl::NameType>(cursor.readBE<uint8_t>());
uint16_t nameLength = cursor.readBE<uint16_t>();
if (typ == NameType::HOST_NAME &&
sock->clientHelloInfo_->clientHelloSNIHostname_.empty() &&
cursor.canAdvance(nameLength)) {
sock->clientHelloInfo_->clientHelloSNIHostname_ =
cursor.readFixedString(nameLength);
} else {
// Must attempt to skip |nameLength| in order to keep cursor
// in sync. If the remaining buffer length is smaller than
// nameLength, this will throw.
cursor.skip(nameLength);
}
extensionDataLength -=
sizeof(typ) + sizeof(nameLength) + nameLength;
}
} else {
cursor.skip(extensionDataLength);
}
......
......@@ -526,7 +526,9 @@ class AsyncSSLSocket : public virtual AsyncSocket {
virtual const char* getNegotiatedCipherName() const;
/**
* Get the server name for this SSL connection.
* Get the server name for this SSL connection. Returns the SNI sent in the
* ClientHello, if enableClientHelloParsing() was called.
*
* Returns the server name used or the constant value "NONE" when no SSL
* session has been established.
* If openssl has no SNI support, throw AsyncSocketException.
......
......@@ -77,6 +77,10 @@ enum class SignatureAlgorithm : uint8_t {
ECDSA = 3
};
enum class NameType : uint8_t {
HOST_NAME = 0,
};
struct ClientHelloInfo {
folly::IOBufQueue clientHelloBuf_;
uint8_t clientHelloMajorVersion_;
......@@ -86,6 +90,11 @@ struct ClientHelloInfo {
std::vector<TLSExtension> clientHelloExtensions_;
std::vector<std::pair<HashAlgorithm, SignatureAlgorithm>> clientHelloSigAlgs_;
std::vector<uint16_t> clientHelloSupportedVersions_;
// Technically, the TLS spec allows for multiple ServerNames to be sent (as
// long as each ServerName has a distinct type). In practice, the only one
// we really care about is HOST_NAME.
std::string clientHelloSNIHostname_;
};
} // namespace ssl
......
......@@ -2704,6 +2704,87 @@ TEST(AsyncSSLSocketTest, SendMsgDataCallback) {
#endif
TEST(AsyncSSLSocketTest, TestSNIClientHelloBehavior) {
EventBase eventBase;
auto serverCtx = std::make_shared<SSLContext>();
auto clientCtx = std::make_shared<SSLContext>();
serverCtx->loadPrivateKey(kTestKey);
serverCtx->loadCertificate(kTestCert);
clientCtx->setSessionCacheContext("test context");
clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
SSL_SESSION* resumptionSession = nullptr;
{
std::array<NetworkSocket, 2> fds;
getfds(fds.data());
AsyncSSLSocket::UniquePtr clientSock(
new AsyncSSLSocket(clientCtx, &eventBase, fds[0], false));
AsyncSSLSocket::UniquePtr serverSock(
new AsyncSSLSocket(serverCtx, &eventBase, fds[1], true));
// Client sends SNI that doesn't match anything the server cert advertises
clientSock->setServerName("Foobar");
SSLHandshakeServerParseClientHello server(
std::move(serverSock), true, true);
SSLHandshakeClient client(std::move(clientSock), true, true);
eventBase.loop();
serverSock = std::move(server).moveSocket();
auto chi = serverSock->getClientHelloInfo();
ASSERT_NE(chi, nullptr);
EXPECT_EQ(
std::string("Foobar"), std::string(serverSock->getSSLServerName()));
// create another client, resuming with the prior session, but under a
// different common name.
clientSock = std::move(client).moveSocket();
resumptionSession = clientSock->getSSLSession();
}
{
std::array<NetworkSocket, 2> fds;
getfds(fds.data());
AsyncSSLSocket::UniquePtr clientSock(
new AsyncSSLSocket(clientCtx, &eventBase, fds[0], false));
AsyncSSLSocket::UniquePtr serverSock(
new AsyncSSLSocket(serverCtx, &eventBase, fds[1], true));
clientSock->setSSLSession(resumptionSession, true);
clientSock->setServerName("Baz");
SSLHandshakeServerParseClientHello server(
std::move(serverSock), true, true);
SSLHandshakeClient client(std::move(clientSock), true, true);
eventBase.loop();
serverSock = std::move(server).moveSocket();
clientSock = std::move(client).moveSocket();
EXPECT_TRUE(clientSock->getSSLSessionReused());
// OpenSSL 1.1.1 changes the semantics of SSL_get_servername
// in
// https://github.com/openssl/openssl/commit/1c4aa31d79821dee9be98e915159d52cc30d8403
//
// Previously, the SNI would be taken from the ClientHello.
// Now, the SNI will be taken from the established session.
//
// But the session that was established with the client (prior handshake)
// would not have set the server name field because the SNI that the client
// requested ("Foobar") did not match any of the SANs that the server was
// presenting ("127.0.0.1")
//
// To preserve this 1.1.0 behavior, getSSLServerName() should return the
// parsed ClientHello servername. This test asserts this behavior.
auto sni = serverSock->getSSLServerName();
ASSERT_NE(sni, nullptr);
std::string sniStr(sni);
EXPECT_EQ(sniStr, std::string("Baz"));
}
}
} // namespace folly
#ifdef SIGPIPE
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment