13

I am writing a small project using Node.JS and TypeScript, once of the requirements is to read a PFX certificate from a .pfx file and use this in the code to encrypt the payload body

I have a certificate public/private key file called cert1.pfx, my code requires this certificate as below

...
const cert = loadPfx("cert1.pfx");
const p: Payload = new Payload();
p.addReaderCertificate(cert);
...

I have searched around but cannot find a way to load the PFX for my use case, I have seen examples of loading a PFX for HTTPS server or Express.JS, I looked a node-x509 but that is for BASE64 encoded CER or PEM certificates, I also looked at node-rsa but thats for encrypt/decrypt using public/private keys.

Does anyone know if this is possible? If so would appreciate some pointers on how to accomplish.

Neil Stevens
  • 3,534
  • 6
  • 42
  • 71

5 Answers5

25

So after a LOT of research and trawling the Google archives I came across a package called pem and this has the following method:

pem.readPkcs12(bufferOrPath, [options], callback)

This can read a PKCS#12 file (or in other words a *.pfx or *.p12 file) amongst other things, I must have missed this in my earlier research.

Usage:

const pem = require("pem");
const fs = require("fs");

const pfx = fs.readFileSync(__dirname + "/test.pfx");
pem.readPkcs12(pfx, { p12Password: "password" }, (err, cert) => {
    console.log(cert);
});

Output:

{ cert: "...", ca: ["subca", "rootca"], key: "..." }

You can find more here and here.

gerichhome
  • 1,872
  • 2
  • 15
  • 21
Neil Stevens
  • 3,534
  • 6
  • 42
  • 71
15

It sounds like you only need to use Node's own https capabilities. Node can read the PFX file directly. (Https.createServer, SSL Options)

Example from Node.js site:

const https = require('https');
const fs = require('fs');

const options = {
  pfx: fs.readFileSync('test/fixtures/test_cert.pfx'),
  passphrase: 'sample'
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);
Constablebrew
  • 816
  • 1
  • 7
  • 23
3

I was also stuck on a similar problem @neil-stevens solution helped me to read the .pfx file but what i find is one feature/bug ( i don't know exactly what it is) of pem that it returns encrypted private key mostly in RSA , but if you need actual private key you need to export encrypted key into pkcs8,which can be done using Node RSA.

Usage :

const RSAKey = cert.key; const key = new NodeRSA(RSAKey); const privateKey = key.exportKey("pkcs8");

mondyfy
  • 417
  • 4
  • 10
1

Complementing the answer of Neil Stevens (Sorry, idk how to quote).

  • I hade that issue of getting a empty object only, but so i just observe that i was not loging the err. So i did that, after that i was able to resolve my problems

1º You need to have installed openSSL or libressl how described on https://github.com/Dexus/pem.

  • Tip: To install if you already have configured and instaled Chocolatey. Just Run the command: "choco install openssl". (with eleveted permissions)

2º I had to use pem.config() to set exatcly path of openSSL folder. I think you can also define in the Environment Variables, i just not tested this way yet.

3º Finally my code it was left like this:

const pem = require("pem");
const fs = require("fs");
const pfx = fs.readFileSync("./cert.pfx");
let certificate = '';

pem.config({
  pathOpenSSL: 'C:\\Program Files\\OpenSSL-Win64\\bin\\openssl'
})

const getPrivateKey = async () => {
  return new Promise(async (resolve, reject) => {
    pem.readPkcs12(pfx, { p12Password: 'myPassWordStr' }, (err, cert) => {
      console.log('err::: ', err);
      // console.log('cert::: ', cert);
      resolve(cert);
    });
  });
}

const start = async () => {
  certificate = await getPrivateKey();
  console.log('privateKey::: ', certificate);
}

start();
Maaelphd
  • 46
  • 3
1

I arrived here while trying to find a way to configure a local web server with HTTPS for local development using the development certificate generated by the .NET CLI (as this is easily created / trusted / removed).

Thanks to Neil Stephen's answer I was able to create a working solution on Windows, using a combination of npm, dotnet CLI, openssl and npm package pem.

Git ships with a copy of OpenSSL, so I didn't need to install it separately :)

.env

OPENSSL_PATH=C:\Program Files\Git\usr\bin\openssl
CERT_PASSWORD=SecurePassword123
CERT_PFX=cert/localhost.pfx
CERT_PEM=cert/localhost.pem
CERT_KEY=cert/localhost.key
PORT=443
ENTRYPOINT=src/index.html

package.json

"scripts": {
  "start": "env-cmd -x parcel $ENTRYPOINT --https --cert $CERT_PEM --key $CERT_KEY --port $PORT --open",
  "build": "env-cmd -x parcel build $ENTRYPOINT",
  "dev-certs": "run-s dev-certs:create dev-certs:convert",
  "dev-certs:create": "env-cmd -x dotnet dev-certs https -ep $CERT_PFX -p $CERT_PASSWORD --verbose --trust",
  "dev-certs:convert": "node ./cli/cert.mjs",
  "dev-certs:clean": "dotnet dev-certs https --clean"
},

cert.mjs

import pem from "pem";
import { PFX2PEM } from "pem/lib/convert.js";
import fs from "fs";
import "dotenv/config";

pem.config({
  pathOpenSSL: process.env.OPENSSL_PATH
});

const pass = process.env.CERT_PASSWORD;

// GET .KEY FILE - without this, HMR won't work
const pfx = fs.readFileSync(process.env.CERT_PFX);
pem.readPkcs12(pfx, { p12Password: pass }, (err, cert) => {
  if (!!err) {
    console.error(err);
    return;
  }
  // console.log(cert.key);
  fs.writeFileSync(process.env.CERT_KEY, cert.key);
});

// GET .PEM FILE
PFX2PEM(process.env.CERT_PFX, process.env.CERT_PEM, pass, (errPem, successPem) => {
  if (!successPem) {
    console.error(errPem);
    return;
  }
  
  console.log(`Certificate '${process.env.CERT_PEM}' created!`);
});

Repository is here: https://github.com/zplume/parcel-https and the README contains details of how it works.

Quantum
  • 11
  • 2