feat: add cert generation
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#include "cert_manager.h"
|
||||
#include <nerutils/log.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
std::string randomizeString(size_t length)
|
||||
{
|
||||
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() {}
|
||||
|
||||
CertManager::~CertManager()
|
||||
{
|
||||
if (_caPkey) EVP_PKEY_free(_caPkey);
|
||||
if (_caCert) X509_free(_caCert);
|
||||
|
||||
for (auto& pair : _hostContexts)
|
||||
SSL_CTX_free(pair.second);
|
||||
}
|
||||
|
||||
bool CertManager::Init()
|
||||
{
|
||||
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);
|
||||
if (!pctx) return false;
|
||||
EVP_PKEY_keygen_init(pctx);
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
|
||||
EVP_PKEY_keygen(pctx, &_caPkey);
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
|
||||
_caCert = X509_new();
|
||||
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);
|
||||
X509_EXTENSION_free(extCA);
|
||||
}
|
||||
|
||||
X509_sign(_caCert, _caPkey, EVP_sha256());
|
||||
|
||||
BIO* keyBioOut = BIO_new_file("ca_key.pem", "w");
|
||||
if (keyBioOut)
|
||||
{
|
||||
PEM_write_bio_PrivateKey(keyBioOut, _caPkey, nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BIO_free(keyBioOut);
|
||||
}
|
||||
|
||||
BIO* certBioOut = BIO_new_file("ca_cert.pem", "w");
|
||||
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...");
|
||||
|
||||
system("certutil.exe -user -addstore root ca_cert.pem");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SSL_CTX* CertManager::CreateHostContext(const std::string& host)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
auto it = _hostContexts.find(host);
|
||||
if (it != _hostContexts.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Log::verbose("Generating dynamic certificate for {}", host);
|
||||
|
||||
EVP_PKEY* pkey = nullptr;
|
||||
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
|
||||
EVP_PKEY_keygen_init(pctx);
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
|
||||
EVP_PKEY_keygen(pctx, &pkey);
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
|
||||
X509* cert = X509_new();
|
||||
X509_set_version(cert, 2);
|
||||
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);
|
||||
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*)dynamicOrg.c_str(), -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)(host.c_str()), -1, -1, 0);
|
||||
X509_set_issuer_name(cert, X509_get_subject_name(_caCert));
|
||||
X509_set_pubkey(cert, pkey);
|
||||
|
||||
X509V3_CTX ctxV3;
|
||||
X509V3_set_ctx_nodb(&ctxV3);
|
||||
X509V3_set_ctx(&ctxV3, _caCert, cert, nullptr, nullptr, 0);
|
||||
std::string san = "DNS:" + host;
|
||||
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_use_certificate(ctx, cert);
|
||||
SSL_CTX_use_PrivateKey(ctx, pkey);
|
||||
|
||||
X509_free(cert);
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
_hostContexts[host] = ctx;
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
class CertManager {
|
||||
public:
|
||||
CertManager();
|
||||
~CertManager();
|
||||
|
||||
bool Init();
|
||||
SSL_CTX* CreateHostContext(const std::string& host);
|
||||
|
||||
private:
|
||||
bool GenerateCA();
|
||||
bool LoadCA();
|
||||
|
||||
EVP_PKEY* _caPkey = nullptr;
|
||||
X509* _caCert = nullptr;
|
||||
|
||||
std::mutex _mutex;
|
||||
std::unordered_map<std::string, SSL_CTX*> _hostContexts;
|
||||
};
|
||||
Reference in New Issue
Block a user