ca certs fixes and tests (#5429)

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
This commit is contained in:
lohit
2025-09-07 23:06:44 +05:30
committed by GitHub
parent 1bc7a1f655
commit 3c656270b3
57 changed files with 1853 additions and 50 deletions

View File

@@ -0,0 +1,6 @@
{
"version": "1",
"name": "badssl",
"type": "collection",
"ignore": ["node_modules", ".git"]
}

View File

@@ -0,0 +1,5 @@
{
"name": "badssl",
"version": "1.0.0",
"description": "Bruno test collection for basic ssl testing"
}

View File

@@ -0,0 +1,15 @@
meta {
name: request
type: http
seq: 6
}
get {
url: https://www.badssl.com
body: none
auth: inherit
}
assert {
res.status: eq 200
}

View File

@@ -0,0 +1,6 @@
{
"version": "1",
"name": "self-signed-badssl",
"type": "collection",
"ignore": ["node_modules", ".git"]
}

View File

@@ -0,0 +1,5 @@
{
"name": "self-signed-badssl",
"version": "1.0.0",
"description": "Bruno test collection for basic ssl testing"
}

View File

@@ -0,0 +1,15 @@
meta {
name: request
type: http
seq: 6
}
get {
url: https://self-signed.badssl.com
body: none
auth: inherit
}
assert {
res.status: eq 200
}

View File

@@ -0,0 +1,59 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('basic ssl success', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('badssl').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/badssl"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": false,
"filePath": ""
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/self-signed-badssl"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": false,
"filePath": ""
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,59 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('self signed rejected', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('self-signed-badssl').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(0);
await expect(parseInt(failed)).toBe(1);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(0);
await expect(parseInt(failed)).toBe(1);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/self-signed-badssl"],
"preferences": {
"request": {
"sslVerification": false,
"customCaCertificate": {
"enabled": false,
"filePath": ""
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,59 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('self signed success with validation disabled', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('self-signed-badssl').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,6 @@
{
"version": "1",
"name": "custom-ca-certs",
"type": "collection",
"ignore": ["node_modules", ".git"]
}

View File

@@ -0,0 +1,5 @@
{
"name": "custom-ca-certs",
"version": "1.0.0",
"description": "Bruno test collection for CA certificates and HTTPS server testing"
}

View File

@@ -0,0 +1,16 @@
meta {
name: request
type: http
seq: 6
}
get {
url: https://localhost:8090
body: none
auth: inherit
}
assert {
res.status: eq 200
res.body: eq helloworld
}

View File

@@ -0,0 +1 @@
certs

View File

@@ -0,0 +1,225 @@
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
};

View File

@@ -0,0 +1,60 @@
const { execSync } = require('node:child_process');
const os = require('node:os');
function execCommand(command, cwd = process.cwd()) {
return execSync(command, {
cwd,
stdio: 'inherit',
timeout: 30000
});
}
function execCommandSilent(command, cwd = process.cwd()) {
return execSync(command, {
cwd,
stdio: 'pipe',
timeout: 30000
});
}
function detectPlatform() {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'macos';
case 'linux': return 'linux';
case 'win32': return 'windows';
default: throw new Error(`Unsupported platform: ${platform}`);
}
}
function killProcessOnPort(port) {
const platform = detectPlatform();
try {
switch (platform) {
case 'macos':
execCommand(`lsof -ti :${port} | xargs kill -9`);
break;
case 'linux':
execCommand(`lsof -ti :${port} | xargs kill -9`);
break;
case 'windows':
const result = execCommandSilent(`netstat -ano | findstr :${port}`);
const lines = result.toString().split('\n');
for (const line of lines) {
const match = line.trim().match(/\s+(\d+)$/);
if (match) {
execCommandSilent(`taskkill /F /PID ${match[1]}`);
}
}
break;
}
} catch (error) {}
}
module.exports = {
execCommand,
execCommandSilent,
detectPlatform,
killProcessOnPort
};

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
const path = require('node:path');
const fs = require('node:fs');
const https = require('node:https');
const { killProcessOnPort } = require('./helpers/platform');
function createServer(certsDir, port = 8090) {
const serverOptions = {
key: fs.readFileSync(path.join(certsDir, 'localhost-key.pem')),
cert: fs.readFileSync(path.join(certsDir, 'localhost-cert.pem')),
ca: fs.readFileSync(path.join(certsDir, 'ca-cert.pem'))
}
const server = https.createServer(serverOptions, (req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('helloworld');
});
return new Promise((resolve, reject) => {
server.listen(port, (error) => {
if (error) {
reject(error);
} else {
resolve(server);
}
});
});
}
function shutdownServer(server, cleanup) {
const shutdown = (signal) => {
console.log(`🛑 Received ${signal}, shutting down`);
if (cleanup) cleanup();
if (server) {
server.close(() => process.exit(0));
} else {
process.exit(0);
}
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
}
async function startServer() {
const certsDir = path.join(__dirname, 'certs');
const port = 8090;
console.log('🚀 Starting HTTPS test server');
try {
killProcessOnPort(port);
console.log(`🌐 Creating server on port ${port}`);
const server = await createServer(certsDir, port);
shutdownServer(server, () => {
console.log('✨ Server cleanup completed');
});
} catch (error) {
console.error('❌ Server startup failed:', error.message);
process.exit(1);
}
}
if (require.main === module) {
startServer();
}
module.exports = { startServer };

View File

@@ -0,0 +1,106 @@
# CA Certificates Test Server
A Node.js HTTPS test server with self-signed certificate generation for testing SSL/TLS connections in Bruno.
## Overview
This server provides two main functionalities:
1. **Certificate Generation** - Creates a complete CA certificate chain for testing
2. **HTTPS Server** - Runs a secure server using the generated certificates
## Usage
### 1. Generate Certificates
Generate the required CA certificates and add them to your system's truststore:
```bash
node scripts/generate-certs.js
```
This will:
- Create a `certs/` directory
- Generate CA certificate, server certificate, and private keys
- Verify the certificate chain
- Add the CA certificate to your system's truststore (macOS/Linux/Windows)
**Generated Files:**
- `certs/ca-cert.pem` - Certificate Authority certificate
- `certs/ca-key.pem` - CA private key
- `certs/localhost-cert.pem` - Server certificate for localhost
- `certs/localhost-key.pem` - Server private key
**Windows-Specific Files (automatically generated on Windows):**
- `certs/ca-cert.der` - CA certificate in DER format (for Windows certificate store)
- `certs/localhost.p12` - PKCS#12 bundle containing server certificate and key
- `certs/localhost-cert.der` - Server certificate in DER format
### Certificate Installation Details
The certificate generation script automatically adds the CA certificate to your system's truststore:
**macOS:** Uses `security add-trusted-cert` to add the CA to the System keychain
**Linux:** Copies the CA certificate to `/usr/local/share/ca-certificates/` and runs `update-ca-certificates`
**Windows:** Uses PowerShell to add the CA certificate to the LocalMachine\Root certificate store
> **Note:** On Windows, the script requires Administrator privileges to install certificates to the machine-wide certificate store. If you encounter permission issues, run your terminal as Administrator.
### 2. Run HTTPS Server
Start the HTTPS server on port 8090:
```bash
node index.js
```
The server will:
- Load certificates from the `certs/` directory
- Start an HTTPS server on `https://localhost:8090`
- Serve a simple "helloworld" response
- Handle graceful shutdown on SIGINT/SIGTERM
## Testing
Once the server is running, you can test SSL connections:
### Unix/Linux/macOS
```bash
# Test with curl
curl https://localhost:8090
# Test certificate verification
openssl s_client -connect localhost:8090 -CAfile certs/ca-cert.pem
```
### Windows
```powershell
# Test with curl (if available)
curl https://localhost:8090
# Test with PowerShell Invoke-WebRequest
Invoke-WebRequest -Uri https://localhost:8090
# Test certificate verification with OpenSSL
openssl s_client -connect localhost:8090 -CAfile certs/ca-cert.pem
# Verify certificate is installed in Windows certificate store
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "*Local Dev CA*" }
# Test with .NET WebClient (alternative method)
$client = New-Object System.Net.WebClient
$client.DownloadString("https://localhost:8090")
```
## File Structure
```
server/
├── index.js # Main HTTPS server
├── scripts/
│ └── generate-certs.js # Certificate generation script
├── helpers/
│ ├── certs.js # Certificate management utilities
│ └── platform.js # Platform-specific utilities
├── certs/ # Generated certificates (created by script)
└── readme.md # This file
```

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env node
const path = require('node:path');
const {
createCertsDir,
generateCertificates,
addCAToTruststore,
verifyCertificates
} = require('../helpers/certs');
/**
* Setup CA certificates for testing server
*/
async function setup() {
console.log('🔧 Setting up CA certificates for test server');
const certsDir = path.join(__dirname, '..', 'certs');
try {
console.log('📁 Creating certificates directory');
createCertsDir(certsDir);
console.log('🔐 Generating certificates');
generateCertificates(certsDir);
console.log('✅ Verifying certificates');
verifyCertificates(certsDir);
console.log('🛡️ Adding CA to truststore');
addCAToTruststore(certsDir);
console.log('🎉 CA certificate setup completed successfully');
return true;
} catch (error) {
console.error('❌ Generate certs failed:', error.message);
throw error;
}
}
if (require.main === module) {
setup()
.then(() => process.exit(0))
.catch(() => process.exit(1));
}
module.exports = { setup };

View File

@@ -0,0 +1,57 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('custom invalid ca cert added to the config and keep default ca certs', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('custom-ca-certs').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": true,
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-key.pem"
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,57 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('custom invalid ca cert added to the config and NO default ca certs', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('custom-ca-certs').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(0);
await expect(parseInt(failed)).toBe(1);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(0);
await expect(parseInt(failed)).toBe(1);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": true,
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-key.pem"
},
"keepDefaultCaCertificates": {
"enabled": false
}
}
}
}

View File

@@ -0,0 +1,57 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('custom valid ca cert added to the config and keep default ca certs', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('custom-ca-certs').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": true,
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-cert.pem"
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,57 @@
import { test, expect } from '../../../../../playwright';
test.describe.serial('custom valid ca cert added to the config and NO default ca certs', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('custom-ca-certs').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(1);
await expect(parseInt(passed)).toBe(1);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": true,
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-cert.pem"
},
"keepDefaultCaCertificates": {
"enabled": false
}
}
}
}