Skip to content

Windows: use (existing) Schannel, not (shipped) OpenSSL #9956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ include_directories(${JSON_INCLUDE})
find_package(UTF8CPP)
include_directories(${UTF8CPP_INCLUDE})

if(WIN32)
find_package(BoostWinTLS)
include_directories(${BoostWinTLS_INCLUDE})
endif()

find_package(Editline)
set(HAVE_EDITLINE "${EDITLINE_FOUND}")

Expand Down
8 changes: 8 additions & 0 deletions cmake/FindBoostWinTLS.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FIND_PATH(BoostWinTLS_HEADERS wintls.hpp HINTS "${PROJECT_SOURCE_DIR}/third-party/boost-wintls/include/boost")

if(BoostWinTLS_HEADERS)
set(BoostWinTLS_INCLUDE "${BoostWinTLS_HEADERS}/..")
message(STATUS "Found Boost.WinTLS: ${BoostWinTLS_INCLUDE}")
else()
message(FATAL_ERROR "Unable to include wintls.hpp")
endif()
33 changes: 30 additions & 3 deletions lib/base/tlsstream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>

#ifdef _WIN32
# include <boost/wintls.hpp>
#endif /* _WIN32 */

namespace icinga
{

Expand Down Expand Up @@ -59,11 +63,34 @@ class SeenStream : public ARS
struct UnbufferedAsioTlsStreamParams
{
boost::asio::io_context& IoContext;
boost::asio::ssl::context& SslContext;
TlsContext& SslContext;
const String& Hostname;
};

typedef SeenStream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> AsioTcpTlsStream;
#ifdef _WIN32
template<class T>
class TlsStream : public boost::wintls::stream<T>
{
public:
using boost::wintls::stream<T>::stream;

typedef typename next_layer_type::lowest_layer_type lowest_layer_type;
typedef boost::wintls::handshake_type handshake_type;

static constexpr auto client = boost::wintls::handshake_type::client;
static constexpr auto server = boost::wintls::handshake_type::server;

lowest_layer_type& lowest_layer()
{
return next_layer().lowest_layer();
}
};
#else /* _WIN32 */
template<class T>
using TlsStream = boost::asio::ssl::stream<T>;
#endif /* _WIN32 */

typedef SeenStream<TlsStream<boost::asio::ip::tcp::socket>> AsioTcpTlsStream;

class UnbufferedAsioTlsStream : public AsioTcpTlsStream
{
Expand Down Expand Up @@ -108,7 +135,7 @@ class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStrea
{
public:
inline
AsioTlsStream(boost::asio::io_context& ioContext, boost::asio::ssl::context& sslContext, const String& hostname = String())
AsioTlsStream(boost::asio::io_context& ioContext, TlsContext& sslContext, const String& hostname = String())
: AsioTlsStream(UnbufferedAsioTlsStreamParams{ioContext, sslContext, hostname})
{
}
Expand Down
103 changes: 57 additions & 46 deletions lib/base/tlsutility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <openssl/ssl.h>
#include <openssl/ssl3.h>
#include <fstream>
#include <utility>

namespace icinga
{
Expand Down Expand Up @@ -72,18 +73,18 @@ void InitializeOpenSSL()
l_SSLInitialized = true;
}

static void InitSslContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& pubkey, const String& privkey, const String& cakey)
static void InitSslContext(const Shared<TlsContext>::Ptr& context, const String& pubkey, const String& privkey, const String& cakey)
{
char errbuf[256];

// Enforce TLS v1.2 as minimum
context->set_options(
boost::asio::ssl::context::default_workarounds |
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is SSL_OP_ALL, which workarounds bugs in foreign clients, e.g. SSL_OP_SAFARI_ECDHE_ECDSA_BUG. If nobody's against, I'd omit this on Windows (given there's an equivalent at all).

boost::asio::ssl::context::no_compression |
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3 |
boost::asio::ssl::context::no_tlsv1 |
boost::asio::ssl::context::no_tlsv1_1
TlsContext::default_workarounds |
TlsContext::no_compression |
TlsContext::no_sslv2 |
TlsContext::no_sslv3 |
TlsContext::no_tlsv1 |
TlsContext::no_tlsv1_1
);

// Custom TLS flags
Expand Down Expand Up @@ -194,33 +195,12 @@ static void InitSslContext(const Shared<boost::asio::ssl::context>::Ptr& context
}
}

/**
* Initializes an SSL context using the specified certificates.
*
* @param pubkey The public key.
* @param privkey The matching private key.
* @param cakey CA certificate chain file.
* @returns An SSL context.
*/
Shared<boost::asio::ssl::context>::Ptr MakeAsioSslContext(const String& pubkey, const String& privkey, const String& cakey)
{
namespace ssl = boost::asio::ssl;

InitializeOpenSSL();

auto context (Shared<ssl::context>::Make(ssl::context::tls));

InitSslContext(context, pubkey, privkey, cakey);

return context;
}

/**
* Set the cipher list to the specified SSL context.
* @param context The ssl context.
* @param cipherList The ciper list.
**/
void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& cipherList)
void SetCipherListToSSLContext(const Shared<TlsContext>::Ptr& context, const String& cipherList)
{
char errbuf[256];

Expand Down Expand Up @@ -264,40 +244,67 @@ void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& con
* @param version String of a TLS version, for example "TLSv1.2".
* @return The value of the corresponding TLS*_VERSION macro.
*/
int ResolveTlsProtocolVersion(const std::string& version) {
TlsProtocolMin ResolveTlsProtocolVersion(const std::string& version) {
#ifdef _WIN32
if (version == "TLSv1.2") {
return TlsProtocolMin((int)TlsProtocolMin::tlsv12 | (int)TlsProtocolMin::tlsv13);
} else if (version == "TLSv1.3") {
return TlsProtocolMin::tlsv13;
#else /* _WIN32 */
if (version == "TLSv1.2") {
return TLS1_2_VERSION;
} else if (version == "TLSv1.3") {
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
# if OPENSSL_VERSION_NUMBER >= 0x10101000L
return TLS1_3_VERSION;
#else /* OPENSSL_VERSION_NUMBER >= 0x10101000L */
# else /* OPENSSL_VERSION_NUMBER >= 0x10101000L */
throw std::runtime_error("'" + version + "' is only supported with OpenSSL 1.1.1 or newer");
#endif /* OPENSSL_VERSION_NUMBER >= 0x10101000L */
# endif /* OPENSSL_VERSION_NUMBER >= 0x10101000L */
#endif /* _WIN32 */
} else {
throw std::runtime_error("Unknown TLS protocol version '" + version + "'");
}
}

Shared<boost::asio::ssl::context>::Ptr SetupSslContext(String certPath, String keyPath,
String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di)
Shared<TlsContext>::Ptr SetupSslContext(const String& certPath, const String& keyPath,
const String& caPath, const String& crlPath, const String& cipherList, const String& protocolmin, DebugInfo di)
{
namespace ssl = boost::asio::ssl;

Shared<ssl::context>::Ptr context;
Shared<TlsContext>::Ptr context;

#ifdef _WIN32
auto method (TlsProtocolMin::system_default);
#else /* _WIN32 */
auto method (TlsContext::tls);
#endif /* _WIN32 */

InitializeOpenSSL();

#ifdef _WIN32
if (!protocolmin.IsEmpty()) {
try {
method = ResolveTlsProtocolVersion(protocolmin);
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + protocolmin + "'.", std::move(di)));
}
}
#endif /* _WIN32 */

try {
context = MakeAsioSslContext(certPath, keyPath, caPath);
context = Shared<TlsContext>::Make(method);

InitSslContext(context, certPath, keyPath, caPath);
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot make SSL context for cert path: '"
+ certPath + "' key path: '" + keyPath + "' ca path: '" + caPath + "'.", di));
+ certPath + "' key path: '" + keyPath + "' ca path: '" + caPath + "'.", std::move(di)));
}

