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
*/
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 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));
- }
+ std::string getSSLClientExts() const;
- return sigAlgs;
- }
-
- std::string getSSLAlertsReceived() const {
- std::string ret;
+ std::string getSSLClientSigAlgs() const;
- 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);
#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)
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)) {
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)
#include <folly/io/Cursor.h>
#include <folly/io/IOBuf.h>
#include <map>
-#include <openssl/ssl.h>
-#include <openssl/tls1.h>
#include <vector>
namespace folly {
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);
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_);
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 {