feat: rewrite CertManager

This commit is contained in:
2026-04-11 11:38:36 -03:00
parent 105f8b6df0
commit e61c20bb5a
2 changed files with 162 additions and 149 deletions
+169 -158
View File
@@ -1,5 +1,8 @@
#include "ssl.h" #include "ssl.h"
#include "utils.h"
#include <nerutils/log.h> #include <nerutils/log.h>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/pem.h> #include <openssl/pem.h>
@@ -9,23 +12,25 @@
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <random>
#include <cstdlib> #include <cstdlib>
#include <ctime> #include <ctime>
#include <processthreadsapi.h>
#include <cstring> #include <cstring>
std::string randomizeString(size_t length) #include <wincrypt.h>
{ #include <processthreadsapi.h>
const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
std::string result;
result.resize(length);
for (size_t i = 0; i < length; ++i)
result[i] = charset[rand() % (sizeof(charset) - 1)];
return result;
}
CertManager::CertManager() : _sessionPkey(nullptr) {} template <typename T, void (*f)(T*)> struct Deleter
{
void operator()(T* p) const { f(p); }
};
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, Deleter<EVP_PKEY, EVP_PKEY_free>>;
using X509_ptr = std::unique_ptr<X509, Deleter<X509, X509_free>>;
using SSL_CTX_ptr = std::unique_ptr<SSL_CTX, Deleter<SSL_CTX, SSL_CTX_free>>;
using BIO_ptr = std::unique_ptr<BIO, Deleter<BIO, BIO_vfree>>;
CertManager::CertManager() : _sessionPkey(nullptr), _caPkey(nullptr), _caCert(nullptr) {}
CertManager::~CertManager() CertManager::~CertManager()
{ {
@@ -37,185 +42,191 @@ CertManager::~CertManager()
SSL_CTX_free(pair.second); SSL_CTX_free(pair.second);
} }
bool CertManager::Init() bool CertManager::init()
{ {
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
if (pctx)
{
EVP_PKEY_keygen_init(pctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
EVP_PKEY_keygen(pctx, &_sessionPkey);
EVP_PKEY_CTX_free(pctx);
}
if (LoadCA())
{
Log::verbose("Loaded existing CA certificate.");
return true;
}
Log::verbose("No CA found. Generating new CA certificate.");
return GenerateCA();
}
bool CertManager::LoadCA()
{
BIO* keyBio = BIO_new_file("ca_key.pem", "r");
if (!keyBio) return false;
_caPkey = PEM_read_bio_PrivateKey(keyBio, nullptr, nullptr, nullptr);
BIO_free(keyBio);
if (!_caPkey) return false;
BIO* certBio = BIO_new_file("ca_cert.pem", "r");
if (!certBio) return false;
_caCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr);
BIO_free(certBio);
if (!_caCert) return false;
return true;
}
bool CertManager::GenerateCA()
{
srand(static_cast<unsigned int>(time(nullptr)));
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
if (!pctx) return false; if (!pctx) return false;
EVP_PKEY_keygen_init(pctx); EVP_PKEY_keygen_init(pctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048); EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
EVP_PKEY_keygen(pctx, &_caPkey);
EVP_PKEY* rawPkey = nullptr;
if (EVP_PKEY_keygen(pctx, &rawPkey) <= 0)
{
Log::error("Failed to generate session key");
EVP_PKEY_CTX_free(pctx);
return false;
}
_sessionPkey = rawPkey;
EVP_PKEY_CTX_free(pctx); EVP_PKEY_CTX_free(pctx);
_caCert = X509_new(); if (loadCA())
X509_set_version(_caCert, 2);
ASN1_INTEGER_set(X509_get_serialNumber(_caCert), 1);
X509_gmtime_adj(X509_get_notBefore(_caCert), 0);
X509_gmtime_adj(X509_get_notAfter(_caCert), 31536000L); // 1 year
std::string org = randomizeString(16);
std::string cn = randomizeString(16);
X509_NAME* name = X509_get_subject_name(_caCert);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"US", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)org.c_str(), -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)cn.c_str(), -1, -1, 0);
X509_set_issuer_name(_caCert, name);
X509_set_pubkey(_caCert, _caPkey);
X509V3_CTX ctxV3;
X509V3_set_ctx_nodb(&ctxV3);
X509V3_set_ctx(&ctxV3, _caCert, _caCert, nullptr, nullptr, 0);
X509_EXTENSION* extCA = X509V3_EXT_conf_nid(nullptr, &ctxV3, NID_basic_constraints, "critical,CA:TRUE");
if (extCA)
{ {
X509_add_ext(_caCert, extCA, -1); Log::verbose("Loaded existing CA certificate");
X509_EXTENSION_free(extCA); return true;
} }
X509_sign(_caCert, _caPkey, EVP_sha256()); Log::verbose("No CA found, generating");
return generateCA();
}
BIO* keyBioOut = BIO_new_file("ca_key.pem", "w"); bool CertManager::loadCA()
if (keyBioOut) {
std::string path = utils::getExePath();
BIO_ptr keyBio(BIO_new_file((path + "/key.pem").c_str(), "r"));
if (!keyBio) return false;
_caPkey = PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr);
BIO_ptr certBio(BIO_new_file((path + "/cert.pem").c_str(), "r"));
if (!certBio) return false;
_caCert = PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr);
return (_caPkey && _caCert);
}
void CertManager::installCert(X509* cert)
{
if (!cert) return;
/*
X509 to DER
*/
int derLen = i2d_X509(cert, nullptr);
if (derLen < 0) return;
unsigned char* derBuf = new unsigned char[derLen];
unsigned char* p = derBuf;
i2d_X509(cert, &p);
PCCERT_CONTEXT certCtx = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, derBuf, derLen);
if (certCtx)
{ {
PEM_write_bio_PrivateKey(keyBioOut, _caPkey, nullptr, nullptr, 0, nullptr, nullptr); HCERTSTORE rootStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"Root");
BIO_free(keyBioOut); if (rootStore)
{
BOOL success =
CertAddCertificateContextToStore(rootStore, certCtx, CERT_STORE_ADD_REPLACE_EXISTING, NULL);
if (success)
Log::info("CA certificate installed");
else
Log::error("Failed to install CA certificate");
CertCloseStore(rootStore, 0);
}
CertFreeCertificateContext(certCtx);
} }
BIO* certBioOut = BIO_new_file("ca_cert.pem", "w"); delete[] derBuf;
if (certBioOut) }
{
PEM_write_bio_X509(certBioOut, _caCert);
BIO_free(certBioOut);
}
Log::info("Generated new CA key and certificate files. Installing to Windows Root CA store automatically..."); bool CertManager::generateCA()
{
std::random_device rd;
std::mt19937 gen(rd());
STARTUPINFOA si; /*
memset(&si, 0, sizeof(si)); key
si.cb = sizeof(si); */
PROCESS_INFORMATION pi; EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
memset(&pi, 0, sizeof(pi)); EVP_PKEY_keygen_init(pctx);
char cmd[] = "certutil.exe -user -addstore root ca_cert.pem"; EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) EVP_PKEY* rawCaKey = nullptr;
{ EVP_PKEY_keygen(pctx, &rawCaKey);
WaitForSingleObject(pi.hProcess, INFINITE); _caPkey = rawCaKey;
CloseHandle(pi.hProcess); EVP_PKEY_CTX_free(pctx);
CloseHandle(pi.hThread);
} /*
cert
*/
X509_ptr cert(X509_new());
X509_set_version(cert.get(), 2);
ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1);
X509_gmtime_adj(X509_get_notBefore(cert.get()), 0);
X509_gmtime_adj(X509_get_notAfter(cert.get()), 31536000L); // 1 year
X509_NAME* name = X509_get_subject_name(cert.get());
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"Debug Proxy CA", -1, -1, 0);
X509_set_issuer_name(cert.get(), name);
X509_set_pubkey(cert.get(), _caPkey);
/*
CA constraints
*/
X509V3_CTX v3ctx;
X509V3_set_ctx(&v3ctx, cert.get(), cert.get(), nullptr, nullptr, 0);
X509_EXTENSION* ext = X509V3_EXT_conf_nid(nullptr, &v3ctx, NID_basic_constraints, "critical,CA:TRUE");
X509_add_ext(cert.get(), ext, -1);
X509_EXTENSION_free(ext);
if (X509_sign(cert.get(), _caPkey, EVP_sha256()) <= 0) return false;
/*
out
*/
std::string path = utils::getExePath();
BIO_ptr kOut(BIO_new_file((path + "/key.pem").c_str(), "w"));
PEM_write_bio_PrivateKey(kOut.get(), _caPkey, nullptr, nullptr, 0, nullptr, nullptr);
BIO_ptr cOut(BIO_new_file((path + "/cert.pem").c_str(), "w"));
PEM_write_bio_X509(cOut.get(), cert.get());
/*
install and release
*/
installCert(cert.get());
_caCert = cert.release();
return true; return true;
} }
SSL_CTX* CertManager::CreateHostContext(const std::string& host) SSL_CTX* CertManager::createHostContext(const std::string& host)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
if (_hostContexts.count(host)) return _hostContexts[host];
auto it = _hostContexts.find(host); /*
if (it != _hostContexts.end()) cert base
{ */
return it->second; X509_ptr cert(X509_new());
} X509_set_version(cert.get(), 2);
EVP_PKEY* pkey = _sessionPkey; ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), static_cast<long>(std::hash<std::string>{}(host) & 0x7FFFFFFF));
if (!pkey) return nullptr;
X509* cert = X509_new(); X509_gmtime_adj(X509_get_notBefore(cert.get()), 0);
X509_set_version(cert, 2); X509_gmtime_adj(X509_get_notAfter(cert.get()), 31536000L);
ASN1_INTEGER_set(X509_get_serialNumber(cert), static_cast<long>(std::hash<std::string>{}(host) & 0x7FFFFFFF));
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
std::string dynamicOrg = randomizeString(16); X509_NAME* name = X509_get_subject_name(cert.get());
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)host.c_str(), -1, -1, 0);
X509_set_issuer_name(cert.get(), X509_get_subject_name(_caCert));
X509_set_pubkey(cert.get(), _sessionPkey);
X509_NAME* name = X509_get_subject_name(cert); /*
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"US", -1, -1, 0); SAN
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)dynamicOrg.c_str(), -1, -1, 0); */
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)(host.c_str()), -1, -1, 0); X509V3_CTX v3ctx;
X509_set_issuer_name(cert, X509_get_subject_name(_caCert)); X509V3_set_ctx(&v3ctx, _caCert, cert.get(), nullptr, nullptr, 0);
X509_set_pubkey(cert, pkey); std::string altName = "DNS:" + host;
X509_EXTENSION* ext = X509V3_EXT_conf_nid(nullptr, &v3ctx, NID_subject_alt_name, altName.c_str());
X509_add_ext(cert.get(), ext, -1);
X509_EXTENSION_free(ext);
X509V3_CTX ctxV3; /*
X509V3_set_ctx_nodb(&ctxV3); sign & ctx load
X509V3_set_ctx(&ctxV3, _caCert, cert, nullptr, nullptr, 0); */
std::string san = "DNS:" + host; if (X509_sign(cert.get(), _caPkey, EVP_sha256()) <= 0) return nullptr;
X509_EXTENSION* extSAN = X509V3_EXT_conf_nid(nullptr, &ctxV3, NID_subject_alt_name, san.c_str());
if (extSAN)
{
X509_add_ext(cert, extSAN, -1);
X509_EXTENSION_free(extSAN);
}
X509_sign(cert, _caPkey, EVP_sha256());
SSL_CTX* ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) return nullptr;
SSL_CTX_set_alpn_select_cb( if (SSL_CTX_use_certificate(ctx, cert.get()) <= 0 || SSL_CTX_use_PrivateKey(ctx, _sessionPkey) <= 0)
ctx,
[](SSL* /*ssl*/, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen,
void* /*arg*/) -> int {
for (unsigned int i = 0; i < inlen;)
{ {
unsigned int len = in[i]; SSL_CTX_free(ctx);
if (len == 8 && memcmp(&in[i + 1], "http/1.1", 8) == 0) return nullptr;
{
*out = &in[i + 1];
*outlen = (unsigned char)len;
return SSL_TLSEXT_ERR_OK;
} }
i += len + 1;
}
return SSL_TLSEXT_ERR_NOACK;
},
nullptr);
SSL_CTX_use_certificate(ctx, cert);
SSL_CTX_use_PrivateKey(ctx, pkey);
X509_free(cert);
_hostContexts[host] = ctx; _hostContexts[host] = ctx;
return ctx; return ctx;
+6 -4
View File
@@ -20,12 +20,14 @@ class CertManager
CertManager(); CertManager();
~CertManager(); ~CertManager();
bool Init(); bool init();
SSL_CTX* CreateHostContext(const std::string& host); SSL_CTX* createHostContext(const std::string& host);
private: private:
bool GenerateCA(); bool generateCA();
bool LoadCA(); bool loadCA();
void installCert(X509* cert);
EVP_PKEY* _caPkey = nullptr; EVP_PKEY* _caPkey = nullptr;
X509* _caCert = nullptr; X509* _caCert = nullptr;