X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Fio%2Fasync%2FAsyncSSLSocket.cpp;h=8a07c41d1a37f473ac880b70975f9ee2df441f32;hb=e0277a6c481b03ebcfc30369321f25f5891b4a3f;hp=c972c87764a2884b3a8b84cd197686e13393f2ee;hpb=35fcff936a0ba58986269fb05689843f99e89eb5;p=folly.git diff --git a/folly/io/async/AsyncSSLSocket.cpp b/folly/io/async/AsyncSSLSocket.cpp index c972c877..8a07c41d 100644 --- a/folly/io/async/AsyncSSLSocket.cpp +++ b/folly/io/async/AsyncSSLSocket.cpp @@ -17,17 +17,15 @@ #include #include +#include #include #include #include -#include -#include #include #include #include #include -#include #include #include @@ -56,6 +54,7 @@ using folly::AsyncSocketException; using folly::AsyncSSLSocket; using folly::Optional; using folly::SSLContext; +using folly::ssl::OpenSSLUtils; // We have one single dummy SSL context so that we can implement attach // and detach methods in a thread safe fashion without modifying opnessl. @@ -115,7 +114,7 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback, } void connectErr(const AsyncSocketException& ex) noexcept override { - LOG(ERROR) << "TCP connect failed: " << ex.what(); + VLOG(1) << "TCP connect failed: " << ex.what(); fail(ex); delete this; } @@ -130,7 +129,7 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback, void handshakeErr(AsyncSSLSocket* /* socket */, const AsyncSocketException& ex) noexcept override { - LOG(ERROR) << "client handshakeErr: " << ex.what(); + VLOG(1) << "client handshakeErr: " << ex.what(); fail(ex); delete this; } @@ -152,57 +151,6 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback, } }; -// XXX: implement an equivalent to corking for platforms with TCP_NOPUSH? -#ifdef TCP_CORK // Linux-only -/** - * Utility class that corks a TCP socket upon construction or uncorks - * the socket upon destruction - */ -class CorkGuard : private boost::noncopyable { - public: - CorkGuard(int fd, bool multipleWrites, bool haveMore, bool* corked): - fd_(fd), haveMore_(haveMore), corked_(corked) { - if (*corked_) { - // socket is already corked; nothing to do - return; - } - if (multipleWrites || haveMore) { - // We are performing multiple writes in this performWrite() call, - // and/or there are more calls to performWrite() that will be invoked - // later, so enable corking - int flag = 1; - setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &flag, sizeof(flag)); - *corked_ = true; - } - } - - ~CorkGuard() { - if (haveMore_) { - // more data to come; don't uncork yet - return; - } - if (!*corked_) { - // socket isn't corked; nothing to do - return; - } - - int flag = 0; - setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &flag, sizeof(flag)); - *corked_ = false; - } - - private: - int fd_; - bool haveMore_; - bool* corked_; -}; -#else -class CorkGuard : private boost::noncopyable { - public: - CorkGuard(int, bool, bool, bool*) {} -}; -#endif - void setup_SSL_CTX(SSL_CTX *ctx) { #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(ctx, @@ -225,16 +173,17 @@ void setup_SSL_CTX(SSL_CTX *ctx) { } -BIO_METHOD eorAwareBioMethod; +BIO_METHOD sslWriteBioMethod; -void* initEorBioMethod(void) { - memcpy(&eorAwareBioMethod, BIO_s_socket(), sizeof(eorAwareBioMethod)); +void* initsslWriteBioMethod(void) { + memcpy(&sslWriteBioMethod, BIO_s_socket(), sizeof(sslWriteBioMethod)); // override the bwrite method for MSG_EOR support - eorAwareBioMethod.bwrite = AsyncSSLSocket::eorAwareBioWrite; + OpenSSLUtils::setCustomBioWriteMethod( + &sslWriteBioMethod, AsyncSSLSocket::bioWrite); - // Note that the eorAwareBioMethod.type and eorAwareBioMethod.name are not + // Note that the sslWriteBioMethod.type and sslWriteBioMethod.name are not // set here. openssl code seems to be checking ".type == BIO_TYPE_SOCKET" and - // then have specific handlings. The eorAwareBioWrite should be compatible + // then have specific handlings. The sslWriteBioWrite should be compatible // with the one in openssl. // Return something here to enable AsyncSSLSocket to call this method using @@ -253,7 +202,8 @@ AsyncSSLSocket::AsyncSSLSocket(const shared_ptr &ctx, EventBase* evb, bool deferSecurityNegotiation) : AsyncSocket(evb), ctx_(ctx), - handshakeTimeout_(this, evb) { + handshakeTimeout_(this, evb), + connectionTimeout_(this, evb) { init(); if (deferSecurityNegotiation) { sslState_ = STATE_UNENCRYPTED; @@ -269,7 +219,8 @@ AsyncSSLSocket::AsyncSSLSocket(const shared_ptr& ctx, AsyncSocket(evb, fd), server_(server), ctx_(ctx), - handshakeTimeout_(this, evb) { + handshakeTimeout_(this, evb), + connectionTimeout_(this, evb) { init(); if (server) { SSL_CTX_set_info_callback(ctx_->getSSLCtx(), @@ -316,8 +267,8 @@ AsyncSSLSocket::~AsyncSSLSocket() { void AsyncSSLSocket::init() { // Do this here to ensure we initialize this once before any use of // AsyncSSLSocket instances and not as part of library load. - static const auto eorAwareBioMethodInitializer = initEorBioMethod(); - (void)eorAwareBioMethodInitializer; + static const auto sslWriteBioMethodInitializer = initsslWriteBioMethod(); + (void)sslWriteBioMethodInitializer; setup_SSL_CTX(ctx_->getSSLCtx()); } @@ -403,44 +354,31 @@ std::string AsyncSSLSocket::getApplicationProtocol() noexcept { } bool AsyncSSLSocket::isEorTrackingEnabled() const { - if (ssl_ == nullptr) { - return false; - } - const BIO *wb = SSL_get_wbio(ssl_); - return wb && wb->method == &eorAwareBioMethod; + return trackEor_; } void AsyncSSLSocket::setEorTracking(bool track) { - BIO *wb = SSL_get_wbio(ssl_); - if (!wb) { - throw AsyncSocketException(AsyncSocketException::INVALID_STATE, - "setting EOR tracking without an initialized " - "BIO"); - } - - if (track) { - if (wb->method != &eorAwareBioMethod) { - // only do this if we didn't - wb->method = &eorAwareBioMethod; - BIO_set_app_data(wb, this); - appEorByteNo_ = 0; - minEorRawByteNo_ = 0; - } - } else if (wb->method == &eorAwareBioMethod) { - wb->method = BIO_s_socket(); - BIO_set_app_data(wb, nullptr); + if (trackEor_ != track) { + trackEor_ = track; appEorByteNo_ = 0; minEorRawByteNo_ = 0; - } else { - CHECK(wb->method == BIO_s_socket()); } } size_t AsyncSSLSocket::getRawBytesWritten() const { + // The bio(s) in the write path are in a chain + // each bio flushes to the next and finally written into the socket + // to get the rawBytesWritten on the socket, + // get the write bytes of the last bio BIO *b; if (!ssl_ || !(b = SSL_get_wbio(ssl_))) { return 0; } + BIO* next = BIO_next(b); + while (next != NULL) { + b = next; + next = BIO_next(b); + } return BIO_number_written(b); } @@ -458,10 +396,10 @@ size_t AsyncSSLSocket::getRawBytesReceived() const { void AsyncSSLSocket::invalidState(HandshakeCB* callback) { LOG(ERROR) << "AsyncSSLSocket(this=" << this << ", fd=" << fd_ << ", state=" << int(state_) << ", sslState=" << sslState_ << ", " - << "events=" << eventFlags_ << ", server=" << short(server_) << "): " - << "sslAccept/Connect() called in invalid " - << "state, handshake callback " << handshakeCallback_ << ", new callback " - << callback; + << "events=" << eventFlags_ << ", server=" << short(server_) + << "): " << "sslAccept/Connect() called in invalid " + << "state, handshake callback " << handshakeCallback_ + << ", new callback " << callback; assert(!handshakeTimeout_.isScheduled()); sslState_ = STATE_ERROR; @@ -609,6 +547,12 @@ void AsyncSSLSocket::timeoutExpired() noexcept { // We are expecting a callback in restartSSLAccept. The cache lookup // and rsa-call necessarily have pointers to this ssl socket, so delay // the cleanup until he calls us back. + } else if (state_ == StateEnum::CONNECTING) { + assert(sslState_ == STATE_CONNECTING); + DestructorGuard dg(this); + AsyncSocketException ex(AsyncSocketException::TIMED_OUT, + "Fallback connect timed out during TFO"); + failHandshake(__func__, ex); } else { assert(state_ == StateEnum::ESTABLISHED && (sslState_ == STATE_CONNECTING || sslState_ == STATE_ACCEPTING)); @@ -705,6 +649,19 @@ void AsyncSSLSocket::applyVerificationOptions(SSL * ssl) { } } +bool AsyncSSLSocket::setupSSLBio() { + auto wb = BIO_new(&sslWriteBioMethod); + + if (!wb) { + return false; + } + + OpenSSLUtils::setBioAppData(wb, this); + OpenSSLUtils::setBioFd(wb, fd_, BIO_NOCLOSE); + SSL_set_bio(ssl_, wb, wb); + return true; +} + void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout, const SSLContext::SSLVerifyPeerEnum& verifyPeer) { DestructorGuard dg(this); @@ -725,10 +682,6 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout, return invalidState(callback); } - handshakeStartTime_ = std::chrono::steady_clock::now(); - // Make end time at least >= start time. - handshakeEndTime_ = handshakeStartTime_; - sslState_ = STATE_CONNECTING; handshakeCallback_ = callback; @@ -743,10 +696,17 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout, return failHandshake(__func__, ex); } + if (!setupSSLBio()) { + sslState_ = STATE_ERROR; + AsyncSocketException ex( + AsyncSocketException::INTERNAL_ERROR, "error creating SSL bio"); + return failHandshake(__func__, ex); + } + applyVerificationOptions(ssl_); - SSL_set_fd(ssl_, fd_); if (sslSession_ != nullptr) { + sessionResumptionAttempted_ = true; SSL_set_session(ssl_, sslSession_); SSL_SESSION_free(sslSession_); sslSession_ = nullptr; @@ -759,10 +719,19 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout, SSL_set_ex_data(ssl_, getSSLExDataIndex(), this); - if (timeout > 0) { - handshakeTimeout_.scheduleTimeout(timeout); - } + handshakeConnectTimeout_ = timeout; + startSSLConnect(); +} +// This could be called multiple times, during normal ssl connections +// and after TFO fallback. +void AsyncSSLSocket::startSSLConnect() { + handshakeStartTime_ = std::chrono::steady_clock::now(); + // Make end time at least >= start time. + handshakeEndTime_ = handshakeStartTime_; + if (handshakeConnectTimeout_ > 0) { + handshakeTimeout_.scheduleTimeout(handshakeConnectTimeout_); + } handleConnect(); } @@ -883,6 +852,10 @@ int AsyncSSLSocket::getSSLCertSize() const { return certSize; } +const X509* AsyncSSLSocket::getSelfCert() const { + return (ssl_ != nullptr) ? SSL_get_certificate(ssl_) : nullptr; +} + bool AsyncSSLSocket::willBlock(int ret, int* sslErrorOut, unsigned long* errErrorOut) noexcept { @@ -966,16 +939,15 @@ void AsyncSSLSocket::checkForImmediateRead() noexcept { void AsyncSSLSocket::restartSSLAccept() { - VLOG(3) << "AsyncSSLSocket::restartSSLAccept() this=" << this << ", fd=" << fd_ - << ", state=" << int(state_) << ", " + VLOG(3) << "AsyncSSLSocket::restartSSLAccept() this=" << this + << ", fd=" << fd_ << ", state=" << int(state_) << ", " << "sslState=" << sslState_ << ", events=" << eventFlags_; DestructorGuard dg(this); assert( sslState_ == STATE_CACHE_LOOKUP || sslState_ == STATE_ASYNC_PENDING || sslState_ == STATE_ERROR || - sslState_ == STATE_CLOSED - ); + sslState_ == STATE_CLOSED); if (sslState_ == STATE_CLOSED) { // I sure hope whoever closed this socket didn't delete it already, // but this is not strictly speaking an error @@ -1012,7 +984,14 @@ AsyncSSLSocket::handleAccept() noexcept { << ", fd=" << fd_ << "): " << e.what(); return failHandshake(__func__, ex); } - SSL_set_fd(ssl_, fd_); + + if (!setupSSLBio()) { + sslState_ = STATE_ERROR; + AsyncSocketException ex( + AsyncSocketException::INTERNAL_ERROR, "error creating write bio"); + return failHandshake(__func__, ex); + } + SSL_set_ex_data(ssl_, getSSLExDataIndex(), this); applyVerificationOptions(ssl_); @@ -1082,16 +1061,25 @@ AsyncSSLSocket::handleConnect() noexcept { return AsyncSocket::handleConnect(); } - assert(state_ == StateEnum::ESTABLISHED && - sslState_ == STATE_CONNECTING); + assert( + (state_ == StateEnum::FAST_OPEN || state_ == StateEnum::ESTABLISHED) && + sslState_ == STATE_CONNECTING); assert(ssl_); + auto originalState = state_; int ret = SSL_connect(ssl_); if (ret <= 0) { int sslError; unsigned long errError; int errnoCopy = errno; if (willBlock(ret, &sslError, &errError)) { + // We fell back to connecting state due to TFO + if (state_ == StateEnum::CONNECTING) { + DCHECK_EQ(StateEnum::FAST_OPEN, originalState); + if (handshakeTimeout_.isScheduled()) { + handshakeTimeout_.cancelTimeout(); + } + } return; } else { sslState_ = STATE_ERROR; @@ -1136,6 +1124,45 @@ AsyncSSLSocket::handleConnect() noexcept { AsyncSocket::handleInitialReadWrite(); } +void AsyncSSLSocket::invokeConnectErr(const AsyncSocketException& ex) { + connectionTimeout_.cancelTimeout(); + AsyncSocket::invokeConnectErr(ex); +} + +void AsyncSSLSocket::invokeConnectSuccess() { + connectionTimeout_.cancelTimeout(); + if (sslState_ == SSLStateEnum::STATE_CONNECTING) { + // If we failed TFO, we'd fall back to trying to connect the socket, + // to setup things like timeouts. + startSSLConnect(); + } + // still invoke the base class since it re-sets the connect time. + AsyncSocket::invokeConnectSuccess(); +} + +void AsyncSSLSocket::scheduleConnectTimeout() { + if (sslState_ == SSLStateEnum::STATE_CONNECTING) { + // We fell back from TFO, and need to set the timeouts. + // We will not have a connect callback in this case, thus if the timer + // expires we would have no-one to notify. + // Thus we should reset even the connect timers to point to the handshake + // timeouts. + assert(connectCallback_ == nullptr); + // We use a different connect timeout here than the handshake timeout, so + // that we can disambiguate the 2 timers. + int timeout = connectTimeout_.count(); + if (timeout > 0) { + if (!connectionTimeout_.scheduleTimeout(timeout)) { + throw AsyncSocketException( + AsyncSocketException::INTERNAL_ERROR, + withAddr("failed to schedule AsyncSSLSocket connect timeout")); + } + } + return; + } + AsyncSocket::scheduleConnectTimeout(); +} + void AsyncSSLSocket::setReadCB(ReadCallback *callback) { #ifdef SSL_MODE_MOVE_BUFFER_OWNERSHIP // turn on the buffer movable in openssl @@ -1331,9 +1358,6 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite( WRITE_ERROR, folly::make_unique(SSLError::EARLY_WRITE)); } - bool cork = isSet(flags, WriteFlags::CORK); - CorkGuard guard(fd_, count > 1, cork, &corked_); - // Declare a buffer used to hold small write requests. It could point to a // memory block either on stack or on heap. If it is on heap, we release it // manually when scope exits @@ -1363,6 +1387,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite( ssize_t bytes; uint32_t buffersStolen = 0; + auto sslWriteBuf = buf; if ((len < minWriteSize_) && ((i + 1) < count)) { // Combine this buffer with part or all of the next buffers in // order to avoid really small-grained calls to SSL_write(). @@ -1383,6 +1408,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite( } } assert(combinedBuf != nullptr); + sslWriteBuf = combinedBuf; memcpy(combinedBuf, buf, len); do { @@ -1401,15 +1427,24 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite( buffersStolen++; } } while ((i + buffersStolen + 1) < count && (len < minWriteSize_)); - bytes = eorAwareSSLWrite( - ssl_, combinedBuf, len, - (isSet(flags, WriteFlags::EOR) && i + buffersStolen + 1 == count)); + } - } else { - bytes = eorAwareSSLWrite(ssl_, buf, len, - (isSet(flags, WriteFlags::EOR) && i + 1 == count)); + // Advance any empty buffers immediately after. + if (bytesStolenFromNextBuffer == 0) { + while ((i + buffersStolen + 1) < count && + vec[i + buffersStolen + 1].iov_len == 0) { + buffersStolen++; + } } + corkCurrentWrite_ = + isSet(flags, WriteFlags::CORK) || (i + buffersStolen + 1 < count); + bytes = eorAwareSSLWrite( + ssl_, + sslWriteBuf, + len, + (isSet(flags, WriteFlags::EOR) && i + buffersStolen + 1 == count)); + if (bytes <= 0) { int error = SSL_get_error(ssl_, bytes); if (error == SSL_ERROR_WANT_WRITE) { @@ -1450,7 +1485,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite( int AsyncSSLSocket::eorAwareSSLWrite(SSL *ssl, const void *buf, int n, bool eor) { - if (eor && SSL_get_wbio(ssl)->method == &eorAwareBioMethod) { + if (eor && trackEor_) { if (appEorByteNo_) { // cannot track for more than one app byte EOR CHECK(appEorByteNo_ == appBytesWritten_ + n); @@ -1495,38 +1530,53 @@ void AsyncSSLSocket::sslInfoCallback(const SSL* ssl, int where, int ret) { } } -int AsyncSSLSocket::eorAwareBioWrite(BIO *b, const char *in, int inl) { - int ret; +int AsyncSSLSocket::bioWrite(BIO* b, const char* in, int inl) { struct msghdr msg; struct iovec iov; int flags = 0; - AsyncSSLSocket *tsslSock; + AsyncSSLSocket* tsslSock; - iov.iov_base = const_cast(in); + iov.iov_base = const_cast(in); iov.iov_len = inl; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; - tsslSock = - reinterpret_cast(BIO_get_app_data(b)); - if (tsslSock && - tsslSock->minEorRawByteNo_ && + auto appData = OpenSSLUtils::getBioAppData(b); + CHECK(appData); + + tsslSock = reinterpret_cast(appData); + CHECK(tsslSock); + + if (tsslSock->trackEor_ && tsslSock->minEorRawByteNo_ && tsslSock->minEorRawByteNo_ <= BIO_number_written(b) + inl) { flags = MSG_EOR; } - ret = sendmsg(b->num, &msg, flags); +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + +#ifdef MSG_MORE + if (tsslSock->corkCurrentWrite_) { + flags |= MSG_MORE; + } +#endif + + auto result = tsslSock->sendSocketMessage( + OpenSSLUtils::getBioFd(b, nullptr), &msg, flags); BIO_clear_retry_flags(b); - if (ret <= 0) { - if (BIO_sock_should_retry(ret)) + if (!result.exception && result.writeReturn <= 0) { + if (OpenSSLUtils::getBioShouldRetryWrite(result.writeReturn)) { BIO_set_retry_write(b); + } } - return(ret); + return result.writeReturn; } -int AsyncSSLSocket::sslVerifyCallback(int preverifyOk, - X509_STORE_CTX* x509Ctx) { +int AsyncSSLSocket::sslVerifyCallback( + int preverifyOk, + X509_STORE_CTX* x509Ctx) { SSL* ssl = (SSL*) X509_STORE_CTX_get_ex_data( x509Ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); AsyncSSLSocket* self = AsyncSSLSocket::getFromSSL(ssl); @@ -1624,6 +1674,7 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written, extensionsLength -= 2; uint16_t extensionDataLength = cursor.readBE(); extensionsLength -= 2; + extensionsLength -= extensionDataLength; if (extensionType == ssl::TLSExtension::SIGNATURE_ALGORITHMS) { cursor.skip(2); @@ -1639,7 +1690,6 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written, } } else { cursor.skip(extensionDataLength); - extensionsLength -= extensionDataLength; } } } @@ -1652,4 +1702,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{{ + static_cast((originalCipherCode >> 8) & 0xffL), + static_cast(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( + clientHelloInfo_->clientHelloSigAlgs_[i].first)); + sigAlgs.push_back(','); + sigAlgs.append(folly::to( + 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(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