Skip to main content
Glama

Web Proxy MCP Server

by mako10k
certificate-manager.js13.9 kB
/** * Certificate Authority Manager * Manages SSL certificates for SSL Bumping functionality */ import fs from 'fs/promises'; import path from 'path'; import { execSync, spawn } from 'child_process'; import { promisify } from 'util'; import os from 'os'; export class CertificateManager { constructor(options = {}) { this.homeDir = os.homedir(); this.caBaseDir = path.join(this.homeDir, '.ca'); this.defaultCaName = options.defaultCaName || 'default'; this.currentCaPath = null; this.certificates = new Map(); // Cache for generated certificates // Certificate settings this.caConfig = { country: options.country || 'JP', state: options.state || 'Tokyo', city: options.city || 'Tokyo', organization: options.organization || 'Web Proxy MCP Server', organizationalUnit: options.organizationalUnit || 'Development Team', emailAddress: options.emailAddress || 'admin@proxy-mcp.local' }; } /** * Initialize certificate authority * @param {string} caName - CA name (default: 'default') * @param {boolean} createNew - Force create new CA even if exists * @returns {Object} CA initialization result */ async initializeCA(caName = this.defaultCaName, createNew = false) { const caPath = path.join(this.caBaseDir, caName); try { // Check if CA already exists const caExists = await this._checkCAExists(caPath); if (caExists && !createNew) { console.log(`📋 Using existing CA: ${caName}`); this.currentCaPath = caPath; return { status: 'existing', caName, caPath, certPath: path.join(caPath, 'ca.crt'), keyPath: path.join(caPath, 'ca.key'), message: `Using existing certificate authority: ${caName}` }; } // Create new CA console.log(`🔧 Creating new certificate authority: ${caName}`); await this._createNewCA(caPath, caName); this.currentCaPath = caPath; return { status: 'created', caName, caPath, certPath: path.join(caPath, 'ca.crt'), keyPath: path.join(caPath, 'ca.key'), message: `Created new certificate authority: ${caName}`, installInstructions: this._getInstallInstructions(path.join(caPath, 'ca.crt')) }; } catch (error) { throw new Error(`Failed to initialize CA: ${error.message}`); } } /** * Generate server certificate for domain * @param {string} domain - Domain name * @param {Array} altNames - Alternative names (SANs) * @returns {Object} Certificate paths */ async generateServerCertificate(domain, altNames = []) { if (!this.currentCaPath) { throw new Error('CA not initialized. Call initializeCA() first.'); } const certKey = domain.toLowerCase(); // Check cache first if (this.certificates.has(certKey)) { return this.certificates.get(certKey); } try { const certDir = path.join(this.currentCaPath, 'certs', domain); await fs.mkdir(certDir, { recursive: true }); const certPath = path.join(certDir, 'server.crt'); const keyPath = path.join(certDir, 'server.key'); const csrPath = path.join(certDir, 'server.csr'); // Generate private key await this._execOpenSSL([ 'genrsa', '-out', keyPath, '2048' ]); // Create certificate signing request const subject = `/C=${this.caConfig.country}/ST=${this.caConfig.state}/L=${this.caConfig.city}/O=${this.caConfig.organization}/OU=${this.caConfig.organizationalUnit}/CN=${domain}/emailAddress=${this.caConfig.emailAddress}`; await this._execOpenSSL([ 'req', '-new', '-key', keyPath, '-out', csrPath, '-subj', subject ]); // Create extensions file for SAN const extPath = path.join(certDir, 'server.ext'); const allNames = [domain, ...altNames].filter((name, index, self) => self.indexOf(name) === index); const sanList = allNames.map((name, index) => `DNS.${index + 1}:${name}`).join(','); const extContent = `authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] ${allNames.map((name, index) => `DNS.${index + 1} = ${name}`).join('\n')}`; await fs.writeFile(extPath, extContent); // Sign the certificate const caCertPath = path.join(this.currentCaPath, 'ca.crt'); const caKeyPath = path.join(this.currentCaPath, 'ca.key'); await this._execOpenSSL([ 'x509', '-req', '-in', csrPath, '-CA', caCertPath, '-CAkey', caKeyPath, '-CAcreateserial', '-out', certPath, '-days', '365', '-sha256', '-extensions', 'v3_req', '-extfile', extPath ]); const result = { domain, certPath, keyPath, altNames: allNames, createdAt: new Date().toISOString() }; // Cache the result this.certificates.set(certKey, result); console.log(`📜 Generated certificate for: ${domain}`); return result; } catch (error) { throw new Error(`Failed to generate certificate for ${domain}: ${error.message}`); } } /** * List available certificate authorities * @returns {Array} List of available CAs */ async listCertificateAuthorities() { try { const caList = []; try { const entries = await fs.readdir(this.caBaseDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const caPath = path.join(this.caBaseDir, entry.name); const caExists = await this._checkCAExists(caPath); if (caExists) { const certPath = path.join(caPath, 'ca.crt'); const info = await this._getCertificateInfo(certPath); caList.push({ name: entry.name, path: caPath, certPath, info, isCurrent: caPath === this.currentCaPath }); } } } } catch (error) { // CA directory might not exist yet console.log('No certificate authorities found'); } return caList; } catch (error) { throw new Error(`Failed to list CAs: ${error.message}`); } } /** * Get certificate info * @param {string} certPath - Certificate file path * @returns {Object} Certificate information */ async getCertificateInfo(certPath) { return await this._getCertificateInfo(certPath); } /** * Get current CA status * @returns {Object} Current CA status */ getCurrentCAStatus() { if (!this.currentCaPath) { return { initialized: false, message: 'No certificate authority initialized' }; } return { initialized: true, caPath: this.currentCaPath, caName: path.basename(this.currentCaPath), certPath: path.join(this.currentCaPath, 'ca.crt'), keyPath: path.join(this.currentCaPath, 'ca.key'), generatedCerts: this.certificates.size }; } /** * Get installation instructions for CA certificate * @returns {Object} Installation instructions */ getInstallationInstructions() { if (!this.currentCaPath) { throw new Error('No CA initialized'); } const certPath = path.join(this.currentCaPath, 'ca.crt'); return this._getInstallInstructions(certPath); } /** * Clear certificate cache */ clearCertificateCache() { this.certificates.clear(); console.log('🗑️ Certificate cache cleared'); } /** * Check if CA exists * @private */ async _checkCAExists(caPath) { try { const certPath = path.join(caPath, 'ca.crt'); const keyPath = path.join(caPath, 'ca.key'); await fs.access(certPath); await fs.access(keyPath); return true; } catch (error) { return false; } } /** * Create new certificate authority * @private */ async _createNewCA(caPath, caName) { // Create CA directory structure await fs.mkdir(caPath, { recursive: true }); await fs.mkdir(path.join(caPath, 'certs'), { recursive: true }); const certPath = path.join(caPath, 'ca.crt'); const keyPath = path.join(caPath, 'ca.key'); // Generate CA private key await this._execOpenSSL([ 'genrsa', '-out', keyPath, '4096' ]); // Generate CA certificate const subject = `/C=${this.caConfig.country}/ST=${this.caConfig.state}/L=${this.caConfig.city}/O=${this.caConfig.organization}/OU=${this.caConfig.organizationalUnit}/CN=${caName} Root CA/emailAddress=${this.caConfig.emailAddress}`; await this._execOpenSSL([ 'req', '-new', '-x509', '-key', keyPath, '-sha256', '-days', '3650', '-out', certPath, '-subj', subject ]); // Create serial file await fs.writeFile(path.join(caPath, 'ca.srl'), '1000'); console.log(`✅ Created certificate authority: ${caName}`); } /** * Execute OpenSSL command * @private */ async _execOpenSSL(args) { return new Promise((resolve, reject) => { const process = spawn('openssl', args, { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { resolve(stdout); } else { reject(new Error(`OpenSSL failed: ${stderr || stdout}`)); } }); process.on('error', (error) => { reject(new Error(`Failed to execute OpenSSL: ${error.message}`)); }); }); } /** * Get certificate information * @private */ async _getCertificateInfo(certPath) { try { const output = await this._execOpenSSL([ 'x509', '-in', certPath, '-text', '-noout' ]); // Parse certificate info (simplified) const lines = output.split('\n'); const info = { subject: this._extractField(lines, 'Subject:'), issuer: this._extractField(lines, 'Issuer:'), validFrom: this._extractField(lines, 'Not Before:'), validTo: this._extractField(lines, 'Not After:'), serialNumber: this._extractField(lines, 'Serial Number:') }; return info; } catch (error) { return { error: error.message }; } } /** * Extract field from certificate text * @private */ _extractField(lines, fieldName) { const line = lines.find(line => line.trim().startsWith(fieldName)); return line ? line.split(fieldName)[1].trim() : 'Unknown'; } /** * Get installation instructions * @private */ _getInstallInstructions(certPath) { const platform = os.platform(); const instructions = { certPath, platform, instructions: {} }; if (platform === 'linux') { instructions.instructions = { system: { title: 'System-wide installation (Ubuntu/Debian)', commands: [ `sudo cp "${certPath}" /usr/local/share/ca-certificates/web-proxy-mcp.crt`, 'sudo update-ca-certificates' ], description: 'Installs the certificate system-wide for all applications' }, chrome: { title: 'Google Chrome', steps: [ '1. Open Chrome Settings', '2. Go to Privacy and Security > Security', '3. Click "Manage certificates"', '4. Go to "Authorities" tab', '5. Click "Import"', `6. Select file: ${certPath}`, '7. Check "Trust this certificate for identifying websites"' ] }, firefox: { title: 'Mozilla Firefox', steps: [ '1. Open Firefox Preferences', '2. Go to Privacy & Security', '3. Scroll to "Certificates" section', '4. Click "View Certificates"', '5. Go to "Authorities" tab', '6. Click "Import"', `7. Select file: ${certPath}`, '8. Check "Trust this CA to identify websites"' ] } }; } else if (platform === 'darwin') { instructions.instructions = { keychain: { title: 'macOS Keychain', commands: [ `security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain "${certPath}"` ], description: 'Adds certificate to user keychain as trusted root' }, system: { title: 'System Keychain (requires admin)', commands: [ `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"` ], description: 'Installs certificate system-wide' } }; } else if (platform === 'win32') { instructions.instructions = { certmgr: { title: 'Windows Certificate Manager', steps: [ '1. Run "certmgr.msc" as administrator', '2. Navigate to "Trusted Root Certification Authorities" > "Certificates"', '3. Right-click and select "All Tasks" > "Import"', `4. Select file: ${certPath}`, '5. Place in "Trusted Root Certification Authorities" store' ] }, powershell: { title: 'PowerShell (as administrator)', commands: [ `Import-Certificate -FilePath "${certPath}" -CertStoreLocation Cert:\\LocalMachine\\Root` ] } }; } return instructions; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mako10k/mcp-web-proxy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server