forge
forge copied to clipboard
mTLS certificate generation
I am trying to generate a server.key, server.pub (cert), client.key and client.pub (cert). The commands to do this are:
openssl genrsa -out server.key 2048
openssl req -new -x509 -key server.key -out server.pub -days 365 -config openssl.cnf
openssl genrsa -out client.key 2048
openssl req -new -x509 -key client.key -out client.pub -days 365 -config openssl.cnf
with openssl.cnf
I tried to recreate this in code but I can't seem to make it work, I get "UNABLE_TO_VERIFY_LEAF_SIGNATURE" when verifying:
const fs = require('fs');
const forge = require('node-forge');
const makeNumberPositive = (hexString) => {
let mostSignificativeHexDigitAsInt = parseInt(hexString[0], 16);
if (mostSignificativeHexDigitAsInt < 8) return hexString;
mostSignificativeHexDigitAsInt -= 8
return mostSignificativeHexDigitAsInt.toString() + hexString.substring(1)
}
// Generate a random serial number for the Certificate
const randomSerialNumber = () => {
return makeNumberPositive(forge.util.bytesToHex(forge.random.getBytesSync(20)));
}
// Get the Not Before Date for a Certificate (will be valid from 2 days ago)
const getCertNotBefore = () => {
let twoDaysAgo = new Date(Date.now() - 60*60*24*2*1000);
let year = twoDaysAgo.getFullYear();
let month = (twoDaysAgo.getMonth() + 1).toString().padStart(2, '0');
let day = twoDaysAgo.getDate();
return new Date(`${year}-${month}-${day} 00:00:00Z`);
}
// Get Certificate Expiration Date (Valid for 90 Days)
const getCertNotAfter = (notBefore) => {
let ninetyDaysLater = new Date(notBefore.getTime() + 60*60*24*90*1000);
let year = ninetyDaysLater.getFullYear();
let month = (ninetyDaysLater.getMonth() + 1).toString().padStart(2, '0');
let day = ninetyDaysLater.getDate();
return new Date(`${year}-${month}-${day} 23:59:59Z`);
}
// Get CA Expiration Date (Valid for 100 Years)
const getCANotAfter = (notBefore) => {
let year = notBefore.getFullYear() + 100;
let month = (notBefore.getMonth() + 1).toString().padStart(2, '0');
let day = notBefore.getDate();
return new Date(`${year}-${month}-${day} 23:59:59Z`);
}
const DEFAULT_C = 'Australia';
const DEFAULT_ST = 'Victoria';
const DEFAULT_L = 'Melbourne';
class CertificateGeneration {
static CreateRootCA() {
// Create a new Keypair for the Root CA
const { privateKey, publicKey } = forge.pki.rsa.generateKeyPair(2048);
// Define the attributes for the new Root CA
const attributes = [{
shortName: 'C',
value: DEFAULT_C
}, {
shortName: 'ST',
value: DEFAULT_ST
}, {
shortName: 'L',
value: DEFAULT_L
}, {
shortName: 'CN',
value: 'My Custom Testing RootCA'
}];
const extensions = [{
name: 'basicConstraints',
cA: true
},
{
name: 'subjectKeyIdentifier',
hash: true
}, {
name: 'authorityKeyIdentifier',
keyid: 'always',
issuer: true
},];
// Create an empty Certificate
const cert = forge.pki.createCertificate();
// Set the Certificate attributes for the new Root CA
cert.publicKey = publicKey;
cert.privateKey = privateKey;
cert.serialNumber = randomSerialNumber();
cert.validity.notBefore = getCertNotBefore();
cert.validity.notAfter = getCANotAfter(cert.validity.notBefore);
cert.setSubject(attributes);
cert.setIssuer(attributes);
cert.setExtensions(extensions);
// Self-sign the Certificate
cert.sign(privateKey, forge.md.sha512.create());
// Convert to PEM format
const pemCert = forge.pki.certificateToPem(cert);
const pemKey = forge.pki.privateKeyToPem(privateKey);
// Return the PEM encoded cert and private key
return { certificate: pemCert, privateKey: pemKey, notBefore: cert.validity.notBefore, notAfter: cert.validity.notAfter };
}
static CreateHostCert(hostCertCN, validDomains, rootCAObject) {
if (!hostCertCN.toString().trim()) throw new Error('"hostCertCN" must be a String');
if (!Array.isArray(validDomains)) throw new Error('"validDomains" must be an Array of Strings');
if (!rootCAObject || !rootCAObject.hasOwnProperty('certificate') || !rootCAObject.hasOwnProperty('privateKey')) throw new Error('"rootCAObject" must be an Object with the properties "certificate" & "privateKey"');
// Convert the Root CA PEM details, to a forge Object
let caCert = forge.pki.certificateFromPem(rootCAObject.certificate);
let caKey = forge.pki.privateKeyFromPem(rootCAObject.privateKey);
// Create a new Keypair for the Host Certificate
const hostKeys = forge.pki.rsa.generateKeyPair(2048);
// Define the attributes/properties for the Host Certificate
const attributes = [{
shortName: 'C',
value: DEFAULT_C
}, {
shortName: 'ST',
value: DEFAULT_ST
}, {
shortName: 'L',
value: DEFAULT_L
}, {
shortName: 'CN',
value: hostCertCN
}];
const extensions = [{
name: 'basicConstraints',
ca: false
}, {
name: 'subjectKeyIdentifier',
hash: true
}, {
name: 'authorityKeyIdentifier',
keyid: true,
issuer: true
}, {
name: 'keyUsage',
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true
}];
// Create an empty Certificate
let newHostCert = forge.pki.createCertificate();
// Set the attributes for the new Host Certificate
newHostCert.publicKey = hostKeys.publicKey;
newHostCert.serialNumber = randomSerialNumber();
newHostCert.validity.notBefore = getCertNotBefore();
newHostCert.validity.notAfter = getCertNotAfter(newHostCert.validity.notBefore);
newHostCert.setSubject(attributes);
newHostCert.setIssuer(caCert.subject.attributes);
newHostCert.setExtensions(extensions);
// Sign the new Host Certificate using the CA
newHostCert.sign(caKey, forge.md.sha512.create());
// Convert to PEM format
let pemHostCert = forge.pki.certificateToPem(newHostCert);
let pemHostKey = forge.pki.privateKeyToPem(hostKeys.privateKey);
return { certificate: pemHostCert, privateKey: pemHostKey, notAfter: newHostCert.validity.notBefore, notAfter: newHostCert.validity.notAfter };
}
}
let CA = CertificateGeneration.CreateRootCA();
server = CertificateGeneration.CreateHostCert('localhost', ['localhost'], CA)
client = CertificateGeneration.CreateHostCert('localhost', ['localhost'], CA)
fs.writeFileSync('./client.pub', client.certificate);
fs.writeFileSync('./client.key', client.privateKey);
fs.writeFileSync('./server.pub', server.certificate);
fs.writeFileSync('./server.key', server.privateKey);
//server.js
const ipc = require("node-ipc").default
/***************************************\
*
* You should start both hello and world
* then you will see them communicating.
*
* *************************************/
ipc.config.id = 'world';
ipc.config.retry= 1500;
ipc.config.networkHost='localhost';
ipc.config.tls={
public: __dirname+'/../server.pub',
private: __dirname+'/../server.key',
requestCert: true,
rejectUnauthorized:true,
trustedConnections: [
__dirname+'/../client.pub'
]
};
ipc.serveNet(
function(){
ipc.server.on(
'message',
function(data,socket){
ipc.log('got a message : ', data);
ipc.server.emit(
socket,
'message',
data+' world!'
);
}
);
ipc.server.on(
'socket.disconnected',
function(data,socket){
console.log(arguments);
}
);
}
);
ipc.server.start();
//client.js
const ipc = require("node-ipc").default
/***************************************\
*
* You should start both hello and world
* then you will see them communicating.
*
* *************************************/
ipc.config.id = 'hello';
ipc.config.retry= 1500;
ipc.config.networkHost='localhost';
ipc.config.tls={
private: __dirname+'/../client.key',
public: __dirname+'/../client.pub',
rejectUnauthorized:true,
trustedConnections: [
__dirname+'/../server.pub'
]
};
ipc.connectToNet(
'world',
function(){
ipc.of.world.on(
'connect',
function(){
ipc.log('## connected to world ##', ipc.config.delay);
ipc.of.world.emit(
'message',
'hello'
);
}
);
ipc.of.world.on(
'disconnect',
function(){
ipc.log('disconnected from world');
}
);
ipc.of.world.on(
'message',
function(data){
ipc.log('got a message from world : ', data);
}
);
}
);