mirror of https://gitee.com/bigwinds/arangodb
541 lines
16 KiB
C++
541 lines
16 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Dr. Frank Celler
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "SslServerFeature.h"
|
|
|
|
#include "Basics/FileUtils.h"
|
|
#include "Logger/Logger.h"
|
|
#include "ProgramOptions/ProgramOptions.h"
|
|
#include "ProgramOptions/Section.h"
|
|
#include "Random/UniformCharacter.h"
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::basics;
|
|
using namespace arangodb::options;
|
|
|
|
SslServerFeature* SslServerFeature::SSL = nullptr;
|
|
|
|
SslServerFeature::SslServerFeature(application_features::ApplicationServer& server)
|
|
: ApplicationFeature(server, "SslServer"),
|
|
_cafile(),
|
|
_keyfile(),
|
|
_sessionCache(false),
|
|
_cipherList("HIGH:!EXPORT:!aNULL@STRENGTH"),
|
|
_sslProtocol(TLS_V12),
|
|
_sslOptions(asio_ns::ssl::context::default_workarounds | asio_ns::ssl::context::single_dh_use),
|
|
_ecdhCurve("prime256v1") {
|
|
setOptional(true);
|
|
startsAfter("AQLPhase");
|
|
}
|
|
|
|
void SslServerFeature::collectOptions(std::shared_ptr<ProgramOptions> options) {
|
|
options->addOldOption("server.cafile", "ssl.cafile");
|
|
options->addOldOption("server.keyfile", "ssl.keyfile");
|
|
options->addOldOption("server.ssl-cache", "ssl.session-cache");
|
|
options->addOldOption("server.ssl-cipher-list", "ssl.cipher-list");
|
|
options->addOldOption("server.ssl-options", "ssl.options");
|
|
options->addOldOption("server.ssl-protocol", "ssl.protocol");
|
|
|
|
options->addSection("ssl", "Configure SSL communication");
|
|
|
|
options->addOption("--ssl.cafile", "ca file used for secure connections",
|
|
new StringParameter(&_cafile));
|
|
|
|
options->addOption("--ssl.keyfile", "key-file used for secure connections",
|
|
new StringParameter(&_keyfile));
|
|
|
|
options->addOption("--ssl.session-cache",
|
|
"enable the session cache for connections",
|
|
new BooleanParameter(&_sessionCache));
|
|
|
|
options->addOption("--ssl.cipher-list",
|
|
"ssl ciphers to use, see OpenSSL documentation",
|
|
new StringParameter(&_cipherList));
|
|
|
|
std::unordered_set<uint64_t> const sslProtocols = availableSslProtocols();
|
|
|
|
options->addOption("--ssl.protocol", availableSslProtocolsDescription(),
|
|
new DiscreteValuesParameter<UInt64Parameter>(&_sslProtocol, sslProtocols));
|
|
|
|
options->addOption("--ssl.options",
|
|
"ssl connection options, see OpenSSL documentation",
|
|
new UInt64Parameter(&_sslOptions),
|
|
arangodb::options::makeFlags(arangodb::options::Flags::Hidden));
|
|
|
|
options->addOption(
|
|
"--ssl.ecdh-curve",
|
|
"SSL ECDH Curve, see the output of \"openssl ecparam -list_curves\"",
|
|
new StringParameter(&_ecdhCurve));
|
|
}
|
|
|
|
void SslServerFeature::validateOptions(std::shared_ptr<ProgramOptions> options) {
|
|
// check for SSLv2
|
|
if (_sslProtocol == SslProtocol::SSL_V2) {
|
|
LOG_TOPIC("b7890", FATAL, arangodb::Logger::SSL)
|
|
<< "SSLv2 is not supported any longer because of security "
|
|
"vulnerabilities in this protocol";
|
|
FATAL_ERROR_EXIT();
|
|
}
|
|
}
|
|
|
|
void SslServerFeature::prepare() {
|
|
LOG_TOPIC("afcd3", INFO, arangodb::Logger::SSL)
|
|
<< "using SSL options: " << stringifySslOptions(_sslOptions);
|
|
|
|
if (!_cipherList.empty()) {
|
|
LOG_TOPIC("9b126", INFO, arangodb::Logger::SSL)
|
|
<< "using SSL cipher-list '" << _cipherList << "'";
|
|
}
|
|
|
|
UniformCharacter r(
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
|
|
_rctx = r.random(SSL_MAX_SSL_SESSION_ID_LENGTH);
|
|
|
|
SSL = this;
|
|
}
|
|
|
|
void SslServerFeature::unprepare() {
|
|
LOG_TOPIC("7093e", TRACE, arangodb::Logger::SSL)
|
|
<< "unpreparing ssl: " << stringifySslOptions(_sslOptions);
|
|
}
|
|
|
|
void SslServerFeature::verifySslOptions() {
|
|
// check keyfile
|
|
if (_keyfile.empty()) {
|
|
LOG_TOPIC("f0dca", FATAL, arangodb::Logger::SSL)
|
|
<< "no value specified for '--ssl.keyfile'";
|
|
FATAL_ERROR_EXIT();
|
|
}
|
|
|
|
// validate protocol
|
|
if (_sslProtocol <= SSL_UNKNOWN || _sslProtocol >= SSL_LAST) {
|
|
LOG_TOPIC("1f48b", FATAL, arangodb::Logger::SSL)
|
|
<< "invalid SSL protocol version specified. Please use a valid "
|
|
"value for '--ssl.protocol'";
|
|
FATAL_ERROR_EXIT();
|
|
}
|
|
|
|
LOG_TOPIC("47161", DEBUG, arangodb::Logger::SSL)
|
|
<< "using SSL protocol version '"
|
|
<< protocolName(SslProtocol(_sslProtocol)) << "'";
|
|
|
|
if (!FileUtils::exists(_keyfile)) {
|
|
LOG_TOPIC("51cf0", FATAL, arangodb::Logger::SSL)
|
|
<< "unable to find SSL keyfile '" << _keyfile << "'";
|
|
FATAL_ERROR_EXIT();
|
|
}
|
|
|
|
try {
|
|
createSslContext();
|
|
} catch (...) {
|
|
LOG_TOPIC("997d2", FATAL, arangodb::Logger::SSL) << "cannot create SSL context";
|
|
FATAL_ERROR_EXIT();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
class BIOGuard {
|
|
public:
|
|
explicit BIOGuard(BIO* bio) : _bio(bio) {}
|
|
|
|
~BIOGuard() { BIO_free(_bio); }
|
|
|
|
public:
|
|
BIO* _bio;
|
|
};
|
|
} // namespace
|
|
|
|
asio_ns::ssl::context SslServerFeature::createSslContext() const {
|
|
try {
|
|
// create context
|
|
asio_ns::ssl::context sslContext = ::sslContext(SslProtocol(_sslProtocol), _keyfile);
|
|
|
|
// and use this native handle
|
|
asio_ns::ssl::context::native_handle_type nativeContext = sslContext.native_handle();
|
|
|
|
// set cache mode
|
|
SSL_CTX_set_session_cache_mode(nativeContext, _sessionCache ? SSL_SESS_CACHE_SERVER
|
|
: SSL_SESS_CACHE_OFF);
|
|
|
|
if (_sessionCache) {
|
|
LOG_TOPIC("af2f4", TRACE, arangodb::Logger::SSL) << "using SSL session caching";
|
|
}
|
|
|
|
// set options
|
|
sslContext.set_options(static_cast<long>(_sslOptions));
|
|
|
|
if (!_cipherList.empty()) {
|
|
if (SSL_CTX_set_cipher_list(nativeContext, _cipherList.c_str()) != 1) {
|
|
LOG_TOPIC("c6981", ERR, arangodb::Logger::SSL) << "cannot set SSL cipher list '" << _cipherList
|
|
<< "': " << lastSSLError();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
|
|
if (!_ecdhCurve.empty()) {
|
|
int sslEcdhNid = OBJ_sn2nid(_ecdhCurve.c_str());
|
|
|
|
if (sslEcdhNid == 0) {
|
|
LOG_TOPIC("40292", ERR, arangodb::Logger::SSL) << "SSL error: " << lastSSLError()
|
|
<< " Unknown curve name: " << _ecdhCurve;
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
// https://www.openssl.org/docs/manmaster/apps/ecparam.html
|
|
EC_KEY* ecdhKey = EC_KEY_new_by_curve_name(sslEcdhNid);
|
|
if (ecdhKey == nullptr) {
|
|
LOG_TOPIC("471f2", ERR, arangodb::Logger::SSL)
|
|
<< "SSL error: " << lastSSLError()
|
|
<< ". unable to create curve by name: " << _ecdhCurve;
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
if (SSL_CTX_set_tmp_ecdh(nativeContext, ecdhKey) != 1) {
|
|
EC_KEY_free(ecdhKey);
|
|
LOG_TOPIC("05d06", ERR, arangodb::Logger::SSL)
|
|
<< "cannot set ECDH option" << lastSSLError();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
EC_KEY_free(ecdhKey);
|
|
SSL_CTX_set_options(nativeContext, SSL_OP_SINGLE_ECDH_USE);
|
|
}
|
|
#endif
|
|
|
|
// set ssl context
|
|
int res = SSL_CTX_set_session_id_context(nativeContext,
|
|
(unsigned char const*)_rctx.c_str(),
|
|
(int)_rctx.size());
|
|
|
|
if (res != 1) {
|
|
LOG_TOPIC("72e4e", ERR, arangodb::Logger::SSL)
|
|
<< "cannot set SSL session id context '" << _rctx << "': " << lastSSLError();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
// check CA
|
|
if (!_cafile.empty()) {
|
|
LOG_TOPIC("cdaf2", TRACE, arangodb::Logger::SSL)
|
|
<< "trying to load CA certificates from '" << _cafile << "'";
|
|
|
|
res = SSL_CTX_load_verify_locations(nativeContext, _cafile.c_str(), nullptr);
|
|
|
|
if (res == 0) {
|
|
LOG_TOPIC("30289", ERR, arangodb::Logger::SSL)
|
|
<< "cannot load CA certificates from '" << _cafile
|
|
<< "': " << lastSSLError();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
STACK_OF(X509_NAME) * certNames;
|
|
|
|
certNames = SSL_load_client_CA_file(_cafile.c_str());
|
|
|
|
if (certNames == nullptr) {
|
|
LOG_TOPIC("30363", ERR, arangodb::Logger::SSL)
|
|
<< "cannot load CA certificates from '" << _cafile
|
|
<< "': " << lastSSLError();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
|
|
if (Logger::logLevel() == arangodb::LogLevel::TRACE) {
|
|
for (int i = 0; i < sk_X509_NAME_num(certNames); ++i) {
|
|
X509_NAME* cert = sk_X509_NAME_value(certNames, i);
|
|
|
|
if (cert) {
|
|
BIOGuard bout(BIO_new(BIO_s_mem()));
|
|
|
|
X509_NAME_print_ex(bout._bio, cert, 0,
|
|
(XN_FLAG_SEP_COMMA_PLUS | XN_FLAG_DN_REV | ASN1_STRFLGS_UTF8_CONVERT) &
|
|
~ASN1_STRFLGS_ESC_MSB);
|
|
|
|
char* r;
|
|
long len = BIO_get_mem_data(bout._bio, &r);
|
|
|
|
LOG_TOPIC("b8ebd", TRACE, arangodb::Logger::SSL) << "name: " << std::string(r, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
SSL_CTX_set_client_CA_list(nativeContext, certNames);
|
|
}
|
|
|
|
sslContext.set_verify_mode(SSL_VERIFY_NONE);
|
|
|
|
return sslContext;
|
|
} catch (std::exception const& ex) {
|
|
LOG_TOPIC("bd0ba", ERR, arangodb::Logger::SSL)
|
|
<< "failed to create SSL context: " << ex.what();
|
|
throw std::runtime_error("cannot create SSL context");
|
|
} catch (...) {
|
|
LOG_TOPIC("1217f", ERR, arangodb::Logger::SSL)
|
|
<< "failed to create SSL context, cannot create HTTPS server";
|
|
throw std::runtime_error("cannot create SSL context");
|
|
}
|
|
}
|
|
|
|
std::string SslServerFeature::stringifySslOptions(uint64_t opts) const {
|
|
std::string result;
|
|
|
|
#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
|
|
if (opts & SSL_OP_MICROSOFT_SESS_ID_BUG) {
|
|
result.append(", SSL_OP_MICROSOFT_SESS_ID_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
|
|
if (opts & SSL_OP_NETSCAPE_CHALLENGE_BUG) {
|
|
result.append(", SSL_OP_NETSCAPE_CHALLENGE_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_LEGACY_SERVER_CONNECT
|
|
if (opts & SSL_OP_LEGACY_SERVER_CONNECT) {
|
|
result.append(", SSL_OP_LEGACY_SERVER_CONNECT");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
|
if (opts & SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG) {
|
|
result.append(", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLSEXT_PADDING
|
|
if (opts & SSL_OP_TLSEXT_PADDING) {
|
|
result.append(", SSL_OP_TLSEXT_PADDING");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
|
|
if (opts & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {
|
|
result.append(", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
|
|
if (opts & SSL_OP_SAFARI_ECDHE_ECDSA_BUG) {
|
|
result.append(", SSL_OP_SAFARI_ECDHE_ECDSA_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
|
|
if (opts & SSL_OP_SSLEAY_080_CLIENT_DH_BUG) {
|
|
result.append(", SSL_OP_SSLEAY_080_CLIENT_DH_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLS_D5_BUG
|
|
if (opts & SSL_OP_TLS_D5_BUG) {
|
|
result.append(", SSL_OP_TLS_D5_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
|
|
if (opts & SSL_OP_TLS_BLOCK_PADDING_BUG) {
|
|
result.append(", SSL_OP_TLS_BLOCK_PADDING_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
|
|
if (opts & SSL_OP_MSIE_SSLV2_RSA_PADDING) {
|
|
result.append(", SSL_OP_MSIE_SSLV2_RSA_PADDING");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
|
|
if (opts & SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG) {
|
|
result.append(", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
|
if (opts & SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) {
|
|
result.append(", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_QUERY_MTU
|
|
if (opts & SSL_OP_NO_QUERY_MTU) {
|
|
result.append(", SSL_OP_NO_QUERY_MTU");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_COOKIE_EXCHANGE
|
|
if (opts & SSL_OP_COOKIE_EXCHANGE) {
|
|
result.append(", SSL_OP_COOKIE_EXCHANGE");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_TICKET
|
|
if (opts & SSL_OP_NO_TICKET) {
|
|
result.append(", SSL_OP_NO_TICKET");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_CISCO_ANYCONNECT
|
|
if (opts & SSL_OP_CISCO_ANYCONNECT) {
|
|
result.append(", SSL_OP_CISCO_ANYCONNECT");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
|
|
if (opts & SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION) {
|
|
result.append(", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_COMPRESSION
|
|
if (opts & SSL_OP_NO_COMPRESSION) {
|
|
result.append(", SSL_OP_NO_COMPRESSION");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
|
|
if (opts & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) {
|
|
result.append(", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SINGLE_ECDH_USE
|
|
if (opts & SSL_OP_SINGLE_ECDH_USE) {
|
|
result.append(", SSL_OP_SINGLE_ECDH_USE");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SINGLE_DH_USE
|
|
if (opts & SSL_OP_SINGLE_DH_USE) {
|
|
result.append(", SSL_OP_SINGLE_DH_USE");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_EPHEMERAL_RSA
|
|
if (opts & SSL_OP_EPHEMERAL_RSA) {
|
|
result.append(", SSL_OP_EPHEMERAL_RSA");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
|
|
if (opts & SSL_OP_CIPHER_SERVER_PREFERENCE) {
|
|
result.append(", SSL_OP_CIPHER_SERVER_PREFERENCE");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLS_ROLLBACK_BUG
|
|
if (opts & SSL_OP_TLS_ROLLBACK_BUG) {
|
|
result.append(", SSL_OP_TLS_ROLLBACK_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_SSLv2
|
|
if (opts & SSL_OP_NO_SSLv2) {
|
|
result.append(", SSL_OP_NO_SSLv2");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_SSLv3
|
|
if (opts & SSL_OP_NO_SSLv3) {
|
|
result.append(", SSL_OP_NO_SSLv3");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_TLSv1
|
|
if (opts & SSL_OP_NO_TLSv1) {
|
|
result.append(", SSL_OP_NO_TLSv1");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_TLSv1_2
|
|
if (opts & SSL_OP_NO_TLSv1_2) {
|
|
result.append(", SSL_OP_NO_TLSv1_2");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_TLSv1_1
|
|
if (opts & SSL_OP_NO_TLSv1_1) {
|
|
result.append(", SSL_OP_NO_TLSv1_1");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_DTLSv1
|
|
if (opts & SSL_OP_NO_DTLSv1) {
|
|
result.append(", SSL_OP_NO_DTLSv1");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_DTLSv1_2
|
|
if (opts & SSL_OP_NO_DTLSv1_2) {
|
|
result.append(", SSL_OP_NO_DTLSv1_2");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_SSL_MASK
|
|
if (opts & SSL_OP_NO_SSL_MASK) {
|
|
result.append(", SSL_OP_NO_SSL_MASK");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_PKCS1_CHECK_1
|
|
if (SSL_OP_PKCS1_CHECK_1) {
|
|
if (opts & SSL_OP_PKCS1_CHECK_1) {
|
|
result.append(", SSL_OP_PKCS1_CHECK_1");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_PKCS1_CHECK_2
|
|
if (SSL_OP_PKCS1_CHECK_1) {
|
|
if (opts & SSL_OP_PKCS1_CHECK_2) {
|
|
result.append(", SSL_OP_PKCS1_CHECK_2");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NETSCAPE_CA_DN_BUG
|
|
if (SSL_OP_NETSCAPE_CA_DN_BUG) {
|
|
if (opts & SSL_OP_NETSCAPE_CA_DN_BUG) {
|
|
result.append(", SSL_OP_NETSCAPE_CA_DN_BUG");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
|
|
if (opts & SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG) {
|
|
result.append(", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG");
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
|
|
if (opts & SSL_OP_CRYPTOPRO_TLSEXT_BUG) {
|
|
result.append(", SSL_OP_CRYPTOPRO_TLSEXT_BUG");
|
|
}
|
|
#endif
|
|
|
|
if (result.empty()) {
|
|
return result;
|
|
}
|
|
|
|
// strip initial comma
|
|
return result.substr(2);
|
|
}
|