node.jsssl-certificatepkidernode-forge

node-forge: save DER encoded certificate to file


I need to save a certificate in both PEM and DER encoding.
PEM is not an issue:

const cert = forge.pki.createCertificate();
// (...)
// (...)
// cert.sign(...);
const pemCert = forge.pki.certificateToPem(cert);
fs.writeFileSync('cert.pem', pemCert, {encoding: 'utf-8'});
// ^ GOOD

DER on the other hand, I cannot figure out how to save the proper certificate to file.

I would expect the following JS code to match the output of
openssl x509 -outform der -in ./cert.pem -out ./cert_good.cer,
however that is not the case and I get something different, apparently "malformed":

const derCert = forge.pki.pemToDer(pemCert).getBytes();
fs.writeFileSync('cert_bad.cer', derCert, {encoding: 'utf-8'});

When I say "malformed" I refer to the fact that openssl x509 -inform der -in cert_bad.cer -text -noout returns: Could not find certificate from cert_bad.cer

What am I doing wrong?
ty

EDIT
I figured another way around this, using Node.js Crypto moudule -> class X509Certificate -> property raw.
Yet the question about node-forge remains. Anyway:

import { X509Certificate } from 'crypto'

const cert = forge.pki.createCertificate();
// (...)
// (...)
// cert.sign(...);
const pemCert = forge.pki.certificateToPem(cert);
fs.writeFileSync('cert.pem', pemCert, {encoding: 'utf-8'});
// ^ GOOD

const x509 = new X509Certificate(pemCert);
const derCert = x509.raw;
fs.writeFileSync('cert_good.cer', derCert, {encoding: 'utf-8'});
// ^ NOW ALSO GOOD

Solution

  • DER data is inherently binary (bytes or technically octets, not characters) but node-forge uses a ByteBuffer class that stores it in a JS string which is designed for characters. Treating the DER bytes as characters encodable in UTF-8 produces garbage. You need to write this string using encoding binary or latin1 (aliases) in fs.writeFile* to get the correct bytes. (I consider binary clearer in this situation but the nodejs people consider it legacy and don't prefer it.)

    OTOH builtin crypto uses (also builtin) Buffer and fs.writeFile* when given a Buffer ignores the encoding because it knows the Buffer is already bytes and not characters.