forge icon indicating copy to clipboard operation
forge copied to clipboard

mTLS certificate generation

Open DeWolfRobin opened this issue 3 years ago • 0 comments

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);
            }
        );
    }
);

DeWolfRobin avatar May 02 '22 14:05 DeWolfRobin