Skip to content

Instantly share code, notes, and snippets.

@ncortines
Last active March 21, 2024 19:21
Show Gist options
  • Save ncortines/7fa5fc66b50d28e9452f775780e6d918 to your computer and use it in GitHub Desktop.
Save ncortines/7fa5fc66b50d28e9452f775780e6d918 to your computer and use it in GitHub Desktop.
Ephemeral SSL certificates for Electron based applications
'use strict';
const https = require('https');
const crypto = require('crypto');
const tls = require('tls');
const forge = require('node-forge');
const pki = forge.pki;
const getRandomPortNumber = () =>
Math.floor(Math.random() * (65535 - 49152)) + 49152
const assignRandomPortToServer = server =>
new Promise((resolve, reject) => {
server.listen(getRandomPortNumber())
.on('error', error => {
if (error.code === 'EADDRINUSE') {
server.close();
server.listen(getRandomPortNumber());
} else {
reject(error);
}
})
.on('listening', () => {
resolve(server);
});
});
const getCryptoKeysInPemFormat = () =>
new Promise((resolve, reject) => {
console.info('http utils :: generating a new rsa key pair');
crypto.generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
}
},
(error, publicKeyInPemFormat, privateKeyInPemFormat) => {
if (error) {
reject(error)
} else {
resolve({
privateKeyInPemFormat,
publicKeyInPemFormat
})
}
});
});
const getEcdheCiphers = () =>
tls.getCiphers()
.filter(cipher => cipher.startsWith('ecdhe'))
.map(cipher => cipher.toUpperCase())
.join(', ');
const getCertificateInPemFormat = (privateKeyInPemFormat, publicKeyInPemFormat) => {
console.info('http utils :: creating a new X.509 self-signed certificate');
const privateKey = pki.privateKeyFromPem(privateKeyInPemFormat)
const publicKey = pki.publicKeyFromPem(publicKeyInPemFormat)
const cert = pki.createCertificate();
cert.publicKey = publicKey;
cert.serialNumber = '01' + crypto.randomBytes(19).toString('hex');
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [{
name: 'commonName',
value: 'localhost'
}, {
name: 'countryName',
value: 'SP'
}, {
shortName: 'ST',
value: 'Madrid'
}, {
name: 'localityName',
value: 'Madrid'
}, {
name: 'organizationName',
value: 'My Org'
}, {
shortName: 'OU',
value: 'My Unit'
}];
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true
}, {
name: 'subjectAltName',
altNames: [
{
type: 7,
ip: '127.0.0.1'
},
{
type: 2,
value: 'localhost'
}
]
}, {
name: 'subjectKeyIdentifier'
}]);
cert.sign(privateKey);
return pki.certificateToPem(cert);
}
const startLocalHttpsServer = (requestCert = false, rejectUnauthorized = true) =>
getCryptoKeysInPemFormat()
.then(({ privateKeyInPemFormat, publicKeyInPemFormat }) => {
const secureCiphers = getEcdheCiphers();
const certificateInPemFormat = getCertificateInPemFormat(privateKeyInPemFormat, publicKeyInPemFormat);
console.info('http utils :: starting new local https server');
const options = {
key: privateKeyInPemFormat,
cert: certificateInPemFormat,
ciphers: secureCiphers,
requestCert: requestCert,
rejectUnauthorized: rejectUnauthorized
}
const server = https.createServer(options);
return assignRandomPortToServer(server);
});
module.exports = Object.freeze({
startLocalHttpsServer
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment