blob: 33a90f5ae7e768589e0de1cfd24033669de19cac [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
#ifdef ENABLE_OPENSSL
#include "tls_util.h"
#include <fmt/ostream.h>
#include <openssl/err.h>
#include <openssl/opensslv.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <bitset>
#include <mutex>
#include <string>
#include "config.h"
#if OPENSSL_VERSION_NUMBER < 0x10100000L
std::unique_ptr<std::mutex[]> ssl_mutexes;
#endif
void InitSSL() {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_crypto_strings();
if (!RAND_poll()) {
ERROR("OpenSSL failed to generate random seed");
exit(1);
}
// OpenSSL do not provide builtin thread safety until 1.1.0
// so we need to use CRYPTO_set_locking_callback
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ssl_mutexes = std::unique_ptr<std::mutex[]>(new std::mutex[CRYPTO_num_locks()]);
CRYPTO_set_locking_callback([](int mode, int n, const char *, int) {
if (mode & CRYPTO_LOCK) {
ssl_mutexes[n].lock();
} else {
ssl_mutexes[n].unlock();
}
});
CRYPTO_set_id_callback([] { return (unsigned long)pthread_self(); }); // NOLINT
#endif
}
StatusOr<unsigned long> ParseSSLProtocols(const std::string &protocols) { // NOLINT
unsigned long ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; // NOLINT
if (protocols.empty()) {
return ctx_options | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
}
auto protos = util::Split(util::ToLower(protocols), " ");
std::bitset<4> has_protocol;
for (const auto &proto : protos) {
if (proto == "tlsv1") {
has_protocol[0] = true;
} else if (proto == "tlsv1.1") {
has_protocol[1] = true;
} else if (proto == "tlsv1.2") {
has_protocol[2] = true;
#ifdef SSL_OP_NO_TLSv1_3
} else if (proto == "tlsv1.3") {
has_protocol[3] = true;
#endif
} else {
return {Status::NotOK, "Failed to set SSL protocols: " + proto + " is not a valid protocol"};
}
}
if (!has_protocol[0]) {
ctx_options |= SSL_OP_NO_TLSv1;
}
if (!has_protocol[1]) {
ctx_options |= SSL_OP_NO_TLSv1_1;
}
if (!has_protocol[2]) {
ctx_options |= SSL_OP_NO_TLSv1_2;
}
#ifdef SSL_OP_NO_TLSv1_3
if (!has_protocol[3]) {
ctx_options |= SSL_OP_NO_TLSv1_3;
}
#endif
if (has_protocol.none()) {
return {Status::NotOK, "Failed to set SSL protocols: no protocol is enabled"};
}
return ctx_options;
}
UniqueSSLContext CreateSSLContext(const Config *config, const SSL_METHOD *method) {
if (config->tls_cert_file.empty() || config->tls_key_file.empty()) {
ERROR("Both tls-cert-file and tls-key-file must be specified while TLS is enabled");
return nullptr;
}
auto ssl_ctx = UniqueSSLContext(method);
if (!ssl_ctx) {
ERROR("Failed to construct SSL context: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
auto proto_status = ParseSSLProtocols(config->tls_protocols);
if (!proto_status) {
ERROR("{}", proto_status.Msg());
return nullptr;
}
auto ctx_options = *proto_status;
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
ctx_options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
#endif
#ifdef SSL_OP_NO_COMPRESSION
ctx_options |= SSL_OP_NO_COMPRESSION;
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
ctx_options |= SSL_OP_NO_CLIENT_RENEGOTIATION;
#endif
if (config->tls_prefer_server_ciphers) {
ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
}
SSL_CTX_set_options(ssl_ctx.get(), ctx_options);
SSL_CTX_set_mode(ssl_ctx.get(), SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
if (config->tls_session_caching) {
SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_SERVER);
SSL_CTX_set_timeout(ssl_ctx.get(), config->tls_session_cache_timeout);
SSL_CTX_sess_set_cache_size(ssl_ctx.get(), config->tls_session_cache_size);
const char *session_id = "kvrocks";
SSL_CTX_set_session_id_context(ssl_ctx.get(), (const unsigned char *)session_id, strlen(session_id));
} else {
SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_OFF);
}
if (config->tls_auth_clients == TLS_AUTH_CLIENTS_NO) {
SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_NONE, nullptr);
} else if (config->tls_auth_clients == TLS_AUTH_CLIENTS_OPTIONAL) {
SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_PEER, nullptr);
} else {
SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
}
auto ca_file = config->tls_ca_cert_file.empty() ? nullptr : config->tls_ca_cert_file.c_str();
auto ca_dir = config->tls_ca_cert_dir.empty() ? nullptr : config->tls_ca_cert_dir.c_str();
if (ca_file || ca_dir) {
if (SSL_CTX_load_verify_locations(ssl_ctx.get(), ca_file, ca_dir) != 1) {
ERROR("Failed to load CA certificates: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
} else if (config->tls_auth_clients != TLS_AUTH_CLIENTS_NO) {
ERROR("Either tls-ca-cert-file or tls-ca-cert-dir must be specified while tls-auth-clients is enabled");
return nullptr;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx.get(), config->tls_cert_file.c_str()) != 1) {
ERROR("Failed to load SSL certificate file: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
if (!config->tls_key_file_pass.empty()) {
SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx.get(), static_cast<void *>(const_cast<Config *>(config)));
SSL_CTX_set_default_passwd_cb(ssl_ctx.get(), [](char *buf, int size, int, void *cfg) -> int {
strncpy(buf, static_cast<const Config *>(cfg)->tls_key_file_pass.c_str(), size);
buf[size - 1] = '\0';
return static_cast<int>(strlen(buf));
});
}
if (SSL_CTX_use_PrivateKey_file(ssl_ctx.get(), config->tls_key_file.c_str(), SSL_FILETYPE_PEM) != 1) {
ERROR("Failed to load SSL private key file: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
if (SSL_CTX_check_private_key(ssl_ctx.get()) != 1) {
ERROR("Failed to check the loaded private key: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
if (!config->tls_ciphers.empty() && !SSL_CTX_set_cipher_list(ssl_ctx.get(), config->tls_ciphers.c_str())) {
ERROR("Failed to set SSL ciphers: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
#ifdef SSL_OP_NO_TLSv1_3
if (!config->tls_ciphersuites.empty() && !SSL_CTX_set_ciphersuites(ssl_ctx.get(), config->tls_ciphersuites.c_str())) {
ERROR("Failed to set SSL ciphersuites: {}", fmt::streamed(SSLErrors{}));
return nullptr;
}
#endif
return ssl_ctx;
}
std::ostream &operator<<(std::ostream &os, SSLErrors) {
ERR_print_errors_cb(
[](const char *str, size_t len, void *pos) {
*reinterpret_cast<std::ostream *>(pos) << std::string(str, len) << ";";
return 0;
},
reinterpret_cast<void *>(&os));
return os;
}
std::ostream &operator<<(std::ostream &os, SSLError e) {
char msg[1024] = {};
ERR_error_string_n(e.err, msg, sizeof(msg) - 1);
return os << msg;
}
#endif