/*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#pragma once
-#include <arpa/inet.h>
#include <iomanip>
-#include <openssl/ssl.h>
#include <folly/Optional.h>
#include <folly/String.h>
#include <folly/io/async/AsyncSocket.h>
-#include <folly/io/async/SSLContext.h>
#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/SSLContext.h>
#include <folly/io/async/TimeoutManager.h>
+#include <folly/io/async/ssl/OpenSSLPtrTypes.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/io/async/ssl/TLSDefinitions.h>
#include <folly/Bits.h>
#include <folly/io/IOBuf.h>
#include <folly/io/Cursor.h>
+#include <folly/portability/OpenSSL.h>
+#include <folly/portability/Sockets.h>
namespace folly {
-class SSLException: public folly::AsyncSocketException {
- public:
- SSLException(int sslError, int errno_copy);
-
- int getSSLError() const { return error_; }
-
- protected:
- int error_;
- char msg_[256];
-};
-
/**
* A class for performing asynchronous I/O on an SSL connection.
*
AsyncSSLSocket* sslSocket_;
};
+ // Timer for if we fallback from SSL connects to TCP connects
+ class ConnectionTimeout : public AsyncTimeout {
+ public:
+ ConnectionTimeout(AsyncSSLSocket* sslSocket, EventBase* eventBase)
+ : AsyncTimeout(eventBase), sslSocket_(sslSocket) {}
- /**
- * These are passed to the application via errno, packed in an SSL err which
- * are outside the valid errno range. The values are chosen to be unique
- * against values in ssl.h
- */
- enum SSLError {
- SSL_CLIENT_RENEGOTIATION_ATTEMPT = 900,
- SSL_INVALID_RENEGOTIATION = 901,
- SSL_EARLY_WRITE = 902
+ virtual void timeoutExpired() noexcept override {
+ sslSocket_->timeoutExpired();
+ }
+
+ private:
+ AsyncSSLSocket* sslSocket_;
};
/**
}
-#if OPENSSL_VERSION_NUMBER >= 0x1000105fL && !defined(OPENSSL_NO_TLSEXT)
+#if FOLLY_OPENSSL_HAS_SNI
/**
* Create a client AsyncSSLSocket with tlsext_servername in
* the Client Hello message.
new AsyncSSLSocket(ctx, evb, serverName, deferSecurityNegotiation),
Destructor());
}
-#endif
+#endif // FOLLY_OPENSSL_HAS_SNI
/**
* TODO: implement support for SSL renegotiation.
virtual bool connecting() const override;
virtual std::string getApplicationProtocol() noexcept override;
- bool isEorTrackingEnabled() const override;
+ virtual std::string getSecurityProtocol() const override { return "TLS"; }
+
virtual void setEorTracking(bool track) override;
virtual size_t getRawBytesWritten() const override;
virtual size_t getRawBytesReceived() const override;
void enableClientHelloParsing();
+ void setPreReceivedData(std::unique_ptr<IOBuf> data);
+
/**
* Accept an SSL connection on the socket.
*
* context by default, can be set explcitly to override the
* method in the context
*/
- virtual void sslAccept(HandshakeCB* callback, uint32_t timeout = 0,
+ virtual void sslAccept(
+ HandshakeCB* callback,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(),
const folly::SSLContext::SSLVerifyPeerEnum& verifyPeer =
- folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
+ folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
/**
* Invoke SSL accept following an asynchronous session cache lookup
* SSL_VERIFY_PEER and invokes
* HandshakeCB::handshakeVer().
*/
- virtual void sslConn(HandshakeCB *callback, uint64_t timeout = 0,
- const folly::SSLContext::SSLVerifyPeerEnum& verifyPeer =
- folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
+ virtual void sslConn(
+ HandshakeCB* callback,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(),
+ const folly::SSLContext::SSLVerifyPeerEnum& verifyPeer =
+ folly::SSLContext::SSLVerifyPeerEnum::USE_CTX);
enum SSLStateEnum {
STATE_UNINIT,
STATE_UNENCRYPTED,
STATE_ACCEPTING,
STATE_CACHE_LOOKUP,
- STATE_RSA_ASYNC_PENDING,
+ STATE_ASYNC_PENDING,
STATE_CONNECTING,
STATE_ESTABLISHED,
STATE_REMOTE_CLOSED, /// remote end closed; we can still write
*/
SSL_SESSION *getSSLSession();
+ /**
+ * Get a handle to the SSL struct.
+ */
+ const SSL* getSSL() const;
+
/**
* Set the SSL session to be used during sslConn. AsyncSSLSocket will
* hold a reference to the session until it is destroyed or released by the
/**
* Get the name of the protocol selected by the client during
- * Next Protocol Negotiation (NPN)
+ * Next Protocol Negotiation (NPN) or Application Layer Protocol Negotiation
+ * (ALPN)
*
* Throw an exception if openssl does not support NPN
*
* Note: the AsyncSSLSocket retains ownership
* of this string.
* @param protoNameLen Length of the name.
+ * @param protoType Whether this was an NPN or ALPN negotiation
*/
- virtual void getSelectedNextProtocol(const unsigned char** protoName,
- unsigned* protoLen) const;
+ virtual void getSelectedNextProtocol(
+ const unsigned char** protoName,
+ unsigned* protoLen,
+ SSLContext::NextProtocolType* protoType = nullptr) const;
/**
* Get the name of the protocol selected by the client during
- * Next Protocol Negotiation (NPN)
+ * Next Protocol Negotiation (NPN) or Application Layer Protocol Negotiation
+ * (ALPN)
*
* @param protoName Name of the protocol (not guaranteed to be
* null terminated); will be set to nullptr if
* Note: the AsyncSSLSocket retains ownership
* of this string.
* @param protoNameLen Length of the name.
+ * @param protoType Whether this was an NPN or ALPN negotiation
* @return false if openssl does not support NPN
*/
- virtual bool getSelectedNextProtocolNoThrow(const unsigned char** protoName,
- unsigned* protoLen) const;
+ virtual bool getSelectedNextProtocolNoThrow(
+ const unsigned char** protoName,
+ unsigned* protoLen,
+ SSLContext::NextProtocolType* protoType = nullptr) const;
/**
* Determine if the session specified during setSSLSession was reused
*/
int getSSLCertSize() const;
+ /**
+ * Get the certificate used for this SSL connection. May be null
+ */
+ virtual const X509* getSelfCert() const override;
+
virtual void attachEventBase(EventBase* eventBase) override {
AsyncSocket::attachEventBase(eventBase);
handshakeTimeout_.attachEventBase(eventBase);
+ connectionTimeout_.attachEventBase(eventBase);
}
virtual void detachEventBase() override {
AsyncSocket::detachEventBase();
handshakeTimeout_.detachEventBase();
+ connectionTimeout_.detachEventBase();
}
virtual bool isDetachable() const override {
void detachSSLContext();
#endif
-#if OPENSSL_VERSION_NUMBER >= 0x1000105fL && !defined(OPENSSL_NO_TLSEXT)
+#if FOLLY_OPENSSL_HAS_SNI
/**
* Switch the SSLContext to continue the SSL handshake.
* It can only be used in server mode.
* ClientHello message.
*/
void setServerName(std::string serverName) noexcept;
-#endif
+#endif // FOLLY_OPENSSL_HAS_SNI
void timeoutExpired() noexcept;
* Get the list of supported ciphers sent by the client in the client's
* preference order.
*/
- void getSSLClientCiphers(std::string& clientCiphers) const {
- std::stringstream ciphersStream;
- std::string cipherName;
-
- if (parseClientHello_ == false
- || clientHelloInfo_->clientHelloCipherSuites_.empty()) {
- clientCiphers = "";
- return;
- }
-
- for (auto originalCipherCode : clientHelloInfo_->clientHelloCipherSuites_)
- {
- // OpenSSL expects code as a big endian char array
- auto cipherCode = htons(originalCipherCode);
-
-#if defined(SSL_OP_NO_TLSv1_2)
- const SSL_CIPHER* cipher =
- TLSv1_2_method()->get_cipher_by_char((unsigned char*)&cipherCode);
-#elif defined(SSL_OP_NO_TLSv1_1)
- const SSL_CIPHER* cipher =
- TLSv1_1_method()->get_cipher_by_char((unsigned char*)&cipherCode);
-#elif defined(SSL_OP_NO_TLSv1)
- const SSL_CIPHER* cipher =
- TLSv1_method()->get_cipher_by_char((unsigned char*)&cipherCode);
-#else
- const SSL_CIPHER* 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);
- }
+ void getSSLClientCiphers(
+ std::string& clientCiphers,
+ 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));
- }
+ /**
+ * Get the list of versions in the supported versions extension (used to
+ * negotiate TLS 1.3).
+ */
+ std::string getSSLClientSupportedVersions() const;
- return sigAlgs;
- }
+ 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);
- static int eorAwareBioWrite(BIO *b, const char *in, int inl);
+ static int bioWrite(BIO* b, const char* in, int inl);
+ static int bioRead(BIO* b, char* out, int outl);
void resetClientHelloParsing(SSL *ssl);
static void clientHelloParsingCallback(int write_p, int version,
int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
-
- // http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
- enum class TLSExtension: uint16_t {
- SERVER_NAME = 0,
- MAX_FRAGMENT_LENGTH = 1,
- CLIENT_CERTIFICATE_URL = 2,
- TRUSTED_CA_KEYS = 3,
- TRUNCATED_HMAC = 4,
- STATUS_REQUEST = 5,
- USER_MAPPING = 6,
- CLIENT_AUTHZ = 7,
- SERVER_AUTHZ = 8,
- CERT_TYPE = 9,
- SUPPORTED_GROUPS = 10,
- EC_POINT_FORMATS = 11,
- SRP = 12,
- SIGNATURE_ALGORITHMS = 13,
- USE_SRTP = 14,
- HEARTBEAT = 15,
- APPLICATION_LAYER_PROTOCOL_NEGOTIATION = 16,
- STATUS_REQUEST_V2 = 17,
- SIGNED_CERTIFICATE_TIMESTAMP = 18,
- CLIENT_CERTIFICATE_TYPE = 19,
- SERVER_CERTIFICATE_TYPE = 20,
- PADDING = 21,
- ENCRYPT_THEN_MAC = 22,
- EXTENDED_MASTER_SECRET = 23,
- SESSION_TICKET = 35,
- RENEGOTIATION_INFO = 65281
- };
-
- // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
- enum class HashAlgorithm: uint8_t {
- NONE = 0,
- MD5 = 1,
- SHA1 = 2,
- SHA224 = 3,
- SHA256 = 4,
- SHA384 = 5,
- SHA512 = 6
- };
-
- // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
- enum class SignatureAlgorithm: uint8_t {
- ANONYMOUS = 0,
- RSA = 1,
- DSA = 2,
- ECDSA = 3
- };
-
- struct ClientHelloInfo {
- folly::IOBufQueue clientHelloBuf_;
- uint8_t clientHelloMajorVersion_;
- uint8_t clientHelloMinorVersion_;
- std::vector<uint16_t> clientHelloCipherSuites_;
- std::vector<uint8_t> clientHelloCompressionMethods_;
- std::vector<TLSExtension> clientHelloExtensions_;
- std::vector<
- std::pair<HashAlgorithm, SignatureAlgorithm>> clientHelloSigAlgs_;
- };
+ static const char* getSSLServerNameFromSSL(SSL* ssl);
// For unit-tests
- ClientHelloInfo* getClientHelloInfo() const {
+ ssl::ClientHelloInfo* getClientHelloInfo() const {
return clientHelloInfo_.get();
}
void setReadCB(ReadCallback* callback) override;
+ /**
+ * Tries to enable the buffer movable experimental feature in openssl.
+ * This is not guaranteed to succeed in case openssl does not have
+ * the experimental feature built in.
+ */
+ void setBufferMovableEnabled(bool enabled);
+
/**
* Returns the peer certificate, or nullptr if no peer certificate received.
*/
- virtual std::unique_ptr<X509, X509_deleter> getPeerCert() const {
+ virtual ssl::X509UniquePtr getPeerCert() const override {
if (!ssl_) {
return nullptr;
}
X509* cert = SSL_get_peer_certificate(ssl_);
- return std::unique_ptr<X509, X509_deleter>(cert);
+ return ssl::X509UniquePtr(cert);
+ }
+
+ /**
+ * Force AsyncSSLSocket object to cache local and peer socket addresses.
+ * If called with "true" before connect() this function forces full local
+ * and remote socket addresses to be cached in the socket object and available
+ * through getLocalAddress()/getPeerAddress() methods even after the socket is
+ * closed.
+ */
+ void forceCacheAddrOnFailure(bool force) { cacheAddrOnFailure_ = force; }
+
+ const std::string& getServiceIdentity() const { return serviceIdentity_; }
+
+ void setServiceIdentity(std::string serviceIdentity) {
+ serviceIdentity_ = std::move(serviceIdentity);
+ }
+
+ void setCertCacheHit(bool hit) {
+ certCacheHit_ = hit;
+ }
+
+ bool getCertCacheHit() const {
+ return certCacheHit_;
+ }
+
+ bool sessionResumptionAttempted() const {
+ return sessionResumptionAttempted_;
+ }
+
+ /**
+ * Clears the ERR stack before invoking SSL methods.
+ * This is useful if unrelated code that runs in the same thread
+ * does not properly handle SSL error conditions, in which case
+ * it could cause SSL_* methods to fail with incorrect error codes.
+ */
+ void setClearOpenSSLErrors(bool clearErr) {
+ clearOpenSSLErrors_ = clearErr;
}
private:
void init();
+ void clearOpenSSLErrors();
protected:
// Inherit event notification methods from AsyncSocket except
// the following.
- void prepareReadBuffer(void** buf, size_t* buflen) noexcept override;
+ void prepareReadBuffer(void** buf, size_t* buflen) override;
void handleRead() noexcept override;
void handleWrite() noexcept override;
void handleAccept() noexcept;
void handleConnect() noexcept override;
void invalidState(HandshakeCB* callback);
- bool willBlock(int ret, int *errorOut) noexcept;
+ bool willBlock(int ret,
+ int* sslErrorOut,
+ unsigned long* errErrorOut) noexcept;
virtual void checkForImmediateRead() noexcept override;
// AsyncSocket calls this at the wrong time for SSL
void handleInitialReadWrite() noexcept override {}
- int interpretSSLError(int rc, int error);
- ssize_t performRead(void** buf, size_t* buflen, size_t* offset) override;
- ssize_t performWrite(const iovec* vec, uint32_t count, WriteFlags flags,
- uint32_t* countWritten, uint32_t* partialWritten)
- override;
+ WriteResult interpretSSLError(int rc, int error);
+ ReadResult performRead(void** buf, size_t* buflen, size_t* offset) override;
+ WriteResult performWrite(
+ const iovec* vec,
+ uint32_t count,
+ WriteFlags flags,
+ uint32_t* countWritten,
+ uint32_t* partialWritten) override;
ssize_t performWriteIovec(const iovec* vec, uint32_t count,
WriteFlags flags, uint32_t* countWritten,
// This virtual wrapper around SSL_write exists solely for testing/mockability
virtual int sslWriteImpl(SSL *ssl, const void *buf, int n) {
+ clearOpenSSLErrors();
return SSL_write(ssl, buf, n);
}
*/
void applyVerificationOptions(SSL * ssl);
+ /**
+ * Sets up SSL with a custom write bio which intercepts all writes.
+ *
+ * @return true, if succeeds and false if there is an error creating the bio.
+ */
+ bool setupSSLBio();
+
/**
* A SSL_write wrapper that understand EOR
*
void invokeHandshakeErr(const AsyncSocketException& ex);
void invokeHandshakeCB();
+ void invokeConnectErr(const AsyncSocketException& ex) override;
+ void invokeConnectSuccess() override;
+ void scheduleConnectTimeout() override;
+
+ void cacheLocalPeerAddr();
+
+ void startSSLConnect();
+
static void sslInfoCallback(const SSL *ssl, int type, int val);
+ // Whether the current write to the socket should use MSG_MORE.
+ bool corkCurrentWrite_{false};
// SSL related members.
bool server_{false};
// Used to prevent client-initiated renegotiation. Note that AsyncSSLSocket
SSL* ssl_{nullptr};
SSL_SESSION *sslSession_{nullptr};
HandshakeTimeout handshakeTimeout_;
+ ConnectionTimeout connectionTimeout_;
// whether the SSL session was resumed using session ID or not
bool sessionIDResumed_{false};
// When openssl is about to sendmsg() across the minEorRawBytesNo_,
// it will pass MSG_EOR to sendmsg().
size_t minEorRawByteNo_{0};
-#if OPENSSL_VERSION_NUMBER >= 0x1000105fL && !defined(OPENSSL_NO_TLSEXT)
+#if FOLLY_OPENSSL_HAS_SNI
std::shared_ptr<folly::SSLContext> handshakeCtx_;
std::string tlsextHostname_;
#endif
+
+ // a service identity that this socket/connection is associated with
+ std::string serviceIdentity_;
+
folly::SSLContext::SSLVerifyPeerEnum
verifyPeer_{folly::SSLContext::SSLVerifyPeerEnum::USE_CTX};
static int sslVerifyCallback(int preverifyOk, X509_STORE_CTX* ctx);
bool parseClientHello_{false};
- std::unique_ptr<ClientHelloInfo> clientHelloInfo_;
+ bool cacheAddrOnFailure_{false};
+ bool bufferMovableEnabled_{false};
+ bool certCacheHit_{false};
+ std::unique_ptr<ssl::ClientHelloInfo> clientHelloInfo_;
+ std::vector<std::pair<char, StringPiece>> alertsReceived_;
// Time taken to complete the ssl handshake.
std::chrono::steady_clock::time_point handshakeStartTime_;
std::chrono::steady_clock::time_point handshakeEndTime_;
+ std::chrono::milliseconds handshakeConnectTimeout_{0};
+ bool sessionResumptionAttempted_{false};
+
+ std::unique_ptr<IOBuf> preReceivedData_;
+ // Whether or not to clear the err stack before invocation of another
+ // SSL method
+ bool clearOpenSSLErrors_{false};
};
} // namespace