mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 20:25:38 +00:00
225 lines
7.7 KiB
JavaScript
225 lines
7.7 KiB
JavaScript
const { execCommand, execCommandSilent, detectPlatform } = require('./platform');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
function createCertsDir(certsDir) {
|
|
if (fs.existsSync(certsDir)) {
|
|
fs.rmSync(certsDir, { recursive: true, force: true });
|
|
}
|
|
fs.mkdirSync(certsDir, { recursive: true });
|
|
}
|
|
|
|
function generateCertificates(certsDir) {
|
|
execCommand('openssl version');
|
|
|
|
// Generate CA private key
|
|
execCommand('openssl genrsa -out ca-key.pem 4096', certsDir);
|
|
|
|
// Create CA configuration file with proper CA extensions and subject (LibreSSL/OpenSSL compatible)
|
|
const caConfigContent = `[req]
|
|
distinguished_name = req_distinguished_name
|
|
x509_extensions = v3_ca
|
|
prompt = no
|
|
|
|
[req_distinguished_name]
|
|
C = US
|
|
ST = Dev
|
|
L = Local
|
|
O = Local Dev CA
|
|
CN = Local Dev CA
|
|
|
|
[v3_ca]
|
|
basicConstraints = critical, CA:TRUE
|
|
keyUsage = critical, keyCertSign, cRLSign
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always,issuer:always`;
|
|
|
|
fs.writeFileSync(path.join(certsDir, 'ca.conf'), caConfigContent);
|
|
|
|
// Generate CA certificate with proper CA extensions using config file (no -subj needed)
|
|
execCommand('openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 3650 -config ca.conf', certsDir);
|
|
|
|
// Generate server private key and CSR
|
|
execCommand('openssl genrsa -out localhost-key.pem 4096', certsDir);
|
|
|
|
// Create server CSR configuration file
|
|
const serverCsrConfigContent = `[req]
|
|
distinguished_name = req_distinguished_name
|
|
prompt = no
|
|
|
|
[req_distinguished_name]
|
|
C = US
|
|
ST = Dev
|
|
L = Local
|
|
O = Local Dev
|
|
CN = localhost`;
|
|
|
|
fs.writeFileSync(path.join(certsDir, 'localhost-csr.conf'), serverCsrConfigContent);
|
|
execCommand('openssl req -new -key localhost-key.pem -out localhost.csr -config localhost-csr.conf', certsDir);
|
|
|
|
// Create server certificate configuration file (LibreSSL/OpenSSL compatible)
|
|
const serverConfigContent = `[req]
|
|
distinguished_name = req_distinguished_name
|
|
req_extensions = v3_req
|
|
prompt = no
|
|
|
|
[req_distinguished_name]
|
|
C = Country Name
|
|
ST = State or Province Name
|
|
L = Locality Name
|
|
O = Organization Name
|
|
CN = Common Name
|
|
|
|
[v3_req]
|
|
keyUsage = critical, keyEncipherment, dataEncipherment, digitalSignature
|
|
extendedKeyUsage = serverAuth
|
|
subjectAltName = @alt_names
|
|
basicConstraints = critical, CA:FALSE
|
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
|
|
[alt_names]
|
|
DNS.1 = localhost
|
|
DNS.2 = localhost.localdomain
|
|
IP.1 = 127.0.0.1
|
|
IP.2 = ::1
|
|
IP.3 = ::ffff:127.0.0.1`;
|
|
|
|
fs.writeFileSync(path.join(certsDir, 'localhost.conf'), serverConfigContent);
|
|
execCommand('openssl x509 -req -in localhost.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out localhost-cert.pem -days 730 -extensions v3_req -extfile localhost.conf', certsDir);
|
|
|
|
const platform = detectPlatform();
|
|
if (platform === 'windows') {
|
|
execCommand('openssl x509 -in ca-cert.pem -outform DER -out ca-cert.der', certsDir);
|
|
execCommand('openssl pkcs12 -export -out localhost.p12 -inkey localhost-key.pem -in localhost-cert.pem -certfile ca-cert.pem -password pass:', certsDir);
|
|
execCommand('openssl x509 -in localhost-cert.pem -outform DER -out localhost-cert.der', certsDir);
|
|
}
|
|
|
|
if (platform !== 'windows') {
|
|
execCommand('chmod 600 ca-key.pem localhost-key.pem', certsDir);
|
|
execCommand('chmod 644 ca-cert.pem localhost-cert.pem', certsDir);
|
|
}
|
|
|
|
['localhost.csr', 'localhost.conf', 'localhost-csr.conf', 'ca.conf', 'ca-cert.srl'].forEach((file) => {
|
|
const filePath = path.join(certsDir, file);
|
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
});
|
|
|
|
// Validate certificate chain
|
|
validateCertificateChain(certsDir);
|
|
}
|
|
|
|
function validateCertificateChain(certsDir) {
|
|
try {
|
|
// Verify CA certificate is valid and has proper CA extensions
|
|
const caVerifyOutput = execCommandSilent('openssl x509 -in ca-cert.pem -text -noout', certsDir).toString();
|
|
|
|
if (!caVerifyOutput.includes('CA:TRUE')) {
|
|
throw new Error('CA certificate missing basicConstraints=CA:TRUE');
|
|
}
|
|
|
|
if (!caVerifyOutput.includes('Certificate Sign')) {
|
|
throw new Error('CA certificate missing keyCertSign in keyUsage');
|
|
}
|
|
|
|
// Verify server certificate is valid and signed by CA
|
|
const serverVerifyOutput = execCommandSilent('openssl x509 -in localhost-cert.pem -text -noout', certsDir).toString();
|
|
|
|
if (!serverVerifyOutput.includes('CA:FALSE')) {
|
|
throw new Error('Server certificate should have basicConstraints=CA:FALSE');
|
|
}
|
|
|
|
if (!serverVerifyOutput.includes('TLS Web Server Authentication')) {
|
|
throw new Error('Server certificate missing serverAuth in extendedKeyUsage');
|
|
}
|
|
|
|
// Verify certificate chain
|
|
execCommandSilent('openssl verify -CAfile ca-cert.pem localhost-cert.pem', certsDir);
|
|
|
|
console.log('✅ Certificate chain validation passed');
|
|
} catch (error) {
|
|
console.error('❌ Certificate validation failed:', error.message);
|
|
throw new Error(`Certificate validation failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function addCAToTruststore(certsDir) {
|
|
const platform = detectPlatform();
|
|
|
|
switch (platform) {
|
|
case 'macos': {
|
|
const macCertPath = path.join(certsDir, 'ca-cert.pem');
|
|
execCommand(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${macCertPath}"`);
|
|
break;
|
|
}
|
|
|
|
case 'linux': {
|
|
const linuxCertPath = path.join(certsDir, 'ca-cert.pem');
|
|
execCommand(`sudo cp "${linuxCertPath}" /usr/local/share/ca-certificates/bruno-ca.crt`);
|
|
execCommand('sudo update-ca-certificates');
|
|
break;
|
|
}
|
|
|
|
case 'windows': {
|
|
const winCertPath = path.join(certsDir, 'ca-cert.der');
|
|
|
|
// Escape backslashes for PowerShell
|
|
const psPath = winCertPath.replace(/\\/g, '\\\\');
|
|
|
|
// PowerShell .NET method (works reliably in CI)
|
|
const psCommand = [
|
|
`$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('${psPath}');`,
|
|
`$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine');`,
|
|
`$store.Open('ReadWrite');`,
|
|
`$store.Add($cert);`,
|
|
`$store.Close();`,
|
|
// Verify cert was added by checking if it exists in LocalMachine\Root
|
|
`$verifyStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine');`,
|
|
`$verifyStore.Open('ReadOnly');`,
|
|
`$found = $verifyStore.Certificates | Where-Object { $_.Thumbprint -eq $cert.Thumbprint };`,
|
|
`$verifyStore.Close();`,
|
|
`if (-not $found) { throw 'Certificate was not added to LocalMachine\Root' };`
|
|
].join(' ');
|
|
|
|
execCommand(`powershell -Command "${psCommand}"`);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
}
|
|
}
|
|
|
|
function verifyCertificates(certsDir) {
|
|
const platform = detectPlatform();
|
|
// Core PEM files required for all platforms
|
|
const requiredFiles = ['ca-cert.pem', 'ca-key.pem', 'localhost-cert.pem', 'localhost-key.pem'];
|
|
|
|
// Verify required PEM files exist
|
|
for (const file of requiredFiles) {
|
|
const filePath = path.join(certsDir, file);
|
|
if (!fs.existsSync(filePath)) {
|
|
throw new Error(`missing certificate file: ${file}`);
|
|
}
|
|
}
|
|
|
|
// Check Windows-specific files but don't require them (they're optional fallbacks)
|
|
if (platform === 'windows') {
|
|
const windowsFiles = ['ca-cert.der', 'localhost.p12', 'localhost-cert.der'];
|
|
for (const file of windowsFiles) {
|
|
const filePath = path.join(certsDir, file);
|
|
if (fs.existsSync(filePath)) {
|
|
console.log(`✅ Windows certificate file available: ${file}`);
|
|
} else {
|
|
console.log(`⚠️ Windows certificate file missing (but not required): ${file}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
createCertsDir,
|
|
generateCertificates,
|
|
addCAToTruststore,
|
|
verifyCertificates
|
|
};
|