if (!crlPath.IsEmpty()) {
try {
AddCRLToSSLContext(context, crlPath);
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot add certificate revocation list to SSL context for crl path: '"
+ crlPath + "'.", di));
+ crlPath + "'.", std::move(di)));
}
}

Expand All @@ -306,30 +313,33 @@ Shared<boost::asio::ssl::context>::Ptr SetupSslContext(String certPath, String k
SetCipherListToSSLContext(context, cipherList);
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot set cipher list to SSL context for cipher list: '"
+ cipherList + "'.", di));
+ cipherList + "'.", std::move(di)));
}
}

#ifndef _WIN32
if (!protocolmin.IsEmpty()){
try {
SetTlsProtocolminToSSLContext(context, protocolmin);
} catch (const std::exception&) {
BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + protocolmin + "'.", di));
BOOST_THROW_EXCEPTION(ScriptError("Cannot set minimum TLS protocol version to SSL context with tls_protocolmin: '" + protocolmin + "'.", std::move(di)));
}
}
#endif /* _WIN32 */

return context;
}

#ifndef _WIN32
/**
* Set the minimum TLS protocol version to the specified SSL context.
*
* @param context The ssl context.
* @param tlsProtocolmin The minimum TLS protocol version.
*/
void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& tlsProtocolmin)
void SetTlsProtocolminToSSLContext(const Shared<TlsContext>::Ptr& context, const String& tlsProtocolmin)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# if OPENSSL_VERSION_NUMBER >= 0x10100000L
int ret = SSL_CTX_set_min_proto_version(context->native_handle(), ResolveTlsProtocolVersion(tlsProtocolmin));

if (ret != 1) {
Expand All @@ -342,20 +352,21 @@ void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr&
<< boost::errinfo_api_function("SSL_CTX_set_min_proto_version")
<< errinfo_openssl_error(ERR_peek_error()));
}
#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
# else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
// This should never happen. On this OpenSSL version, ResolveTlsProtocolVersion() should either return TLS 1.2
// or throw an exception, as that's the only TLS version supported by both Icinga and ancient OpenSSL.
VERIFY(ResolveTlsProtocolVersion(tlsProtocolmin) == TLS1_2_VERSION);
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
# endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
}
#endif /* _WIN32 */

