Skip to content

[@types/node] tls.Certificate DN fields should be string | string[], not string #74538

@tgies

Description

@tgies

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));
// -> true

Impact

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) // false

OU 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[];
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions