4

Using node.js, I'd like to write code to programmatically do the equivalent of the following:

openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
openssl rsa -passin pass:x -in server.pass.key -out server.key
rm server.pass.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

When complete, I need the RSA key server.key and the self-signed SSL certificate server.crt.

forge looks the most promising, but so far I haven't figured out how to get it to work. I have the following code:

var pki = forge.pki;
var keys = pki.rsa.generateKeyPair(2048);
var privKey = forge.pki.privateKeyToPem(keys.privateKey);
var pubKey = forge.pki.publicKeyToPem(keys.publicKey);

But when I write the pubKey to a file, I've noticed it starts with ...

-----BEGIN PUBLIC KEY-----
MIIB...
-----END PUBLIC KEY-----

... and isn't recognized, whereas using openssl above it starts with:

-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----
Wade
  • 741
  • 1
  • 5
  • 18

2 Answers2

3

Since the original link went dead, I've made my own code that generates a self-signed certificate using node-forge (which it looks like they already have based on the original question), so I thought I'd put it here for someone who wants it

Simply creating a public and private key pair isn't enough to work as a certificate, you have to put in attributes, node-forge is incredibly useful this way, as its pki submodule is designed for this.

First, you need to create a certificate via pki.createCertificate(), this is where you'll assign all of your certificate attributes.

You need to set the certificate public key, serial number, and the valid from date and valid to date. In this example, the public key is set to the generated public key from before, the serial number is randomly generated, and the valid from and to dates are set to one day ago and one year in the future.

You then need to assign a subject, and extensions to your certificate, this is a very basic example, so the subject is just a name you can define (or let it default to 'Testing CA - DO NOT TRUST'), and the extensions are just a single 'Basic Constraints' extension, with certificate authority set to true.

We then set the issuer to itself, as all certificates need an issuer, and we don't have one.

Then we tell the certificate to sign itself, with the private key (corresponding to its public key we've assigned) that we generated earlier, this part is important when signing certificates (or child certificates), they need to be signed with the private key of its parent (this prevents you from making fake certificates with a trusted certificate parent, as you don't have that trusted parent's private key)

Then we return the new certificate in a PEM-encoded format, you could save this to a file or convert it to a buffer and use it for a https server.

const forge = require('node-forge')
const crypto = require('crypto')
const pki = forge.pki

//using a blank options is perfectly fine here
async function genCACert(options = {}) {
    options = {...{
        commonName: 'Testing CA - DO NOT TRUST',
        bits: 2048
    }, ...options}
    
    let keyPair = await new Promise((res, rej) => {
        pki.rsa.generateKeyPair({ bits: options.bits }, (error, pair) => {
            if (error) rej(error);
            else res(pair)
        })
    })
    
    let cert = pki.createCertificate()
    cert.publicKey = keyPair.publicKey
    cert.serialNumber = crypto.randomUUID().replace(/-/g, '')
    
    cert.validity.notBefore = new Date()
    cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1)
    cert.validity.notAfter = new Date()
    cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
    
    cert.setSubject([{name: 'commonName', value: options.commonName}])
    cert.setExtensions([{ name: 'basicConstraints', cA: true }])
    
    cert.setIssuer(cert.subject.attributes)
    cert.sign(keyPair.privateKey, forge.md.sha256.create())
    
    return {
        ca: {
            key: pki.privateKeyToPem(keyPair.privateKey),
            cert: pki.certificateToPem(cert)
        },
        fingerprint: forge.util.encode64(
            pki.getPublicKeyFingerprint(keyPair.publicKey, {
                type: 'SubjectPublicKeyInfo',
                md: forge.md.sha256.create(),
                encoding: 'binary'
            })
        )
    }
}

//you need to put the output from genCACert() through this if you want to use it for a https server
/* e.g
let cert = await genCACert();
let buffers = caToBuffer(cert.ca);
let options = {};
options.key = buffers.key;
options.cert = buffers.cert;
let server = https.createServer(options, <listener here>);
*/
function caToBuffer(ca) {
    return {
        key: Buffer.from(ca.key),
        cert: Buffer.from(ca.cert)
    }
}

Do with this what you will.

  • Hi, and welcome. When answering an old question please explain in your answer how your answer is an improvement on the existing answers. Also, the repo still had a history of the file from when the answer was posted: https://github.com/wadewegner/sfdx-waw-plugin/blob/7564986cb04af42c484da3ff2934894c7ed27dc2/commands/connectedapp_create.js – treckstar May 30 '22 at 00:31
  • 1
    okay, i'll update my answer to explain what it's doing – JoshieGemFinder Aug 12 '22 at 04:21
1

Okay, as you probably realized, I wasn't generating a certificate. It required quite a bit more work, which you can find here.

Essentially, after a bunch of setup, I had to create, sign, and convert the certificate to Pem:

cert.sign(keys.privateKey);
var pubKey = pki.certificateToPem(cert);

Hope this helps someone else!

treckstar
  • 1,956
  • 5
  • 21
  • 26
Wade
  • 741
  • 1
  • 5
  • 18