diff --git a/src/unlocker/ssl.cpp b/src/unlocker/ssl.cpp index 0b97977..d3c825d 100644 --- a/src/unlocker/ssl.cpp +++ b/src/unlocker/ssl.cpp @@ -1,5 +1,8 @@ #include "ssl.h" +#include "utils.h" + #include + #include #include #include @@ -9,23 +12,25 @@ #include #include +#include #include #include - -#include #include -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; -} +#include +#include -CertManager::CertManager() : _sessionPkey(nullptr) {} +template struct Deleter +{ + void operator()(T* p) const { f(p); } +}; + +using EVP_PKEY_ptr = std::unique_ptr>; +using X509_ptr = std::unique_ptr>; +using SSL_CTX_ptr = std::unique_ptr>; +using BIO_ptr = std::unique_ptr>; + +CertManager::CertManager() : _sessionPkey(nullptr), _caPkey(nullptr), _caCert(nullptr) {} CertManager::~CertManager() { @@ -37,185 +42,191 @@ CertManager::~CertManager() 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 (!pctx) return false; - if (LoadCA()) + EVP_PKEY_keygen_init(pctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048); + + EVP_PKEY* rawPkey = nullptr; + if (EVP_PKEY_keygen(pctx, &rawPkey) <= 0) { - Log::verbose("Loaded existing CA certificate."); + Log::error("Failed to generate session key"); + EVP_PKEY_CTX_free(pctx); + return false; + } + _sessionPkey = rawPkey; + 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(); + Log::verbose("No CA found, generating"); + return generateCA(); } -bool CertManager::LoadCA() +bool CertManager::loadCA() { - BIO* keyBio = BIO_new_file("ca_key.pem", "r"); + 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); - _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"); + 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); - _caCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr); - BIO_free(certBio); - - if (!_caCert) return false; - - return true; + return (_caPkey && _caCert); } -bool CertManager::GenerateCA() +void CertManager::installCert(X509* cert) { - srand(static_cast(time(nullptr))); + 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) + { + HCERTSTORE rootStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"Root"); + 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); + } + + delete[] derBuf; +} + +bool CertManager::generateCA() +{ + std::random_device rd; + std::mt19937 gen(rd()); + + /* + key + */ 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* rawCaKey = nullptr; + EVP_PKEY_keygen(pctx, &rawCaKey); + _caPkey = rawCaKey; 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 + /* + 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 - std::string org = randomizeString(16); - std::string cn = randomizeString(16); + 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); - 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); + /* + 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); - 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); - } + if (X509_sign(cert.get(), _caPkey, EVP_sha256()) <= 0) return false; - X509_sign(_caCert, _caPkey, EVP_sha256()); + /* + 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* 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_ptr cOut(BIO_new_file((path + "/cert.pem").c_str(), "w")); + PEM_write_bio_X509(cOut.get(), cert.get()); - BIO* certBioOut = BIO_new_file("ca_cert.pem", "w"); - if (certBioOut) - { - PEM_write_bio_X509(certBioOut, _caCert); - BIO_free(certBioOut); - } + /* + install and release + */ + installCert(cert.get()); - Log::info("Generated new CA key and certificate files. Installing to Windows Root CA store automatically..."); - - STARTUPINFOA si; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - PROCESS_INFORMATION pi; - memset(&pi, 0, sizeof(pi)); - char cmd[] = "certutil.exe -user -addstore root ca_cert.pem"; - if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) - { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } + _caCert = cert.release(); return true; } -SSL_CTX* CertManager::CreateHostContext(const std::string& host) +SSL_CTX* CertManager::createHostContext(const std::string& host) { std::lock_guard lock(_mutex); + if (_hostContexts.count(host)) return _hostContexts[host]; - auto it = _hostContexts.find(host); - if (it != _hostContexts.end()) - { - return it->second; - } + /* + cert base + */ + X509_ptr cert(X509_new()); + X509_set_version(cert.get(), 2); - EVP_PKEY* pkey = _sessionPkey; - if (!pkey) return nullptr; + ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), static_cast(std::hash{}(host) & 0x7FFFFFFF)); - X509* cert = X509_new(); - X509_set_version(cert, 2); - ASN1_INTEGER_set(X509_get_serialNumber(cert), static_cast(std::hash{}(host) & 0x7FFFFFFF)); - X509_gmtime_adj(X509_get_notBefore(cert), 0); - X509_gmtime_adj(X509_get_notAfter(cert), 31536000L); + X509_gmtime_adj(X509_get_notBefore(cert.get()), 0); + X509_gmtime_adj(X509_get_notAfter(cert.get()), 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); - 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); + /* + SAN + */ + X509V3_CTX v3ctx; + X509V3_set_ctx(&v3ctx, _caCert, cert.get(), nullptr, nullptr, 0); + 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); - 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()); + /* + sign & ctx load + */ + if (X509_sign(cert.get(), _caPkey, EVP_sha256()) <= 0) return nullptr; SSL_CTX* ctx = SSL_CTX_new(TLS_server_method()); + if (!ctx) return nullptr; - SSL_CTX_set_alpn_select_cb( - 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]; - if (len == 8 && memcmp(&in[i + 1], "http/1.1", 8) == 0) - { - *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); + if (SSL_CTX_use_certificate(ctx, cert.get()) <= 0 || SSL_CTX_use_PrivateKey(ctx, _sessionPkey) <= 0) + { + SSL_CTX_free(ctx); + return nullptr; + } _hostContexts[host] = ctx; return ctx; diff --git a/src/unlocker/ssl.h b/src/unlocker/ssl.h index ccfcf02..d12fe69 100644 --- a/src/unlocker/ssl.h +++ b/src/unlocker/ssl.h @@ -20,12 +20,14 @@ class CertManager CertManager(); ~CertManager(); - bool Init(); - SSL_CTX* CreateHostContext(const std::string& host); + bool init(); + SSL_CTX* createHostContext(const std::string& host); private: - bool GenerateCA(); - bool LoadCA(); + bool generateCA(); + bool loadCA(); + + void installCert(X509* cert); EVP_PKEY* _caPkey = nullptr; X509* _caCert = nullptr;