Problem Statement
According to SPIFFE implementation, Workload API returns DER encoded intermediate CA's and leaf certificate also known as end entity certificate. And this certificates formed via concatenating certificates starting with leaf certificate.
Parsing these certificates and converting them is quite straightforward in Go with the help of standard crypto module.
According to SPIFFE documentation, it is possible to have multiple intermediate CA before a leaf certificate in the certificate chain.
But I was not able to find a library in Node.js which converts DER encoding to PEM encoding correctly, when this is the case.
Following libraries are investigated:
-
Node.js Standard Crypto Module
Problem with standard Crypto module in Node.js is, it does not parse all the buffer provided, only parses the leaf certificate and first intermediate CA. If there is more than one intermediate CA, it is lost.
-
Node-Forge
Has the same problem with Node.js standard crypto module, and in addition to that, node-forge does not support ECC based x509 certificates.
Why is this critical?
When Node.js applications want to make use of SPIFFE certificates, they need the certificates in PEM format because standard TLS module in Node.js supports only PEM encoded certificates. (createSecureContext function of standard module). Therefore the applications will not be able to use it
In short the problem is, SPIFFE allows having multiple Intermediate CA and leaf certificate concatenated with each other with DER encoding. But when existance of multple intermediate CA's, there is no library doing this properly. (Please
Solution Proposal
We can have a function in the client which converts DER to PEM format without parsing all certificate. Since DER encoding is a type length encoding, I was able to implement the following.
function parseDER(buffer) {
let currentIndex = 0;
let pemCerts = [];
while (currentIndex < buffer.length) {
let beginOfSequence = currentIndex;
// Get the first byte for tag
const tag = buffer[currentIndex++];
// Get the second byte for length value representing bytes
let length = buffer[currentIndex++];
if (length === 0x80) {
// Indefinite length encoding is not supported
throw new Error('Indefinite length encoding not supported');
}
if (length & 0x80) {
const lengthBytes = length & 0x7F;
length = 0;
for (let i = 0; i < lengthBytes; i++) {
length = (length << 8) | buffer[currentIndex++];
}
}
// Extract the value based on the tag and length
pemCerts.push(`-----BEGIN CERTIFICATE-----\n${Buffer.from(buffer.slice(beginOfSequence, currentIndex + length)).toString("base64")}\n-----END CERTIFICATE-----`);
// Move the current index to the next tag
currentIndex += length;
}
return pemCerts.join("\n")
}
I am open to any other solutions, or creating a PR for this. But first I wanted to share the issue with you to check if you have experienced similar problem.