TeaSpeakLibrary/src/ssl/SSLManager.cpp

371 lines
13 KiB
C++

#include <fstream>
#include <cstring>
#include <log/LogUtils.h>
#include <experimental/filesystem>
#include <misc/digest.h>
#include "SSLManager.h"
namespace fs = std::experimental::filesystem;
/*
openssl req -x509 -nodes -out default_certificate.pem -newkey rsa:2048 -keyout default_privatekey.pem -days 3650 -config <(
cat <<-EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
C=DE
O=TeaSpeak
OU=Web Server
emailAddress=contact@teaspeak.de
CN = web.teaspeak.de
EOF
)
*/
/*
static std::string SSL_DEFAULT_KEY = R"(
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrnuiIrA/uK/VI
yUDFIc8aClGNzlWvCL18LmhCdE0Mlp0X6aYS/lergGJZark8dNbARNcsU41535L/
0IrqhQr3zsaZqK4sQ5xKtRpUXjknFPMRIcfx7efA/8687yxJ90IJbov7EOT2cVF+
nzE4DNPFyih6G42wMeMTfnuy60fABblWZikeYNM+/YtFcB1uTiJhPWid0URo85d+
e0ifrO7y3EszGd1Hl27oroyFAeGRP5MyU3cj0ZUBjxEdEnJXmAQnHM4cO6tC9E62
775J4G+abJy6TdyxUUK9LhtfTpMnLYpGhMzJwslrfqQ9almY2K7X0xzBYxRNRXsA
B6sBI1llAgMBAAECggEBAJ7pic/j4uxa78jx8XOYFri6DUINaPGmWi5+mjPOlPmv
DM9znj/AG1XGj0rUs6jzV1a5Z7S3uSy8hNUzOS5m+vzzDpqBwqViBXp3r2WnyawS
je+zI/00mX/wXnI71Pq4ZQFux1c3EYvQ6fEhXuXTmtRumIRYtx4LU4RdfhTyH4IB
Rqsb6tPyO0gVPdTL5V3O74bSs0k2QVkm4U/UKiuDJeY5Upy/MX0y+ObEzkxCL/e5
o7jB0DkvCu5wjHWWoA/hduvBXLelbPqxXSuz6YGGPiOaM131Zmk1JIkmeANX1T8b
raWg4yV7oiOprAyx2ioU6Q55w+AYfq2q+noJwEOOrYECgYEA0fJ+pfywksV2eXBu
N2TkruTYsMGE7bSvM8E6ABkYutUz4bC8ytHszs6e3vMhojO6aHqp389vFrPHSu23
1Uqk/eVBbRSHzbvRXl35OzNzeSfmJKkzbG8OnLnukoHzDJUPrhFUHBR8KHnuIycv
87Lj+wmpIaAfaFCct8sKbgnB3e0CgYEA0UQ0GLS6BnwfwdKJVubaIc6P1wtr7vam
UVFntusx3v8AI1qiQ6EzQ73NX9eh3L5sKVcrMtBLur3G/Ferp135J6V2+ZYc1nUA
67x/9wquo42SxlRCfx00zeCCGU0Q56/UEND626So7E6k1sgKnWXq5uuQYZU0wpbi
InD1oo+bulkCgYA2wd2AY2CWV0QoNke40OrIJs3RhBese9S6VepPvjvx9st6UMNc
ztXJtqA/HACosn8q4ttNkWey7x7KjyfETJytz855qcIlyZe42h+37hpu/hYLd8n+
vRR9kg0ETzpaDMKzLrfWPw2G7Q5MQttB32WQwxtGtuGaLnRBh4Zn3smenQKBgANF
DYtVR5LSXaypnXu+H6pnj9fMVeNl9zNOElDJW/4f/eCPifmEi0iDrrHQrLbGQupi
ckpY9tX0ISfQNt5mmX4FF9bOgaTYLyt/xoAVqqTjkWeH6YIS8sBEwcOjcKAuHyIk
IcdMy1bl4613crMC5Ki3BYqAylJACUiAe1YO6GABAoGBALk+VuDvY8IxcSTOZXxe
hGG/TZlHz/xfvuOC0ngsfX+C7Q98KDh72SzBDk4wSqWTLTLPn4jZArmD+gVagNz9
GSb4gpxinfXnb1px0BjR7YoVxJnk8FEjxe7PPSeYWt5e0Liyl0LPBm6xK0LXppyr
N9G+1ojrRslgE5HGdZ9Axi54
-----END PRIVATE KEY-----
)";
static std::string SSL_DEFAULT_CERT = R"(
-----BEGIN CERTIFICATE-----
MIIDYjCCAkoCCQCagl242EEilDANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJE
RTERMA8GA1UECgwIVGVhU3BlYWsxEzARBgNVBAsMCldlYiBTZXJ2ZXIxIjAgBgkq
hkiG9w0BCQEWE2NvbnRhY3RAdGVhc3BlYWsuZGUxGDAWBgNVBAMMD3dlYi50ZWFz
cGVhay5kZTAeFw0xODAzMjIxNTM4MzVaFw0yODAzMTkxNTM4MzVaMHMxCzAJBgNV
BAYTAkRFMREwDwYDVQQKDAhUZWFTcGVhazETMBEGA1UECwwKV2ViIFNlcnZlcjEi
MCAGCSqGSIb3DQEJARYTY29udGFjdEB0ZWFzcGVhay5kZTEYMBYGA1UEAwwPd2Vi
LnRlYXNwZWFrLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq57o
iKwP7iv1SMlAxSHPGgpRjc5Vrwi9fC5oQnRNDJadF+mmEv5Xq4BiWWq5PHTWwETX
LFONed+S/9CK6oUK987GmaiuLEOcSrUaVF45JxTzESHH8e3nwP/OvO8sSfdCCW6L
+xDk9nFRfp8xOAzTxcooehuNsDHjE357sutHwAW5VmYpHmDTPv2LRXAdbk4iYT1o
ndFEaPOXfntIn6zu8txLMxndR5du6K6MhQHhkT+TMlN3I9GVAY8RHRJyV5gEJxzO
HDurQvROtu++SeBvmmycuk3csVFCvS4bX06TJy2KRoTMycLJa36kPWpZmNiu19Mc
wWMUTUV7AAerASNZZQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCGNR1J3ynUVwuR
D7RrxfsVYiW/Wx6/i+MQCsk0R+lrsjlkPIUr8iIQu762QszWFaubdh8jqXVu5psT
utQk5RoZ5XrKUcE2av9G4o6Grj3BcsT3JXQcHtIpuDQnJDFoRe950YSVmZKbpvL/
STN46EjSSiDUpv1qqXeVr9CEyCZftj4esJ6RvJwYeKBG8HXoNzMYK32N6JWGbZFu
U76TSNwcjXNId43V4OVFZ/ReaD8Nvzq10GwgKb2HshQtdIvOVNfAAk/mX4e+5I1k
9zTEm+IBljBFvNzAKQRxUiiUDTjazKVt51ToJhRVBGXtPmVvhKacWWC3tbHdWisG
5vm7hxLQ
-----END CERTIFICATE-----
)";
*/
using namespace ts;
using namespace ts::ssl;
using namespace std;
SSLManager::SSLManager() = default;
SSLManager::~SSLManager() = default;
bool SSLManager::initialize() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
return true;
}
static auto ERR_TO_STRING = [](const char* err, size_t length, void* ptrTarget){
auto target = (string*) ptrTarget;
target->resize(length);
memcpy((void *) target->data(), err, length);
return 0;
};
#define SSL_ERROR(message) \
do { \
ERR_print_errors_cb(ERR_TO_STRING, &error); \
error = (message) + error; \
return nullptr; \
} while(false)
std::shared_ptr<SSLContext> SSLManager::initializeContext(const std::string &key, std::string &privateKey, std::string &certificate, std::string &error, bool raw, const std::shared_ptr<SSLGenerator>& generator) {
auto load = this->loadContext(privateKey, certificate, error, raw, generator);
if(!load) return nullptr;
this->contexts[key] = load;
return load;
}
std::shared_ptr<SSLKeyPair> SSLManager::initializeSSLKey(const std::string &key, const std::string &rsaKey, std::string &error, bool raw) {
auto load = this->loadSSL(rsaKey, error, raw);
if(!load) return nullptr;
this->rsa[key] = load;
return load;
}
EVP_PKEY* SSLGenerator::generateKey() {
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
BN_set_word(e.get(), RSA_F4);
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
EVP_PKEY_assign_RSA(key.get(), rsa);
return key.release();
}
X509* SSLGenerator::generateCertificate(EVP_PKEY* key) {
auto cert = X509_new();
X509_set_pubkey(cert, key);
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
X509_NAME* name = nullptr;
name = X509_get_subject_name(cert);
for(const auto& subject : this->subjects)
X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
X509_set_subject_name(cert, name);
name = X509_get_issuer_name(cert);
for(const auto& subject : this->issues)
X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
X509_set_issuer_name(cert, name);
X509_sign(cert, key, EVP_sha512());
return cert;
}
//TODO passwords
std::shared_ptr<SSLContext> SSLManager::loadContext(std::string &rawKey, std::string &rawCert, std::string &error, bool rawData, const shared_ptr<SSLGenerator>& generator) {
std::shared_ptr<BIO> certBio = nullptr;
std::shared_ptr<BIO> keyBio = nullptr;
std::shared_ptr<X509> cert = nullptr;
std::shared_ptr<EVP_PKEY> key = nullptr;
bool flagCertModified = false;
bool flagKeyModified = false;
std::shared_ptr<SSL_CTX> context = nullptr;
std::shared_ptr<SSLContext> result = nullptr;
if(rawData) {
certBio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
BIO_write(certBio.get(), rawCert.c_str(), rawCert.length());
keyBio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
BIO_write(keyBio.get(), rawKey.c_str(), rawKey.length());
} else {
auto keyPath = fs::u8path(rawKey);
auto certPath = fs::u8path(rawCert);
if(!fs::exists(keyPath)) {
if(!generator) {
error = "Missing key file";
return nullptr;
}
try {
if(keyPath.has_parent_path())
fs::create_directories(keyPath.parent_path());
} catch (fs::filesystem_error& error) {
logError("Could not create key directory: " + string(error.what()));
}
{
std::ofstream { keyPath };
}
}
if(!fs::exists(certPath)) {
if(!generator) {
error = "Missing certificate file";
return nullptr;
}
try {
if(certPath.has_parent_path())
fs::create_directories(certPath.parent_path());
} catch (fs::filesystem_error& error) {
logError("Could not create certificate directory: " + string(error.what()));
}
{
std::ofstream { certPath };
}
}
auto mode = generator ? "rw" : "r";
certBio = shared_ptr<BIO>(BIO_new_file(rawCert.c_str(), mode), ::BIO_free);
if(!certBio) SSL_ERROR("Could not load certificate: ");
keyBio = shared_ptr<BIO>(BIO_new_file(rawKey.c_str(), mode), ::BIO_free);
if(!keyBio) SSL_ERROR("Could not load key: ");
}
cert = shared_ptr<X509>(PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr), ::X509_free);
if(!cert && !generator) SSL_ERROR("Could not read certificate: ");
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
if(!key && !generator) SSL_ERROR("Could not read key: ");
if(!key) {
key = shared_ptr<EVP_PKEY>(generator->generateKey(), ::EVP_PKEY_free);
flagKeyModified = true;
}
if(!cert) {
cert = shared_ptr<X509>(generator->generateCertificate(key.get()), ::X509_free);
flagCertModified = true;
}
//Create context
context = shared_ptr<SSL_CTX>(SSL_CTX_new(SSLv23_server_method()), ::SSL_CTX_free);
if (!context) SSL_ERROR("Could not create context: ");
if (SSL_CTX_use_PrivateKey(context.get(), key.get()) <= 0) SSL_ERROR("Could not use private key: ");
if (SSL_CTX_use_certificate(context.get(), cert.get()) <= 0) SSL_ERROR("Could not use certificate: ");
result = std::make_shared<SSLContext>();
result->context = context;
result->certificate = cert;
result->privateKey = key;
if(flagCertModified) {
if(!rawData) {
certBio = shared_ptr<BIO>(BIO_new_file(rawCert.c_str(), "w"), ::BIO_free);
if(PEM_write_bio_X509(certBio.get(), cert.get()) != 1) SSL_ERROR("Could not write new certificate: ");
flagCertModified = false;
} else {
assert(false); //TODO implement with membuf
/*
void* ptr = nullptr;
auto length = BIO_get_mem_data(certBio, &ptr);
if(!ptr) SSL_ERROR("Could not get cert bio mem pointer: ");
if(length <= 0) SSL_ERROR("Could not get cert bio mem length (" + to_string(length) + "): ");
rawCert.reserve(length);
memcpy((void*) rawCert.data(), ptr, length);
*/
}
}
if(flagKeyModified) {
if(!rawData) {
keyBio = shared_ptr<BIO>(BIO_new_file(rawKey.c_str(), "w"), ::BIO_free);
if(PEM_write_bio_PrivateKey(keyBio.get(), key.get(), nullptr, nullptr, 0, nullptr, nullptr) != 1) SSL_ERROR("Could not write new key: ");
flagKeyModified = false;
} else {
assert(false); //TODO implement with membuf
/*
void* ptr = nullptr;
auto length = BIO_get_mem_data(certBio, &ptr);
if(!ptr) SSL_ERROR("Could not get cert bio mem pointer: ");
if(length <= 0) SSL_ERROR("Could not get cert bio mem length (" + to_string(length) + "): ");
rawCert.reserve(length);
memcpy((void*) rawCert.data(), ptr, length);
*/
}
}
return result;
}
std::shared_ptr<SSLKeyPair> SSLManager::loadSSL(const std::string &rawKey, std::string &error, bool rawData, bool readPublic) {
std::shared_ptr<BIO> keyBio = nullptr;
std::shared_ptr<EVP_PKEY> key = nullptr;
std::shared_ptr<SSLKeyPair> result = make_shared<SSLKeyPair>();
// SSL_CTX_set_ecdh_auto(ctx, 1);
if(rawData) {
keyBio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
BIO_write(keyBio.get(), rawKey.c_str(), rawKey.length());
} else {
auto keyPath = fs::u8path(rawKey);
if(!fs::exists(keyPath)) {
try {
if(keyPath.has_parent_path())
fs::create_directories(keyPath.parent_path());
} catch (fs::filesystem_error& error) {
logError("Could not create key directory: " + string(error.what()));
}
{
std::ofstream { keyPath };
}
}
keyBio = shared_ptr<BIO>(BIO_new_file(rawKey.c_str(), "r"), ::BIO_free);
if(!keyBio) SSL_ERROR("Could not load key: ");
}
if(readPublic)
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
else
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
result->contains_private = !readPublic;
if(!key) {
if(readPublic) {
SSL_ERROR("Could not read key!");
} else return this->loadSSL(rawKey, error, rawData, true);
}
result->key = key;
return result;
}
bool SSLManager::verifySign(const std::shared_ptr<SSLKeyPair> &key, const std::string &message, const std::string &sign) {
assert(key);
auto hash = digest::sha256(message);
return RSA_verify(NID_sha256, (u_char*) hash.data(), hash.length(), (u_char*) sign.data(), sign.length(), EVP_PKEY_get1_RSA(key->key.get())) == 1;
}
std::shared_ptr<pipes::SSL::Options> SSLManager::web_ssl_options() {
lock_guard lock(this->_web_options_lock);
if(this->_web_options || this->_web_disabled)
return this->_web_options;
this->_web_options = make_shared<pipes::SSL::Options>();
this->_web_options->type = pipes::SSL::SERVER;
this->_web_options->context_method = TLS_method();
this->_web_options->free_unused_keypairs = false; /* we dont want our keys get removed */
string web_prefix = "web_";
for(auto& context : this->contexts) {
auto name = context.first;
if(name.length() < web_prefix.length())
continue;
if(name.substr(0, web_prefix.length()) != web_prefix)
continue;
auto servername = context.first.substr(web_prefix.length());
if(servername == "default") {
this->_web_options->default_keypair({context.second->privateKey, context.second->certificate});
} else {
this->_web_options->servername_keys[servername] = {context.second->privateKey, context.second->certificate};
}
}
return this->_web_options;
}