/**
* @file Tests for Asn1DerUtils
- * @description Unit tests for ASN.1 DER encoding primitives and PKCS#10 CSR generation
+ * @description Unit tests for ASN.1 DER encoding, parsing, and PKCS#10 CSR generation
*/
import assert from 'node:assert/strict'
-import { X509Certificate } from 'node:crypto'
+import { hash, X509Certificate } from 'node:crypto'
import { afterEach, describe, it } from 'node:test'
import {
})
await describe('readDerLength', async () => {
- await it('should parse short form length', () => {
- const buf = Buffer.from([42])
- const result = readDerLength(buf, 0)
+ await it('should round-trip with derLength for short form', () => {
+ const encoded = derLength(42)
+ const result = readDerLength(encoded, 0)
assert.strictEqual(result.length, 42)
- assert.strictEqual(result.end, 1)
+ assert.strictEqual(result.end, encoded.length)
})
- await it('should parse single-byte long form (0x81)', () => {
- const buf = Buffer.from([0x81, 200])
- const result = readDerLength(buf, 0)
+ await it('should round-trip with derLength for single-byte long form', () => {
+ const encoded = derLength(200)
+ const result = readDerLength(encoded, 0)
assert.strictEqual(result.length, 200)
- assert.strictEqual(result.end, 2)
+ assert.strictEqual(result.end, encoded.length)
})
- await it('should parse two-byte long form (0x82)', () => {
- const buf = Buffer.from([0x82, 0x01, 0x2c])
- const result = readDerLength(buf, 0)
+ await it('should round-trip with derLength for two-byte long form', () => {
+ const encoded = derLength(300)
+ const result = readDerLength(encoded, 0)
assert.strictEqual(result.length, 300)
- assert.strictEqual(result.end, 3)
+ assert.strictEqual(result.end, encoded.length)
})
await it('should throw RangeError for empty buffer', () => {
assert.strictEqual(issuerDer[0], 0x30)
})
+ await it('should extract issuer that differs from hashing the string DN', () => {
+ const x509 = new X509Certificate(VALID_X509_PEM_CERTIFICATE)
+ const issuerDer = extractDerIssuer(x509.raw)
+ const derHash = hash('sha256', issuerDer, 'hex')
+ const stringHash = hash('sha256', x509.issuer, 'hex')
+ assert.notStrictEqual(derHash, stringHash)
+ })
+
await it('should produce consistent output for the same certificate', () => {
const x509 = new X509Certificate(VALID_X509_PEM_CERTIFICATE)
const first = extractDerIssuer(x509.raw)
const result = manager.validateCertificateX509(brokenChain)
assert.strictEqual(result.valid, false)
- assert.ok(
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- result.reason?.includes('issuer mismatch') ||
- result.reason?.includes('signature verification failed')
- )
+ assert.match(result.reason ?? '', /issuer mismatch|signature verification failed/)
})
await it('should return invalid when chain has expired intermediate', () => {
*/
export const EMPTY_PEM_CERTIFICATE = ''
-/**
- * Real EC P-256 CA certificate (CN=TestRootCA, valid 2026-2036).
- * Used as issuer for VALID_X509_LEAF_CERTIFICATE.
- */
-export const VALID_X509_CA_CERTIFICATE = `-----BEGIN CERTIFICATE-----
-MIIBGDCBwAIJAMY5KBDzNkHGMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMClRlc3RS
-b290Q0EwHhcNMjYwNDA3MjIxMjU1WhcNMzYwNDA0MjIxMjU1WjAVMRMwEQYDVQQD
-DApUZXN0Um9vdENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhOoqHSLrnwVr
-0Nu78E9rGDQmhGK1qvr2W7ye7yXMHgfFGYMuVr7ejCj6TXk2YPSS8ADRwCRj8R6S
-JFomGI2soDAKBggqhkjOPQQDAgNHADBEAiBDCuKOo0v3y/ZTGSf20GQyTtmfibV5
-5t1yXJkVTudOMgIgWqQXoyjI/k2s+T9U38X4S9yTeMkjeNb3FEYVq5kaA5w=
------END CERTIFICATE-----`
-
-/**
- * Real EC P-256 leaf certificate (CN=TestLeaf, valid 2026-2036).
- * Signed by VALID_X509_CA_CERTIFICATE.
- */
-export const VALID_X509_LEAF_CERTIFICATE = `-----BEGIN CERTIFICATE-----
-MIIBGDCBvgIJAJn8LXzPXkayMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMClRlc3RS
-b290Q0EwHhcNMjYwNDA3MjIxMjU1WhcNMzYwNDA0MjIxMjU1WjATMREwDwYDVQQD
-DAhUZXN0TGVhZjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDMWqNhqXlWIksJi
-fYrEQLdPlWG4zAfvc/1q7npD5d+OJu4uwbseA2uXf8/wHgQYrpD3jkXT4b/amhyv
-W1dCq+0wCgYIKoZIzj0EAwIDSQAwRgIhAPnXKKIs+8sE+W+3AH9zE3Z51I1ndCks
-wD0Gud+kCORgAiEArgyP/lVR0Vh9NWe8iTNVXOyc4s8Jn0J+CF9UsUIGuFA=
------END CERTIFICATE-----`
-
// ============================================================================
// Real X.509 Certificates (parseable by node:crypto X509Certificate)
// ============================================================================
/**
- * Valid self-signed X.509 certificate (EC P-256, CN=TestCA, valid 2026-2036).
- * Parseable by node:crypto X509Certificate for X.509 structural validation tests.
+ * Self-signed X.509 certificate (EC P-256, CN=TestCA, valid 2026-2036).
*/
export const VALID_X509_PEM_CERTIFICATE = `-----BEGIN CERTIFICATE-----
MIICBjCCAawCCQDuW/VTwcEHDTAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZUZXN0
/**
* Expired self-signed X.509 certificate (EC P-256, CN=ExpiredTestCA, valid 2020-2021).
- * Parseable by node:crypto X509Certificate; triggers expiration check.
*/
export const EXPIRED_X509_PEM_CERTIFICATE = `-----BEGIN CERTIFICATE-----
MIIBLjCB1qADAgECAhRHkkXuRncB5xOMPnTt0pqA/uWOsTAKBggqhkjOPQQDAjAY
XvHyZ5eCRgOTBCMBDXvxxZXGaWFsrhq066F0MKd6D1ICICrQl8LxyYh72Bc0gWBG
2LQtv+sPK1CmsMqp8G8DiFqY
-----END CERTIFICATE-----`
+
+/**
+ * CA certificate (EC P-256, CN=TestRootCA, valid 2026-2036).
+ * Issuer for VALID_X509_LEAF_CERTIFICATE.
+ */
+export const VALID_X509_CA_CERTIFICATE = `-----BEGIN CERTIFICATE-----
+MIIBGDCBwAIJAMY5KBDzNkHGMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMClRlc3RS
+b290Q0EwHhcNMjYwNDA3MjIxMjU1WhcNMzYwNDA0MjIxMjU1WjAVMRMwEQYDVQQD
+DApUZXN0Um9vdENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhOoqHSLrnwVr
+0Nu78E9rGDQmhGK1qvr2W7ye7yXMHgfFGYMuVr7ejCj6TXk2YPSS8ADRwCRj8R6S
+JFomGI2soDAKBggqhkjOPQQDAgNHADBEAiBDCuKOo0v3y/ZTGSf20GQyTtmfibV5
+5t1yXJkVTudOMgIgWqQXoyjI/k2s+T9U38X4S9yTeMkjeNb3FEYVq5kaA5w=
+-----END CERTIFICATE-----`
+
+/**
+ * Leaf certificate (EC P-256, CN=TestLeaf, valid 2026-2036).
+ * Signed by VALID_X509_CA_CERTIFICATE.
+ */
+export const VALID_X509_LEAF_CERTIFICATE = `-----BEGIN CERTIFICATE-----
+MIIBGDCBvgIJAJn8LXzPXkayMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMClRlc3RS
+b290Q0EwHhcNMjYwNDA3MjIxMjU1WhcNMzYwNDA0MjIxMjU1WjATMREwDwYDVQQD
+DAhUZXN0TGVhZjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDMWqNhqXlWIksJi
+fYrEQLdPlWG4zAfvc/1q7npD5d+OJu4uwbseA2uXf8/wHgQYrpD3jkXT4b/amhyv
+W1dCq+0wCgYIKoZIzj0EAwIDSQAwRgIhAPnXKKIs+8sE+W+3AH9zE3Z51I1ndCks
+wD0Gud+kCORgAiEArgyP/lVR0Vh9NWe8iTNVXOyc4s8Jn0J+CF9UsUIGuFA=
+-----END CERTIFICATE-----`