UNPKG

@axlotl-lab/navigrator

Version:

A powerful local domain manager for development environments. Navigrator helps you manage local domains and SSL certificates with a simple web interface.

317 lines (313 loc) 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CertificateManager = void 0; const child_process_1 = require("child_process"); const fs = __importStar(require("fs/promises")); const os = __importStar(require("os")); const path = __importStar(require("path")); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class CertificateManager { constructor(certsDir) { this.certsDir = certsDir || path.join(os.homedir(), '.navigrator', 'certs'); this.caDir = path.join(this.certsDir, 'ca'); this.hasOpenSSL = false; // Will be validated in initialize() } /** * Check if OpenSSL is installed on the system */ checkOpenSSLInstalled() { return __awaiter(this, void 0, void 0, function* () { try { yield execAsync('openssl version'); return true; } catch (error) { return false; } }); } /** * Initialize the certificate directory and validate OpenSSL */ initialize() { return __awaiter(this, void 0, void 0, function* () { try { // Verify OpenSSL this.hasOpenSSL = yield this.checkOpenSSLInstalled(); if (!this.hasOpenSSL) { throw new Error('OpenSSL is not installed. Please install OpenSSL to continue.'); } // Create required directories yield fs.mkdir(this.certsDir, { recursive: true }); yield fs.mkdir(this.caDir, { recursive: true }); // Create local CA if it doesn't exist yield this.initLocalCA(); } catch (error) { console.error('Error initializing certificate manager:', error); throw new Error(`Failed to initialize certificate manager: ${error === null || error === void 0 ? void 0 : error.message}`); } }); } /** * Initialize a local CA to sign certificates */ initLocalCA() { return __awaiter(this, void 0, void 0, function* () { const caKeyPath = path.join(this.caDir, 'rootCA.key'); const caCertPath = path.join(this.caDir, 'rootCA.crt'); // Check if CA already exists try { yield fs.access(caKeyPath); yield fs.access(caCertPath); return; // CA already exists } catch (error) { // Doesn't exist, need to create it } // Create private key for the CA yield execAsync(`openssl genrsa -out "${caKeyPath}" 4096`); // Create certificate for the CA (valid for 10 years) yield execAsync(`openssl req -x509 -new -nodes -key "${caKeyPath}" -sha256 -days 3650 ` + `-out "${caCertPath}" -subj "/CN=Navigrator Local CA/O=Axlotl Lab/OU=Development"`); console.log('Local CA created successfully'); }); } /** * Create a certificate for a domain using OpenSSL */ createCertificate(domain) { return __awaiter(this, void 0, void 0, function* () { if (!this.hasOpenSSL) { throw new Error('OpenSSL is not available'); } try { const keyPath = path.join(this.certsDir, `${domain}.key`); const csrPath = path.join(this.certsDir, `${domain}.csr`); const certPath = path.join(this.certsDir, `${domain}.crt`); const configPath = path.join(this.certsDir, `${domain}.cnf`); // Create configuration file for the certificate const configContent = this.generateOpenSSLConfig(domain); yield fs.writeFile(configPath, configContent); // Generate private key for the domain yield execAsync(`openssl genrsa -out "${keyPath}" 2048`); // Create CSR (Certificate Signing Request) yield execAsync(`openssl req -new -key "${keyPath}" -out "${csrPath}" ` + `-config "${configPath}" -subj "/CN=${domain}/O=Axlotl Lab/OU=Development"`); // Sign the certificate with our CA const caKeyPath = path.join(this.caDir, 'rootCA.key'); const caCertPath = path.join(this.caDir, 'rootCA.crt'); yield execAsync(`openssl x509 -req -in "${csrPath}" -CA "${caCertPath}" ` + `-CAkey "${caKeyPath}" -CAcreateserial ` + `-out "${certPath}" -days 365 -sha256 ` + `-extensions v3_req -extfile "${configPath}"`); // Remove temporary files yield fs.unlink(csrPath); yield fs.unlink(configPath); // Verify the created certificate const certInfo = yield this.parseCertificate(domain); if (!certInfo) { throw new Error(`Failed to verify certificate for ${domain} after creation`); } return certInfo; } catch (error) { console.error(`Error creating certificate for ${domain}:`, error); throw new Error(`Failed to create certificate: ${error === null || error === void 0 ? void 0 : error.message}`); } }); } /** * Generate an OpenSSL configuration file for the certificate */ generateOpenSSLConfig(domain) { return `[req] default_bits = 2048 distinguished_name = req_distinguished_name req_extensions = v3_req prompt = no [req_distinguished_name] CN = ${domain} O = Axlotl Lab OU = Development [v3_req] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = ${domain} `; } /** * Delete a certificate for a domain */ deleteCertificate(domain) { return __awaiter(this, void 0, void 0, function* () { try { const certInfo = yield this.verifyCertificate(domain); if (!certInfo || !certInfo.certFilePath || !certInfo.keyFilePath) { return false; } try { yield fs.unlink(certInfo.certFilePath); yield fs.unlink(certInfo.keyFilePath); return true; } catch (error) { console.error(`Error deleting certificate files for ${domain}:`, error); return false; } } catch (error) { console.error(`Error deleting certificate for ${domain}:`, error); return false; } }); } /** * Verify if a valid certificate exists for the domain */ verifyCertificate(domain) { return __awaiter(this, void 0, void 0, function* () { try { const certPath = path.join(this.certsDir, `${domain}.crt`); const keyPath = path.join(this.certsDir, `${domain}.key`); // Check if files exist try { yield fs.access(certPath); yield fs.access(keyPath); } catch (error) { return null; // If certificates don't exist, there is no valid certificate } return yield this.parseCertificate(domain); } catch (error) { console.error(`Error verifying certificate for ${domain}:`, error); return null; } }); } /** * Parse an existing certificate to get its information */ parseCertificate(domain) { return __awaiter(this, void 0, void 0, function* () { try { const certPath = path.join(this.certsDir, `${domain}.crt`); const keyPath = path.join(this.certsDir, `${domain}.key`); // Verify that the certificate exists try { yield fs.access(certPath); yield fs.access(keyPath); } catch (error) { return null; } // Validate certificate dates const { stdout: dates } = yield execAsync(`openssl x509 -in "${certPath}" -noout -dates`); // Parse dates (format: notBefore=May 30 12:00:00 2023 GMT / notAfter=May 30 12:00:00 2024 GMT) const notBeforeMatch = dates.match(/notBefore=(.+)$/m); const notAfterMatch = dates.match(/notAfter=(.+)$/m); if (!notBeforeMatch || !notAfterMatch) { throw new Error('Could not extract dates from certificate'); } const validFrom = new Date(notBeforeMatch[1]); const validTo = new Date(notAfterMatch[1]); // Get issuer name const { stdout: issuerData } = yield execAsync(`openssl x509 -in "${certPath}" -noout -issuer`); // Extract CN from issuer const issuerCNMatch = issuerData.match(/CN\s*=\s*([^,\/]+)/); const issuer = issuerCNMatch ? issuerCNMatch[1].trim() : 'Unknown'; // Verify that the domain is included in the certificate const { stdout: subjectAltNames } = yield execAsync(`openssl x509 -in "${certPath}" -noout -ext subjectAltName`); const domainIncluded = subjectAltNames.includes(`DNS:${domain}`); // Verify certificate validity const now = new Date(); const isValid = now >= validFrom && now <= validTo && domainIncluded; return { domain, validFrom, validTo, issuer, isValid, certFilePath: certPath, keyFilePath: keyPath }; } catch (error) { console.error(`Error parsing certificate for ${domain}:`, error); return null; } }); } /** * List all certificates created by the application */ listCertificates() { return __awaiter(this, void 0, void 0, function* () { try { const files = yield fs.readdir(this.certsDir); const certFiles = files.filter(file => file.endsWith('.crt')); const certificates = []; for (const certFile of certFiles) { const domain = certFile.replace('.crt', ''); const certInfo = yield this.verifyCertificate(domain); if (certInfo) { certificates.push(certInfo); } } return certificates; } catch (error) { console.error('Error listing certificates:', error); return []; } }); } } exports.CertificateManager = CertificateManager;