/**
* Loads a CRL and appends its certificates to the specified Boost SSL context.
*
* @param context The SSL context.
* @param crlPath The path to the CRL file.
*/
void AddCRLToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& crlPath)
void AddCRLToSSLContext(const Shared<TlsContext>::Ptr& context, const String& crlPath)
{
X509_STORE *x509_store = SSL_CTX_get_cert_store(context->native_handle());
AddCRLToSSLContext(x509_store, crlPath);
Expand Down
26 changes: 19 additions & 7 deletions lib/base/tlsutility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <boost/asio/ssl/context.hpp>
#include <boost/exception/info.hpp>

#ifdef _WIN32
# include <boost/wintls/context.hpp>
#endif /* _WIN32 */

namespace icinga
{

Expand All @@ -38,19 +42,27 @@ const auto LEAF_VALID_FOR = 60 * 60 * 24 * 397;
const auto RENEW_THRESHOLD = 60 * 60 * 24 * 30;
const auto RENEW_INTERVAL = 60 * 60 * 24;

#ifdef _WIN32
typedef boost::wintls::context TlsContext;
typedef boost::wintls::method TlsProtocolMin;
#else /* _WIN32 */
typedef boost::asio::ssl::context TlsContext;
typedef int TlsProtocolMin;

void SetTlsProtocolminToSSLContext(const Shared<TlsContext>::Ptr& context, const String& tlsProtocolmin);
#endif /* _WIN32 */

void InitializeOpenSSL();

String GetOpenSSLVersion();

Shared<boost::asio::ssl::context>::Ptr MakeAsioSslContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
void AddCRLToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& crlPath);
void AddCRLToSSLContext(const Shared<TlsContext>::Ptr& context, const String& crlPath);
void AddCRLToSSLContext(X509_STORE *x509_store, const String& crlPath);
void SetCipherListToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& cipherList);
void SetTlsProtocolminToSSLContext(const Shared<boost::asio::ssl::context>::Ptr& context, const String& tlsProtocolmin);
int ResolveTlsProtocolVersion(const std::string& version);
void SetCipherListToSSLContext(const Shared<TlsContext>::Ptr& context, const String& cipherList);
TlsProtocolMin ResolveTlsProtocolVersion(const std::string& version);

Shared<boost::asio::ssl::context>::Ptr SetupSslContext(String certPath, String keyPath,
String caPath, String crlPath, String cipherList, String protocolmin, DebugInfo di);
Shared<TlsContext>::Ptr SetupSslContext(const String& certPath = String(), const String& keyPath = String(), const String& caPath = String(),
const String& crlPath = String(), const String& cipherList = String(), const String& protocolmin = String(), DebugInfo di = {});

String GetCertificateCN(const std::shared_ptr<X509>& certificate);
std::shared_ptr<X509> GetX509Certificate(const String& pemfile);
Expand Down
2 changes: 1 addition & 1 deletion lib/base/win32.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#define WIN32_LEAN_AND_MEAN
#ifndef _WIN32_WINNT
#define _WIN32_WINNT _WIN32_WINNT_VISTA
#define _WIN32_WINNT _WIN32_WINNT_WIN7
#endif /* _WIN32_WINNT */
#define NOMINMAX
#include <winsock2.h>
Expand Down
7 changes: 3 additions & 4 deletions lib/cli/consolecommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,12 @@ int ConsoleCommand::RunScriptConsole(ScriptFrame& scriptFrame, const String& con
*/
Shared<AsioTlsStream>::Ptr ConsoleCommand::Connect()
{
Shared<boost::asio::ssl::context>::Ptr sslContext;
Shared<TlsContext>::Ptr sslContext;

try {
sslContext = MakeAsioSslContext(Empty, Empty, Empty); //TODO: Add support for cert, key, ca parameters
sslContext = SetupSslContext(); //TODO: Add support for cert, key, ca parameters
} catch(const std::exception& ex) {
Log(LogCritical, "DebugConsole")
<< "Cannot make SSL context: " << ex.what();
Log(LogCritical, "DebugConsole") << ex.what();
throw;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/icingadb/redisconnection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ namespace icinga
typedef boost::asio::buffered_stream<Tcp::socket> TcpConn;
typedef boost::asio::buffered_stream<Unix::socket> UnixConn;

Shared<boost::asio::ssl::context>::Ptr m_TLSContext;
Shared<TlsContext>::Ptr m_TLSContext;

template<class AsyncReadStream>
static Value ReadRESP(AsyncReadStream& stream, boost::asio::yield_context& yc);
Expand Down
4 changes: 2 additions & 2 deletions lib/methods/ifwapichecktask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,11 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes

auto& io (IoEngine::Get().GetIoContext());
auto strand (Shared<asio::io_context::strand>::Make(io));
Shared<asio::ssl::context>::Ptr ctx;
Shared<TlsContext>::Ptr ctx;
double start = Utility::GetTime();

try {
ctx = SetupSslContext(cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo());
ctx = SetupSslContext(cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN);
} catch (const std::exception& ex) {
ReportIfwCheckResult(checkable, cmdLine, cr, ex.what(), start, Utility::GetTime());
return;
Expand Down
Loading
Loading