Commit 67dac89c authored by Anirudh Ramachandran's avatar Anirudh Ramachandran Committed by Facebook Github Bot 3

AsyncSSLSocket::getSSLClientCiphers using static map

Summary:
OpenSSL SSL_METHOD->get_cipher_by_char is not present in either OpenSSL
1.1.0 or BoringSSL. In addition, knekritz reports that time's being spent in
binary searching for cipher names.

Since the ciphercodes and names are (fairly) static, we store these in a static
hash map.

Reviewed By: siyengar

Differential Revision: D3275185

fbshipit-source-id: 08b36f3e73239b415b74c6ecc30ed65832d9ebd0
parent 8bed0626
......@@ -1739,4 +1739,114 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written,
sock->resetClientHelloParsing(ssl);
}
void AsyncSSLSocket::getSSLClientCiphers(
std::string& clientCiphers,
bool convertToString) const {
std::string ciphers;
if (parseClientHello_ == false
|| clientHelloInfo_->clientHelloCipherSuites_.empty()) {
clientCiphers = "";
return;
}
bool first = true;
for (auto originalCipherCode : clientHelloInfo_->clientHelloCipherSuites_)
{
if (first) {
first = false;
} else {
ciphers += ":";
}
bool nameFound = convertToString;
if (convertToString) {
const auto& name = OpenSSLUtils::getCipherName(originalCipherCode);
if (name.empty()) {
nameFound = false;
} else {
ciphers += name;
}
}
if (!nameFound) {
folly::hexlify(
std::array<uint8_t, 2>{{
static_cast<uint8_t>((originalCipherCode >> 8) & 0xffL),
static_cast<uint8_t>(originalCipherCode & 0x00ffL) }},
ciphers,
/* append to ciphers = */ true);
}
}
clientCiphers = std::move(ciphers);
}
std::string AsyncSSLSocket::getSSLClientComprMethods() const {
if (!parseClientHello_) {
return "";
}
return folly::join(":", clientHelloInfo_->clientHelloCompressionMethods_);
}
std::string AsyncSSLSocket::getSSLClientExts() const {
if (!parseClientHello_) {
return "";
}
return folly::join(":", clientHelloInfo_->clientHelloExtensions_);
}
std::string AsyncSSLSocket::getSSLClientSigAlgs() const {
if (!parseClientHello_) {
return "";
}
std::string sigAlgs;
sigAlgs.reserve(clientHelloInfo_->clientHelloSigAlgs_.size() * 4);
for (size_t i = 0; i < clientHelloInfo_->clientHelloSigAlgs_.size(); i++) {
if (i) {
sigAlgs.push_back(':');
}
sigAlgs.append(folly::to<std::string>(
clientHelloInfo_->clientHelloSigAlgs_[i].first));
sigAlgs.push_back(',');
sigAlgs.append(folly::to<std::string>(
clientHelloInfo_->clientHelloSigAlgs_[i].second));
}
return sigAlgs;
}
std::string AsyncSSLSocket::getSSLAlertsReceived() const {
std::string ret;
for (const auto& alert : alertsReceived_) {
if (!ret.empty()) {
ret.append(",");
}
ret.append(folly::to<std::string>(alert.first, ": ", alert.second));
}
return ret;
}
void AsyncSSLSocket::getSSLSharedCiphers(std::string& sharedCiphers) const {
char ciphersBuffer[1024];
ciphersBuffer[0] = '\0';
SSL_get_shared_ciphers(ssl_, ciphersBuffer, sizeof(ciphersBuffer) - 1);
sharedCiphers = ciphersBuffer;
}
void AsyncSSLSocket::getSSLServerCiphers(std::string& serverCiphers) const {
serverCiphers = SSL_get_cipher_list(ssl_, 0);
int i = 1;
const char *cipher;
while ((cipher = SSL_get_cipher_list(ssl_, i)) != nullptr) {
serverCiphers.append(":");
serverCiphers.append(cipher);
i++;
}
}
} // namespace
......@@ -547,129 +547,33 @@ class AsyncSSLSocket : public virtual AsyncSocket {
*/
void getSSLClientCiphers(
std::string& clientCiphers,
bool convertToString = true) const {
std::stringstream ciphersStream;
std::string cipherName;
if (parseClientHello_ == false
|| clientHelloInfo_->clientHelloCipherSuites_.empty()) {
clientCiphers = "";
return;
}
for (auto originalCipherCode : clientHelloInfo_->clientHelloCipherSuites_)
{
const SSL_CIPHER* cipher = nullptr;
if (convertToString) {
// OpenSSL expects code as a big endian char array
auto cipherCode = htons(originalCipherCode);
#if defined(SSL_OP_NO_TLSv1_2)
cipher =
TLSv1_2_method()->get_cipher_by_char((unsigned char*)&cipherCode);
#elif defined(SSL_OP_NO_TLSv1_1)
cipher =
TLSv1_1_method()->get_cipher_by_char((unsigned char*)&cipherCode);
#elif defined(SSL_OP_NO_TLSv1)
cipher =
TLSv1_method()->get_cipher_by_char((unsigned char*)&cipherCode);
#else
cipher =
SSLv3_method()->get_cipher_by_char((unsigned char*)&cipherCode);
#endif
}
if (cipher == nullptr) {
ciphersStream << std::setfill('0') << std::setw(4) << std::hex
<< originalCipherCode << ":";
} else {
ciphersStream << SSL_CIPHER_get_name(cipher) << ":";
}
}
clientCiphers = ciphersStream.str();
clientCiphers.erase(clientCiphers.end() - 1);
}
bool convertToString = true) const;
/**
* Get the list of compression methods sent by the client in TLS Hello.
*/
std::string getSSLClientComprMethods() const {
if (!parseClientHello_) {
return "";
}
return folly::join(":", clientHelloInfo_->clientHelloCompressionMethods_);
}
std::string getSSLClientComprMethods() const;
/**
* Get the list of TLS extensions sent by the client in the TLS Hello.
*/
std::string getSSLClientExts() const {
if (!parseClientHello_) {
return "";
}
return folly::join(":", clientHelloInfo_->clientHelloExtensions_);
}
std::string getSSLClientExts() const;
std::string getSSLClientSigAlgs() const {
if (!parseClientHello_) {
return "";
}
std::string getSSLClientSigAlgs() const;
std::string sigAlgs;
sigAlgs.reserve(clientHelloInfo_->clientHelloSigAlgs_.size() * 4);
for (size_t i = 0; i < clientHelloInfo_->clientHelloSigAlgs_.size(); i++) {
if (i) {
sigAlgs.push_back(':');
}
sigAlgs.append(folly::to<std::string>(
clientHelloInfo_->clientHelloSigAlgs_[i].first));
sigAlgs.push_back(',');
sigAlgs.append(folly::to<std::string>(
clientHelloInfo_->clientHelloSigAlgs_[i].second));
}
return sigAlgs;
}
std::string getSSLAlertsReceived() const {
std::string ret;
for (const auto& alert : alertsReceived_) {
if (!ret.empty()) {
ret.append(",");
}
ret.append(folly::to<std::string>(alert.first, ": ", alert.second));
}
return ret;
}
std::string getSSLAlertsReceived() const;
/**
* Get the list of shared ciphers between the server and the client.
* Works well for only SSLv2, not so good for SSLv3 or TLSv1.
*/
void getSSLSharedCiphers(std::string& sharedCiphers) const {
char ciphersBuffer[1024];
ciphersBuffer[0] = '\0';
SSL_get_shared_ciphers(ssl_, ciphersBuffer, sizeof(ciphersBuffer) - 1);
sharedCiphers = ciphersBuffer;
}
void getSSLSharedCiphers(std::string& sharedCiphers) const;
/**
* Get the list of ciphers supported by the server in the server's
* preference order.
*/
void getSSLServerCiphers(std::string& serverCiphers) const {
serverCiphers = SSL_get_cipher_list(ssl_, 0);
int i = 1;
const char *cipher;
while ((cipher = SSL_get_cipher_list(ssl_, i)) != nullptr) {
serverCiphers.append(":");
serverCiphers.append(cipher);
i++;
}
}
void getSSLServerCiphers(std::string& serverCiphers) const;
static int getSSLExDataIndex();
static AsyncSSLSocket* getFromSSL(const SSL *ssl);
......
......@@ -16,14 +16,13 @@
#include <folly/io/async/ssl/OpenSSLUtils.h>
#include <folly/ScopeGuard.h>
#include <folly/portability/Sockets.h>
#include <glog/logging.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <glog/logging.h>
#include <unordered_map>
#define OPENSSL_IS_101 (OPENSSL_VERSION_NUMBER >= 0x1000105fL && \
OPENSSL_VERSION_NUMBER < 0x1000200fL)
......@@ -147,6 +146,57 @@ bool OpenSSLUtils::validatePeerCertNames(X509* cert,
return false;
}
static std::unordered_map<uint16_t, std::string> getOpenSSLCipherNames() {
std::unordered_map<uint16_t, std::string> ret;
SSL_CTX* ctx = nullptr;
SSL* ssl = nullptr;
const SSL_METHOD* meth = SSLv23_server_method();
OpenSSL_add_ssl_algorithms();
if ((ctx = SSL_CTX_new(meth)) == nullptr) {
return ret;
}
SCOPE_EXIT {
SSL_CTX_free(ctx);
};
if ((ssl = SSL_new(ctx)) == nullptr) {
return ret;
}
SCOPE_EXIT {
SSL_free(ssl);
};
STACK_OF(SSL_CIPHER)* sk = SSL_get_ciphers(ssl);
for (size_t i = 0; i < (size_t)sk_SSL_CIPHER_num(sk); i++) {
SSL_CIPHER* c;
c = sk_SSL_CIPHER_value(sk, i);
unsigned long id = SSL_CIPHER_get_id(c);
// OpenSSL 1.0.2 and prior does weird things such as stuff the SSL/TLS
// version into the top 16 bits. Let's ignore those for now. This is
// BoringSSL compatible (their id can be cast as uint16_t)
uint16_t cipherCode = id & 0xffffL;
ret[cipherCode] = SSL_CIPHER_get_name(c);
}
return ret;
}
const std::string& OpenSSLUtils::getCipherName(uint16_t cipherCode) {
// Having this in a hash map saves the binary search inside OpenSSL
static std::unordered_map<uint16_t, std::string> cipherCodeToName(
getOpenSSLCipherNames());
const auto& iter = cipherCodeToName.find(cipherCode);
if (iter != cipherCodeToName.end()) {
return iter->second;
} else {
static std::string empty("");
return empty;
}
}
bool OpenSSLUtils::setCustomBioReadMethod(
BIO_METHOD* bioMeth,
int (*meth)(BIO*, char*, int)) {
......
......@@ -81,6 +81,16 @@ class OpenSSLUtils {
sockaddr_storage* addrStorage,
socklen_t* addrLen);
/**
* Get a stringified cipher name (e.g., ECDHE-ECDSA-CHACHA20-POLY1305) given
* the 2-byte code (e.g., 0xcca9) for the cipher. The name conversion only
* works for the ciphers built into the linked OpenSSL library
*
* @param cipherCode A 16-bit IANA cipher code (machine endianness)
* @return Cipher name, or empty if the code is not found
*/
static const std::string& getCipherName(uint16_t cipherCode);
/**
* Wrappers for BIO operations that may be different across different
* versions/flavors of OpenSSL (including forks like BoringSSL)
......
......@@ -19,8 +19,6 @@
#include <folly/io/Cursor.h>
#include <folly/io/IOBuf.h>
#include <map>
#include <openssl/ssl.h>
#include <openssl/tls1.h>
#include <vector>
namespace folly {
......
......@@ -1008,14 +1008,14 @@ TEST(AsyncSSLSocketTest, SSLParseClientHelloSuccess) {
auto clientCtx = std::make_shared<SSLContext>();
auto serverCtx = std::make_shared<SSLContext>();
serverCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
serverCtx->ciphers("RSA:!SHA:!NULL:!SHA256@STRENGTH");
serverCtx->ciphers("ECDHE-RSA-AES128-SHA:AES128-SHA:AES256-SHA");
serverCtx->loadPrivateKey(testKey);
serverCtx->loadCertificate(testCert);
serverCtx->loadTrustedCertificates(testCA);
serverCtx->loadClientCAList(testCA);
clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
clientCtx->ciphers("RC4-SHA:AES128-SHA:AES256-SHA:RC4-MD5");
clientCtx->ciphers("AES256-SHA:RC4-MD5");
clientCtx->loadPrivateKey(testKey);
clientCtx->loadCertificate(testCert);
clientCtx->loadTrustedCertificates(testCA);
......@@ -1033,8 +1033,8 @@ TEST(AsyncSSLSocketTest, SSLParseClientHelloSuccess) {
eventBase.loop();
EXPECT_EQ(server.clientCiphers_,
"RC4-SHA:AES128-SHA:AES256-SHA:RC4-MD5:00ff");
EXPECT_EQ(server.clientCiphers_, "AES256-SHA:RC4-MD5:00ff");
EXPECT_EQ(server.chosenCipher_, "AES256-SHA");
EXPECT_TRUE(client.handshakeVerify_);
EXPECT_TRUE(client.handshakeSuccess_);
EXPECT_TRUE(!client.handshakeError_);
......@@ -1695,6 +1695,16 @@ TEST(AsyncSSLSocketTest, ConnOpenSSLErrorString) {
std::string::npos);
}
TEST(AsyncSSLSocketTest, TestSSLCipherCodeToNameMap) {
using folly::ssl::OpenSSLUtils;
EXPECT_EQ(
OpenSSLUtils::getCipherName(0xc02c), "ECDHE-ECDSA-AES256-GCM-SHA384");
// TLS_DHE_RSA_WITH_DES_CBC_SHA - We shouldn't be building with this
EXPECT_EQ(OpenSSLUtils::getCipherName(0x0015), "");
// This indicates TLS_EMPTY_RENEGOTIATION_INFO_SCSV, no name expected
EXPECT_EQ(OpenSSLUtils::getCipherName(0x00ff), "");
}
#if FOLLY_ALLOW_TFO
class MockAsyncTFOSSLSocket : public AsyncSSLSocket {
......
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