-
Notifications
You must be signed in to change notification settings - Fork 30.6k
Description
Problem
The tls.Certificate interface (used by PeerCertificate.subject and PeerCertificate.issuer) types all Distinguished Name fields as string:
interface Certificate {
C: string;
ST: string;
L: string;
O: string;
OU: string;
CN: string;
}However, Node.js returns string[] when a DN attribute has multiple values. This is a well-known X.509 feature -- certificates can have multiple OUs, for example. Node.js's own documentation shows this in its example output for tlsSocket.getPeerCertificate():
subject:
{ OU: [ 'Domain Control Validated', 'PositiveSSL Wildcard' ],
CN: '*.nodejs.org' },(Source: https://nodejs.org/api/tls.html#certificate-object)
Runtime Proof
const { X509Certificate } = require('crypto');
const { execSync } = require('child_process');
const pem = execSync(
'openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 ' +
'-nodes -keyout /dev/null -days 1 ' +
'-subj "/CN=test/OU=Engineering/OU=DevTeam/O=TestCorp" 2>/dev/null'
);
const cert = new X509Certificate(pem).toLegacyObject();
console.log(cert.subject.OU);
// -> [ 'Engineering', 'DevTeam' ]
console.log(Array.isArray(cert.subject.OU));
// -> trueImpact
Any code that assumes cert.subject.OU (or any other DN field) is always a string will silently produce wrong results on multi-valued certificates:
// These all fail silently when OU is an array:
cert.subject.OU.toLowerCase() // TypeError at runtime
cert.subject.OU === 'Engineering' // false (comparing string to array)
new Set(allowed).has(cert.subject.OU) // falseOU is the most commonly multi-valued field in enterprise PKI, but any DN attribute can repeat per X.509/RFC 5280.
Suggested Fix
interface Certificate {
C: string | string[];
ST: string | string[];
L: string | string[];
O: string | string[];
OU: string | string[];
CN: string | string[];
}This is a type-level breaking change -- downstream code doing cert.subject.CN.toLowerCase() would need a type guard -- but it reflects the actual runtime behavior and prevents silent bugs.
Workaround
Consumers can use declaration merging in the meantime:
declare module 'tls' {
interface Certificate {
C: string | string[];
ST: string | string[];
L: string | string[];
O: string | string[];
OU: string | string[];
CN: string | string[];
}
}