This commit is contained in:
Raven Scott
2025-12-17 20:05:50 -05:00
commit 742e27d3f7
276 changed files with 89838 additions and 0 deletions
+563
View File
@@ -0,0 +1,563 @@
const fs = require("fs");
const path = require("path");
const tls = require("tls");
const net = require("net");
const http = require("http");
const https = require("https");
const url = require("url");
const forge = require("node-forge");
const os = require("os");
const { exec } = require("child_process");
const { logDebug, logError, logWarn, logInfo } = require("../infrastructure/logger");
const state = require("../infrastructure/state");
const certsDir = process.env.CERTS_DIR || "./certs";
const caKeyPath = path.join(certsDir, "ca.key.pem");
const caCertPath = path.join(certsDir, "ca.cert.pem");
const caFetchPromises = new Map(); // domain => Promise for CA chain fetch
// Create certs directory if not exists
if (!fs.existsSync(certsDir)) {
fs.mkdirSync(certsDir);
}
// Generate or validate Root CA
let regenerated = false;
if (fs.existsSync(caKeyPath) && fs.existsSync(caCertPath)) {
const caCertPem = fs.readFileSync(caCertPath, "utf8");
const cert = forge.pki.certificateFromPem(caCertPem);
if (cert.validity.notAfter > new Date()) {
logDebug("CA", "Existing Root CA is valid.");
} else {
logWarn("CA", "Existing Root CA expired, regenerating.");
regenerated = true;
}
} else {
regenerated = true;
}
if (regenerated) {
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(
cert.validity.notBefore.getFullYear() + 10
);
const attrs = [
{ name: "commonName", value: "P2NS CA" },
{ name: "countryName", value: "US" },
{ shortName: "ST", value: "CA" },
{ name: "localityName", value: "San Francisco" },
{ name: "organizationName", value: "P2NS" },
{ shortName: "OU", value: "P2NS Root CA" },
];
cert.setSubject(attrs);
cert.setIssuer(attrs);
const skid = forge.pki.getPublicKeyFingerprint(cert.publicKey, {
md: forge.md.sha1.create(),
});
cert.setExtensions([
{ name: "basicConstraints", cA: true, pathLenConstraint: 0 },
{ name: "keyUsage", keyCertSign: true, cRLSign: true },
{ name: "subjectKeyIdentifier" },
{ name: "authorityKeyIdentifier", keyIdentifier: skid.getBytes() },
]);
cert.sign(keys.privateKey, forge.md.sha256.create(), {
padding: forge.pki.rsa.PKCS1_v1_5,
});
fs.writeFileSync(caKeyPath, forge.pki.privateKeyToPem(keys.privateKey));
fs.writeFileSync(caCertPath, forge.pki.certificateToPem(cert));
logInfo("CA", "Root CA generated.");
}
// Install Root CA
function installRootCA() {
const platform = os.platform();
const caPath = path.resolve(caCertPath);
const commonName = "P2NS CA";
if (platform === "darwin") {
exec(
`security find-certificate -c "${commonName}" -a /Library/Keychains/System.keychain`,
(err, stdout) => {
const installed = !err && stdout.trim() !== "";
if (!regenerated && installed) {
logInfo("CA", "Root CA already installed on macOS.");
return;
}
if (installed) {
exec(
`security delete-certificate -c "${commonName}" /Library/Keychains/System.keychain`,
(err, stdout, stderr) => {
if (err) {
logError("CA", `Error deleting old CA on macOS: ${stderr}`);
} else {
logDebug("CA", "Removed old CA on macOS.");
}
addCA();
}
);
} else {
addCA();
}
function addCA() {
const command = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${caPath}"`;
exec(command, (err, stdout, stderr) => {
if (err) {
logError("CA", `Error installing root CA on macOS: ${stderr}`);
} else {
logInfo("CA", "Root CA installed successfully on macOS.");
}
});
}
}
);
} else if (platform === "linux") {
const caTargetPath = "/usr/local/share/ca-certificates/p2ns-ca.crt";
const installed = fs.existsSync(caTargetPath);
if (!regenerated && installed) {
logInfo("CA", "Root CA already installed on Linux.");
return;
}
if (installed) {
fs.unlinkSync(caTargetPath);
logDebug("CA", "Removed old CA file on Linux.");
}
const copyCmd = `cp "${caPath}" ${caTargetPath}`;
exec(copyCmd, (err, stdout, stderr) => {
if (err) {
logError("CA", `Error copying CA on Linux: ${stderr}`);
return;
}
exec("update-ca-certificates", (err, stdout, stderr) => {
if (err) {
logError("CA", `Error updating CA on Linux: ${stderr}`);
} else {
logInfo("CA", "Root CA installed on Linux.");
}
});
});
} else if (platform === "win32") {
exec(`certutil -store ROOT | findstr "${commonName}"`, (err, stdout) => {
const installed = !err && stdout.trim() !== "";
if (!regenerated && installed) {
logInfo("CA", "Root CA already installed on Windows.");
return;
}
if (installed) {
exec(
`certutil -delstore ROOT "${commonName}"`,
(err, stdout, stderr) => {
if (err) {
logError("CA", `Error deleting old CA on Windows: ${stderr}`);
} else {
logDebug("CA", "Removed old CA on Windows.");
}
addCA();
}
);
} else {
addCA();
}
function addCA() {
const command = `certutil -addstore -f "ROOT" "${caPath}"`;
exec(command, (err, stdout, stderr) => {
if (err) {
logError("CA", `Error installing root CA on Windows: ${stderr}`);
} else {
logInfo("CA", "Root CA installed successfully on Windows.");
}
});
}
});
} else {
logWarn("CA", "Unsupported platform for auto-install CA");
}
}
// Function to regenerate Root CA
function regenerateRootCA() {
if (fs.existsSync(caKeyPath)) fs.unlinkSync(caKeyPath);
if (fs.existsSync(caCertPath)) fs.unlinkSync(caCertPath);
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = "01";
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(
cert.validity.notBefore.getFullYear() + 10
);
const attrs = [
{ name: "commonName", value: "P2NS CA" },
{ name: "countryName", value: "US" },
{ shortName: "ST", value: "CA" },
{ name: "localityName", value: "San Francisco" },
{ name: "organizationName", value: "P2NS" },
{ shortName: "OU", value: "P2NS Root CA" },
];
cert.setSubject(attrs);
cert.setIssuer(attrs);
const skid = forge.pki.getPublicKeyFingerprint(cert.publicKey, {
md: forge.md.sha1.create(),
});
cert.setExtensions([
{ name: "basicConstraints", cA: true, pathLenConstraint: 0 },
{ name: "keyUsage", keyCertSign: true, cRLSign: true },
{ name: "subjectKeyIdentifier" },
{ name: "authorityKeyIdentifier", keyIdentifier: skid.getBytes() },
]);
cert.sign(keys.privateKey, forge.md.sha256.create(), {
padding: forge.pki.rsa.PKCS1_v1_5,
});
fs.writeFileSync(caKeyPath, forge.pki.privateKeyToPem(keys.privateKey));
fs.writeFileSync(caCertPath, forge.pki.certificateToPem(cert));
logInfo("CA", "Root CA regenerated.");
}
// Certificate revocation list (in-memory, could be persisted)
const certificateRevocationList = new Set();
// Certificate expiration monitoring
const certificateExpirationCheckInterval = 24 * 60 * 60 * 1000; // 24 hours
let expirationCheckInterval = null;
// Start certificate expiration monitoring
function startCertificateExpirationMonitoring() {
if (expirationCheckInterval) {
clearInterval(expirationCheckInterval);
}
// Check immediately on start
checkCertificateExpirations();
// Then check periodically
expirationCheckInterval = setInterval(() => {
checkCertificateExpirations();
}, certificateExpirationCheckInterval);
logInfo("CA", "Certificate expiration monitoring started");
}
// Check all certificates for expiration
function checkCertificateExpirations() {
try {
if (!fs.existsSync(certsDir)) {
return;
}
const domains = fs.readdirSync(certsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
const renewalThreshold = 30 * 24 * 60 * 60 * 1000; // 30 days before expiration
const now = Date.now();
for (const domain of domains) {
const certPath = path.join(certsDir, domain, "cert.pem");
if (!fs.existsSync(certPath)) continue;
try {
const certPem = fs.readFileSync(certPath, "utf8");
// Extract the first certificate from the chain
const certMatch = certPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/);
if (!certMatch) continue;
const cert = forge.pki.certificateFromPem(certMatch[0]);
const expirationDate = cert.validity.notAfter;
const timeUntilExpiration = expirationDate.getTime() - now;
if (timeUntilExpiration < 0) {
logWarn("CA", `Certificate for ${domain} has expired. Auto-renewing...`);
renewDomainCertificate(domain);
} else if (timeUntilExpiration < renewalThreshold) {
logInfo("CA", `Certificate for ${domain} expires in ${Math.floor(timeUntilExpiration / (24 * 60 * 60 * 1000))} days. Auto-renewing...`);
renewDomainCertificate(domain);
}
} catch (err) {
logError("CA", `Error checking certificate expiration for ${domain}: ${err.message}`);
}
}
} catch (err) {
logError("CA", `Error in certificate expiration check: ${err.message}`);
}
}
// Renew a domain certificate
function renewDomainCertificate(domain) {
try {
const domainDir = path.join(certsDir, domain);
const keyPath = path.join(domainDir, "key.pem");
const certPath = path.join(domainDir, "cert.pem");
// Remove old certificate
if (fs.existsSync(keyPath)) fs.unlinkSync(keyPath);
if (fs.existsSync(certPath)) fs.unlinkSync(certPath);
// Get IP from state if available
const ip = state.domainToIPMap?.get(domain);
const altNames = [{ type: 2, value: domain }];
// Regenerate certificate
getOrCreateDomainCert(domain, ip, altNames);
logInfo("CA", `Certificate for ${domain} renewed successfully`);
} catch (err) {
logError("CA", `Error renewing certificate for ${domain}: ${err.message}`);
}
}
// Check if certificate is revoked
function isCertificateRevoked(domain) {
return certificateRevocationList.has(domain);
}
// Revoke a certificate
function revokeCertificate(domain) {
certificateRevocationList.add(domain);
logInfo("CA", `Certificate for ${domain} has been revoked`);
// Optionally persist CRL to disk
const crlPath = path.join(certsDir, "crl.json");
try {
const crl = Array.from(certificateRevocationList);
fs.writeFileSync(crlPath, JSON.stringify(crl, null, 2));
} catch (err) {
logError("CA", `Error saving CRL: ${err.message}`);
}
}
// Load CRL from disk
function loadCRL() {
const crlPath = path.join(certsDir, "crl.json");
if (fs.existsSync(crlPath)) {
try {
const crl = JSON.parse(fs.readFileSync(crlPath, "utf8"));
crl.forEach(domain => certificateRevocationList.add(domain));
logInfo("CA", `Loaded ${crl.length} revoked certificates from CRL`);
} catch (err) {
logError("CA", `Error loading CRL: ${err.message}`);
}
}
}
// Function to get or create domain certificate
function getOrCreateDomainCert(
domain,
ipInput,
altNames = [{ type: 2, value: domain }]
) {
const domainDir = path.join(certsDir, domain);
if (!fs.existsSync(domainDir)) fs.mkdirSync(domainDir, { recursive: true });
const keyPath = path.join(domainDir, "key.pem");
const certPath = path.join(domainDir, "cert.pem");
// Check if certificate is revoked
if (isCertificateRevoked(domain)) {
logWarn("CA", `Certificate for ${domain} is revoked, regenerating...`);
if (fs.existsSync(keyPath)) fs.unlinkSync(keyPath);
if (fs.existsSync(certPath)) fs.unlinkSync(certPath);
}
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
// Check expiration
try {
const certPem = fs.readFileSync(certPath, "utf8");
const certMatch = certPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/);
if (certMatch) {
const cert = forge.pki.certificateFromPem(certMatch[0]);
if (cert.validity.notAfter > new Date()) {
return {
key: fs.readFileSync(keyPath, "utf8"),
cert: fs.readFileSync(certPath, "utf8"),
};
} else {
logWarn("CA", `Certificate for ${domain} has expired, regenerating...`);
if (fs.existsSync(keyPath)) fs.unlinkSync(keyPath);
if (fs.existsSync(certPath)) fs.unlinkSync(certPath);
}
}
} catch (err) {
logError("CA", `Error checking certificate expiration for ${domain}: ${err.message}`);
}
}
const caKeyPem = fs.readFileSync(caKeyPath, "utf8");
const caCertPem = fs.readFileSync(caCertPath, "utf8");
const caKey = forge.pki.privateKeyFromPem(caKeyPem);
const caCert = forge.pki.certificateFromPem(caCertPem);
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = "" + Date.now();
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [{ name: "commonName", value: domain }];
cert.setSubject(attrs);
cert.setIssuer(caCert.subject.attributes);
const akid = forge.pki.getPublicKeyFingerprint(caCert.publicKey, {
md: forge.md.sha1.create(),
});
const skid = forge.pki.getPublicKeyFingerprint(cert.publicKey, {
md: forge.md.sha1.create(),
});
// Only include domain in SAN, not IPs (as requested)
const finalAltNames = [...altNames];
// Removed IP addition to SAN - only domain certificate is provided
cert.setExtensions([
{ name: "basicConstraints", cA: false },
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true },
{ name: "extKeyUsage", serverAuth: true },
{ name: "subjectAltName", altNames: finalAltNames },
{ name: "authorityKeyIdentifier", keyIdentifier: akid.getBytes() },
{ name: "subjectKeyIdentifier" },
]);
try {
cert.sign(caKey, forge.md.sha256.create(), {
padding: forge.pki.rsa.PKCS1_v1_5,
});
} catch (err) {
logError("CERT", `Error signing certificate for ${domain}: ${err.message}`);
throw err;
}
const pemKey = forge.pki.privateKeyToPem(keys.privateKey);
const pemCert = forge.pki.certificateToPem(cert);
const pemChain = pemCert + caCertPem;
fs.writeFileSync(keyPath, pemKey);
fs.writeFileSync(certPath, pemChain);
return { key: pemKey, cert: pemChain };
}
// Async function to fetch missing intermediate certificates using AIA
async function getCaChain(domain, ip) {
if (state.caForDomain.has(domain)) {
return state.caForDomain.get(domain);
}
if (caFetchPromises.has(domain)) {
return await caFetchPromises.get(domain);
}
const fetchPromise = (async () => {
let socket;
try {
socket = await new Promise((resolve, reject) => {
const s = tls.connect(
{
host: ip,
port: 443,
servername: domain,
rejectUnauthorized: false,
},
() => resolve(s)
);
s.on("error", reject);
});
let chain = [];
let cert = socket.getPeerCertificate(true);
let seenFingerprints = new Set();
while (cert) {
if (seenFingerprints.has(cert.fingerprint)) break;
seenFingerprints.add(cert.fingerprint);
chain.push(cert);
cert = cert.issuerCertificate;
}
socket.end();
let fullChain = [...chain];
let current = chain[chain.length - 1];
seenFingerprints = new Set(chain.map((c) => c.fingerprint));
while (!selfSigned(current)) {
let pem = forge.pki.certificateToPem(
forge.pki.certificateFromAsn1(
forge.asn1.fromDer(current.raw.toString("binary"))
)
);
let fCert = forge.pki.certificateFromPem(pem);
let aia = fCert.getExtension({ name: "authorityInfoAccess" });
if (!aia || !aia.altNames) {
logWarn(
"CERT",
`No AIA extension for ${domain}, falling back to no CA chain`
);
return null;
}
let caIssuers = aia.altNames.find((alt) => alt.type === 6);
if (!caIssuers) {
logWarn(
"CERT",
`No caIssuers in AIA for ${domain}, falling back to no CA chain`
);
return null;
}
let uri = caIssuers.value;
let buf = await fetchUri(uri);
let asn1 = forge.asn1.fromDer(buf.toString("binary"));
let issuerCert = forge.pki.certificateFromAsn1(asn1);
let issuerPem = forge.pki.certificateToPem(issuerCert);
let issuerObj = {
raw: buf,
fingerprint: issuerCert.generateFingerprint().toLowerCase(),
issuerCertificate: null,
};
fullChain.push(issuerObj);
current = fullChain[fullChain.length - 1];
if (seenFingerprints.has(current.fingerprint)) break;
seenFingerprints.add(current.fingerprint);
}
let caPems = fullChain
.slice(1)
.map((c) =>
forge.pki.certificateToPem(
forge.pki.certificateFromAsn1(
forge.asn1.fromDer(c.raw.toString("binary"))
)
)
);
return caPems;
} catch (err) {
logError(
"CERT",
`Failed to fetch CA chain for ${domain}: ${err.message}`
);
if (socket) socket.end();
return null;
}
})();
caFetchPromises.set(domain, fetchPromise);
const result = await fetchPromise;
caFetchPromises.delete(domain);
state.caForDomain.set(domain, result);
return result;
}
function selfSigned(cert) {
let pem = forge.pki.certificateToPem(
forge.pki.certificateFromAsn1(
forge.asn1.fromDer(cert.raw.toString("binary"))
)
);
let fCert = forge.pki.certificateFromPem(pem);
return fCert.isIssuer(fCert);
}
async function fetchUri(uri) {
const parsed = url.parse(uri);
const protocol = parsed.protocol === "http:" ? http : https;
return new Promise((resolve, reject) => {
const req = protocol.get(uri, (res) => {
if (res.statusCode !== 200) {
reject(new Error(`Bad status code: ${res.statusCode}`));
return;
}
const data = [];
res.on("data", (chunk) => data.push(chunk));
res.on("end", () => resolve(Buffer.concat(data)));
});
req.on("error", reject);
});
}
// Load CRL on module initialization
loadCRL();
// Start expiration monitoring
startCertificateExpirationMonitoring();
module.exports = {
installRootCA,
regenerateRootCA,
getOrCreateDomainCert,
getCaChain,
revokeCertificate,
isCertificateRevoked,
renewDomainCertificate,
checkCertificateExpirations,
};