371 lines
13 KiB
C++
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;
|
|
} |