micro509 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +220 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1 -0
- package/dist/internal/asn1/asn1.js +2 -0
- package/dist/internal/asn1/asn1.js.map +1 -0
- package/dist/internal/asn1/der.js +2 -0
- package/dist/internal/asn1/der.js.map +1 -0
- package/dist/internal/asn1/oids.js +2 -0
- package/dist/internal/asn1/oids.js.map +1 -0
- package/dist/internal/crypto/algorithm-names.js +2 -0
- package/dist/internal/crypto/algorithm-names.js.map +1 -0
- package/dist/internal/crypto/ecdsa.js +2 -0
- package/dist/internal/crypto/ecdsa.js.map +1 -0
- package/dist/internal/crypto/hash.js +2 -0
- package/dist/internal/crypto/hash.js.map +1 -0
- package/dist/internal/crypto/pbes2.d.ts +23 -0
- package/dist/internal/crypto/pbes2.js +2 -0
- package/dist/internal/crypto/pbes2.js.map +1 -0
- package/dist/internal/crypto/rsa-pss.js +2 -0
- package/dist/internal/crypto/rsa-pss.js.map +1 -0
- package/dist/internal/crypto/sig-verify.js +2 -0
- package/dist/internal/crypto/sig-verify.js.map +1 -0
- package/dist/internal/crypto/signing.d.ts +16 -0
- package/dist/internal/crypto/signing.js +2 -0
- package/dist/internal/crypto/signing.js.map +1 -0
- package/dist/internal/crypto/webcrypto.js +2 -0
- package/dist/internal/crypto/webcrypto.js.map +1 -0
- package/dist/internal/shared/base64.js +2 -0
- package/dist/internal/shared/base64.js.map +1 -0
- package/dist/internal/shared/dn.js +2 -0
- package/dist/internal/shared/dn.js.map +1 -0
- package/dist/internal/shared/ip.js +2 -0
- package/dist/internal/shared/ip.js.map +1 -0
- package/dist/internal/verify/name-constraints-engine.js +2 -0
- package/dist/internal/verify/name-constraints-engine.js.map +1 -0
- package/dist/internal/verify/policy-engine.js +2 -0
- package/dist/internal/verify/policy-engine.js.map +1 -0
- package/dist/internal/verify/verify-path.js +2 -0
- package/dist/internal/verify/verify-path.js.map +1 -0
- package/dist/internal/x509/extension-bits.d.ts +18 -0
- package/dist/internal/x509/extension-bits.js +2 -0
- package/dist/internal/x509/extension-bits.js.map +1 -0
- package/dist/internal/x509/extension-registry.js +2 -0
- package/dist/internal/x509/extension-registry.js.map +1 -0
- package/dist/internal/x509/name-fields.js +2 -0
- package/dist/internal/x509/name-fields.js.map +1 -0
- package/dist/keys/keys.d.ts +431 -0
- package/dist/keys/keys.js +5 -0
- package/dist/keys/keys.js.map +1 -0
- package/dist/keys.d.ts +3 -0
- package/dist/keys.js +1 -0
- package/dist/pem/pem.d.ts +56 -0
- package/dist/pem/pem.js +6 -0
- package/dist/pem/pem.js.map +1 -0
- package/dist/pem.d.ts +2 -0
- package/dist/pem.js +1 -0
- package/dist/pkcs/pfx.d.ts +177 -0
- package/dist/pkcs/pfx.js +2 -0
- package/dist/pkcs/pfx.js.map +1 -0
- package/dist/pkcs/pkcs12-mac.d.ts +41 -0
- package/dist/pkcs/pkcs12-mac.js +2 -0
- package/dist/pkcs/pkcs12-mac.js.map +1 -0
- package/dist/pkcs/pkcs7.d.ts +131 -0
- package/dist/pkcs/pkcs7.js +2 -0
- package/dist/pkcs/pkcs7.js.map +1 -0
- package/dist/pkcs.d.ts +5 -0
- package/dist/pkcs.js +1 -0
- package/dist/result/result.d.ts +68 -0
- package/dist/result/result.js +2 -0
- package/dist/result/result.js.map +1 -0
- package/dist/result.d.ts +2 -0
- package/dist/result.js +1 -0
- package/dist/revocation/chain.d.ts +180 -0
- package/dist/revocation/chain.js +2 -0
- package/dist/revocation/chain.js.map +1 -0
- package/dist/revocation/crl.d.ts +316 -0
- package/dist/revocation/crl.js +2 -0
- package/dist/revocation/crl.js.map +1 -0
- package/dist/revocation/ocsp.d.ts +332 -0
- package/dist/revocation/ocsp.js +2 -0
- package/dist/revocation/ocsp.js.map +1 -0
- package/dist/revocation/revocation.d.ts +168 -0
- package/dist/revocation/revocation.js +2 -0
- package/dist/revocation/revocation.js.map +1 -0
- package/dist/revocation.d.ts +5 -0
- package/dist/revocation.js +1 -0
- package/dist/verify/identity.d.ts +129 -0
- package/dist/verify/identity.js +2 -0
- package/dist/verify/identity.js.map +1 -0
- package/dist/verify/name-constraints.d.ts +18 -0
- package/dist/verify/policy.d.ts +39 -0
- package/dist/verify/verify.d.ts +404 -0
- package/dist/verify/verify.js +2 -0
- package/dist/verify/verify.js.map +1 -0
- package/dist/verify.d.ts +5 -0
- package/dist/verify.js +1 -0
- package/dist/x509/certificate.d.ts +191 -0
- package/dist/x509/certificate.js +2 -0
- package/dist/x509/certificate.js.map +1 -0
- package/dist/x509/csr.d.ts +55 -0
- package/dist/x509/csr.js +2 -0
- package/dist/x509/csr.js.map +1 -0
- package/dist/x509/extensions.d.ts +550 -0
- package/dist/x509/extensions.js +2 -0
- package/dist/x509/extensions.js.map +1 -0
- package/dist/x509/name.d.ts +140 -0
- package/dist/x509/name.js +2 -0
- package/dist/x509/name.js.map +1 -0
- package/dist/x509/parse.d.ts +377 -0
- package/dist/x509/parse.js +2 -0
- package/dist/x509/parse.js.map +1 -0
- package/dist/x509.d.ts +8 -0
- package/dist/x509.js +1 -0
- package/package.json +153 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pbes2.js","names":[],"sources":["../../../src/internal/crypto/pbes2.ts"],"sourcesContent":["/**\n * PBES2 password-based encryption and decryption (RFC 8018).\n *\n * Supports AES-CBC-128/192/256 with PBKDF2 using HMAC-SHA-1 or HMAC-SHA-256.\n * Used internally by encrypted PKCS#8 and PFX flows.\n *\n * @module\n */\n\nimport {\n\tdecodeIntegerNumber,\n\tdecodeObjectIdentifier,\n\ttoArrayBuffer,\n} from '#micro509/internal/asn1/asn1.ts';\nimport {\n\tintegerFromNumber,\n\tnullValue,\n\tobjectIdentifier,\n\toctetString,\n\treadSequenceChildren,\n\tsequence,\n} from '#micro509/internal/asn1/der.ts';\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\nimport { getCrypto } from './webcrypto.ts';\n\n/** AES-CBC key sizes supported by this PBES2 implementation. */\nexport type Pbes2EncryptionScheme = 'aes128-cbc' | 'aes192-cbc' | 'aes256-cbc';\n\n/** PBKDF2 pseudo-random function choices. `hmac-sha1` is the RFC default; `hmac-sha256` is preferred. */\nexport type Pbes2Prf = 'hmac-sha1' | 'hmac-sha256';\n\n/** Input for `encryptPbes2`. */\nexport interface Pbes2EncryptionOptions {\n\t/** Password fed to PBKDF2 for key derivation. */\n\treadonly password: string;\n\t/** PBKDF2 iteration count. Default: `100_000`. */\n\treadonly iterations?: number;\n\t/** PBKDF2 salt. Default: 16 cryptographically random bytes. */\n\treadonly salt?: Uint8Array;\n\t/** AES-CBC initialization vector. Default: 16 cryptographically random bytes. */\n\treadonly iv?: Uint8Array;\n\t/** AES key size. Default: `'aes256-cbc'`. */\n\treadonly encryption?: Pbes2EncryptionScheme;\n\t/** PBKDF2 PRF. Default: `'hmac-sha256'`. */\n\treadonly prf?: Pbes2Prf;\n}\n\n/** Resolved PBES2 algorithm parameters, either parsed from DER or built by `encryptPbes2`. */\nexport interface Pbes2Parameters {\n\t/** PBKDF2 iteration count. */\n\treadonly iterations: number;\n\t/** PBKDF2 salt bytes. */\n\treadonly salt: Uint8Array;\n\t/** AES-CBC initialization vector. */\n\treadonly iv: Uint8Array;\n\t/** AES-CBC key-size variant. */\n\treadonly encryption: Pbes2EncryptionScheme;\n\t/** PBKDF2 pseudo-random function. */\n\treadonly prf: Pbes2Prf;\n}\n\n/** Output of `encryptPbes2`: ciphertext plus the DER-encoded AlgorithmIdentifier. */\nexport interface Pbes2EncryptionResult {\n\t/** DER-encoded PBES2 AlgorithmIdentifier SEQUENCE (embeds KDF + cipher params). */\n\treadonly algorithmIdentifierDer: Uint8Array;\n\t/** AES-CBC ciphertext (includes PKCS#7 padding). */\n\treadonly encryptedData: Uint8Array;\n\t/** Resolved algorithm parameters used during encryption. */\n\treadonly parameters: Pbes2Parameters;\n}\n\n/** Encrypts `data` using PBES2 (PBKDF2 + AES-CBC) and returns ciphertext with algorithm params. */\nexport async function encryptPbes2(\n\tdata: Uint8Array,\n\toptions: Pbes2EncryptionOptions,\n): Promise<Pbes2EncryptionResult> {\n\tconst iterations = options.iterations ?? 100_000;\n\tconst salt = options.salt ?? getCrypto().getRandomValues(new Uint8Array(16));\n\tconst iv = options.iv ?? getCrypto().getRandomValues(new Uint8Array(16));\n\tconst encryption = options.encryption ?? 'aes256-cbc';\n\tconst prf = options.prf ?? 'hmac-sha256';\n\n\t// Validate inputs before any WebCrypto calls\n\tif (!Number.isInteger(iterations) || iterations < 1) {\n\t\tthrow new RangeError(`Invalid iterations: must be an integer >= 1, got ${iterations}`);\n\t}\n\tif (!(salt instanceof Uint8Array) || salt.length < 8) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid salt: must be Uint8Array with length >= 8, got length ${salt.length}`,\n\t\t);\n\t}\n\tif (!(iv instanceof Uint8Array) || iv.length !== 16) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid IV: must be Uint8Array of exactly 16 bytes, got length ${iv.length}`,\n\t\t);\n\t}\n\n\tconst key = await deriveAesKey(options.password, salt, iterations, encryption, prf, ['encrypt']);\n\tconst encryptedData = new Uint8Array(\n\t\tawait getCrypto().subtle.encrypt(\n\t\t\t{ name: 'AES-CBC', iv: toArrayBuffer(iv) },\n\t\t\tkey,\n\t\t\ttoArrayBuffer(data),\n\t\t),\n\t);\n\treturn {\n\t\talgorithmIdentifierDer: encodePbes2AlgorithmIdentifier({\n\t\t\titerations,\n\t\t\tsalt,\n\t\t\tiv,\n\t\t\tencryption,\n\t\t\tprf,\n\t\t}),\n\t\tencryptedData,\n\t\tparameters: { iterations, salt, iv, encryption, prf },\n\t};\n}\n\n/** Decrypts PBES2 ciphertext given the DER AlgorithmIdentifier and password. Throws on wrong password. */\nexport async function decryptPbes2(\n\talgorithmIdentifierDer: Uint8Array,\n\tencryptedData: Uint8Array,\n\tpassword: string,\n): Promise<Uint8Array> {\n\tconst parameters = parsePbes2AlgorithmIdentifier(algorithmIdentifierDer);\n\tconst key = await deriveAesKey(\n\t\tpassword,\n\t\tparameters.salt,\n\t\tparameters.iterations,\n\t\tparameters.encryption,\n\t\tparameters.prf,\n\t\t['decrypt'],\n\t);\n\ttry {\n\t\treturn new Uint8Array(\n\t\t\tawait getCrypto().subtle.decrypt(\n\t\t\t\t{ name: 'AES-CBC', iv: toArrayBuffer(parameters.iv) },\n\t\t\t\tkey,\n\t\t\t\ttoArrayBuffer(encryptedData),\n\t\t\t),\n\t\t);\n\t} catch {\n\t\tthrow new Error('Invalid password or encrypted content');\n\t}\n}\n\n/** DER-encodes a PBES2 AlgorithmIdentifier SEQUENCE from resolved parameters. */\nexport function encodePbes2AlgorithmIdentifier(parameters: Pbes2Parameters): Uint8Array {\n\tconst encryption = resolveEncryptionProfile(parameters.encryption);\n\tconst prf = resolvePrfProfile(parameters.prf);\n\treturn sequence([\n\t\tobjectIdentifier(OIDS.pbes2),\n\t\tsequence([\n\t\t\tsequence([\n\t\t\t\tobjectIdentifier(OIDS.pbkdf2),\n\t\t\t\tsequence([\n\t\t\t\t\toctetString(parameters.salt),\n\t\t\t\t\tintegerFromNumber(parameters.iterations),\n\t\t\t\t\tintegerFromNumber(encryption.keyLengthBytes),\n\t\t\t\t\tsequence([objectIdentifier(prf.oid), nullValue()]),\n\t\t\t\t]),\n\t\t\t]),\n\t\t\tsequence([objectIdentifier(encryption.oid), octetString(parameters.iv)]),\n\t\t]),\n\t]);\n}\n\n/** Decodes a DER-encoded PBES2 AlgorithmIdentifier into structured {@linkcode Pbes2Parameters}. */\nexport function parsePbes2AlgorithmIdentifier(algorithmIdentifierDer: Uint8Array): Pbes2Parameters {\n\tconst topLevel = readSequenceChildren(algorithmIdentifierDer);\n\tconst oid = topLevel[0];\n\tconst params = topLevel[1];\n\tif (oid === undefined || params === undefined) {\n\t\tthrow new Error('Malformed PBES2 algorithm identifier');\n\t}\n\tif (decodeObjectIdentifier(oid.value) !== OIDS.pbes2) {\n\t\tthrow new Error('Unsupported encryption algorithm');\n\t}\n\tconst paramsDer = algorithmIdentifierDer.slice(params.start - params.headerLength, params.end);\n\tconst pbes2Params = readSequenceChildren(paramsDer);\n\tconst kdf = pbes2Params[0];\n\tconst scheme = pbes2Params[1];\n\tif (kdf === undefined || scheme === undefined) {\n\t\tthrow new Error('Malformed PBES2 params');\n\t}\n\tconst kdfDer = paramsDer.slice(kdf.start - kdf.headerLength, kdf.end);\n\tconst kdfChildren = readSequenceChildren(kdfDer);\n\tconst kdfOid = kdfChildren[0];\n\tconst kdfParams = kdfChildren[1];\n\tif (kdfOid === undefined || kdfParams === undefined) {\n\t\tthrow new Error('Malformed KDF params');\n\t}\n\tif (decodeObjectIdentifier(kdfOid.value) !== OIDS.pbkdf2) {\n\t\tthrow new Error('Unsupported KDF');\n\t}\n\t// PBKDF2 params: SEQUENCE { salt OCTET STRING, iterationCount INTEGER, [keyLength INTEGER], [prf AlgorithmIdentifier] }\n\tconst pbkdf2Der = kdfDer.slice(kdfParams.start - kdfParams.headerLength, kdfParams.end);\n\tconst pbkdf2Params = readSequenceChildren(pbkdf2Der);\n\tconst salt = pbkdf2Params[0];\n\tconst iterations = pbkdf2Params[1];\n\tif (salt === undefined || iterations === undefined || salt.tag !== 0x04) {\n\t\tthrow new Error('Malformed PBKDF2 params');\n\t}\n\tconst keyLengthElement = pbkdf2Params[2];\n\tconst hasExplicitKeyLength = keyLengthElement?.tag === 0x02;\n\tconst prfElement = hasExplicitKeyLength ? pbkdf2Params[3] : keyLengthElement;\n\tconst schemeDer = paramsDer.slice(scheme.start - scheme.headerLength, scheme.end);\n\tconst schemeChildren = readSequenceChildren(schemeDer);\n\tconst schemeOid = schemeChildren[0];\n\tconst iv = schemeChildren[1];\n\tif (schemeOid === undefined || iv === undefined || iv.tag !== 0x04) {\n\t\tthrow new Error('Malformed encryption scheme');\n\t}\n\tconst encryption = encryptionSchemeFromOid(decodeObjectIdentifier(schemeOid.value));\n\tif (encryption === undefined) {\n\t\tthrow new Error('Unsupported content encryption scheme');\n\t}\n\tif (keyLengthElement !== undefined && !hasExplicitKeyLength && keyLengthElement.tag !== 0x30) {\n\t\tthrow new Error('Malformed PBKDF2 params');\n\t}\n\tif (\n\t\thasExplicitKeyLength &&\n\t\tdecodeIntegerNumber(keyLengthElement.value) !== encryption.keyLengthBytes\n\t) {\n\t\tthrow new Error('Unsupported PBKDF2 key length');\n\t}\n\tconst prf = parsePbkdf2Prf(pbkdf2Der, prfElement);\n\n\t// Validate parsed parameters before returning\n\tconst iterationsValue = decodeIntegerNumber(iterations.value);\n\tconst saltValue = new Uint8Array(salt.value);\n\tconst ivValue = new Uint8Array(iv.value);\n\n\tif (iterationsValue < 1) {\n\t\tthrow new RangeError(`Invalid PBES2 iterations: must be >= 1, got ${iterationsValue}`);\n\t}\n\tif (saltValue.length < 8) {\n\t\tthrow new RangeError(`Invalid PBES2 salt: must be >= 8 bytes, got ${saltValue.length}`);\n\t}\n\tif (ivValue.length !== 16) {\n\t\tthrow new RangeError(`Invalid PBES2 IV: must be exactly 16 bytes, got ${ivValue.length}`);\n\t}\n\n\treturn {\n\t\tsalt: saltValue,\n\t\titerations: iterationsValue,\n\t\tiv: ivValue,\n\t\tencryption: encryption.name,\n\t\tprf,\n\t};\n}\n\n/** Derives an AES-CBC `CryptoKey` from a password via PBKDF2. */\nasync function deriveAesKey(\n\tpassword: string,\n\tsalt: Uint8Array,\n\titerations: number,\n\tencryptionName: Pbes2EncryptionScheme,\n\tprfName: Pbes2Prf,\n\tusages: KeyUsage[],\n): Promise<CryptoKey> {\n\tconst encryption = resolveEncryptionProfile(encryptionName);\n\tconst prf = resolvePrfProfile(prfName);\n\tconst passwordKey = await getCrypto().subtle.importKey(\n\t\t'raw',\n\t\tnew TextEncoder().encode(password),\n\t\t'PBKDF2',\n\t\tfalse,\n\t\t['deriveKey'],\n\t);\n\treturn getCrypto().subtle.deriveKey(\n\t\t{\n\t\t\tname: 'PBKDF2',\n\t\t\tsalt: toArrayBuffer(salt),\n\t\t\titerations,\n\t\t\thash: prf.hash,\n\t\t},\n\t\tpasswordKey,\n\t\t{ name: 'AES-CBC', length: encryption.keyLengthBits },\n\t\tfalse,\n\t\tusages,\n\t);\n}\n\n/** Parses the optional PRF AlgorithmIdentifier from PBKDF2 params. Absent means HMAC-SHA-1. */\nfunction parsePbkdf2Prf(\n\tpbkdf2Der: Uint8Array,\n\telement: ReturnType<typeof readSequenceChildren>[number] | undefined,\n): Pbes2Prf {\n\tif (element === undefined) {\n\t\treturn 'hmac-sha1';\n\t}\n\tif (element.tag !== 0x30) {\n\t\tthrow new Error('Malformed PBKDF2 PRF');\n\t}\n\tconst prfDer = readSequenceChildren(\n\t\tpbkdf2Der.slice(element.start - element.headerLength, element.end),\n\t);\n\tconst oid = prfDer[0];\n\tif (oid === undefined) {\n\t\tthrow new Error('Malformed PBKDF2 PRF');\n\t}\n\tconst prf = prfFromOid(decodeObjectIdentifier(oid.value));\n\tif (prf === undefined) {\n\t\tthrow new Error('Unsupported PBKDF2 PRF');\n\t}\n\treturn prf;\n}\n\n/** Maps an AES-CBC OID to the encryption profile, or `undefined` if unsupported. */\nfunction encryptionSchemeFromOid(oid: string):\n\t| {\n\t\t\treadonly name: Pbes2EncryptionScheme;\n\t\t\treadonly oid: string;\n\t\t\treadonly keyLengthBits: 128 | 192 | 256;\n\t\t\treadonly keyLengthBytes: 16 | 24 | 32;\n\t }\n\t| undefined {\n\tswitch (oid) {\n\t\tcase OIDS.aes128Cbc:\n\t\t\treturn { name: 'aes128-cbc', oid, keyLengthBits: 128, keyLengthBytes: 16 };\n\t\tcase OIDS.aes192Cbc:\n\t\t\treturn { name: 'aes192-cbc', oid, keyLengthBits: 192, keyLengthBytes: 24 };\n\t\tcase OIDS.aes256Cbc:\n\t\t\treturn { name: 'aes256-cbc', oid, keyLengthBits: 256, keyLengthBytes: 32 };\n\t}\n\treturn undefined;\n}\n\n/** Maps an HMAC OID to the PRF name, or `undefined` if unsupported. */\nfunction prfFromOid(oid: string): Pbes2Prf | undefined {\n\tswitch (oid) {\n\t\tcase OIDS.hmacWithSHA1:\n\t\t\treturn 'hmac-sha1';\n\t\tcase OIDS.hmacWithSHA256:\n\t\t\treturn 'hmac-sha256';\n\t}\n\treturn undefined;\n}\n\n/** Looks up the full encryption profile for a scheme name. Throws if unsupported. */\nfunction resolveEncryptionProfile(name: Pbes2EncryptionScheme): {\n\treadonly name: Pbes2EncryptionScheme;\n\treadonly oid: string;\n\treadonly keyLengthBits: 128 | 192 | 256;\n\treadonly keyLengthBytes: 16 | 24 | 32;\n} {\n\tconst profile = encryptionSchemeFromOid(\n\t\tname === 'aes128-cbc'\n\t\t\t? OIDS.aes128Cbc\n\t\t\t: name === 'aes192-cbc'\n\t\t\t\t? OIDS.aes192Cbc\n\t\t\t\t: OIDS.aes256Cbc,\n\t);\n\tif (profile === undefined) {\n\t\tthrow new Error('Unsupported content encryption scheme');\n\t}\n\treturn profile;\n}\n\n/** Looks up OID and WebCrypto hash name for a PRF. */\nfunction resolvePrfProfile(name: Pbes2Prf): {\n\treadonly oid: string;\n\treadonly hash: 'SHA-1' | 'SHA-256';\n} {\n\tswitch (name) {\n\t\tcase 'hmac-sha1':\n\t\t\treturn { oid: OIDS.hmacWithSHA1, hash: 'SHA-1' };\n\t\tcase 'hmac-sha256':\n\t\t\treturn { oid: OIDS.hmacWithSHA256, hash: 'SHA-256' };\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{explicitContext as e,integerFromNumber as t,nullValue as n,objectIdentifier as r,readRootElement as i,sequence as a}from"../asn1/der.js";import{childrenOf as o,decodeNonNegativeIntegerNumber as s,decodeObjectIdentifier as c,requireElement as l}from"../asn1/asn1.js";import{OIDS as u}from"../asn1/oids.js";function d(e){switch(e){case`SHA-256`:return{hash:`SHA-256`,mgfHash:`SHA-256`,saltLength:32,trailerField:1};case`SHA-384`:return{hash:`SHA-384`,mgfHash:`SHA-384`,saltLength:48,trailerField:1};case`SHA-512`:return{hash:`SHA-512`,mgfHash:`SHA-512`,saltLength:64,trailerField:1}}}function f(n){let i=y(n.hash);return a([e(0,v(i)),e(1,a([r(u.mgf1),v(i)])),e(2,t(n.saltLength)),...n.trailerField===1?[]:[e(3,t(n.trailerField))]])}function p(e){if(e===void 0)return x(`default_hash_sha1`);try{let t=i(e,{maxDepth:64});if(t.tag!==48)throw Error(`RSA-PSS parameters must be a SEQUENCE`);let n=o(e,t),r=u.sha1,a={oid:u.mgf1,hashOid:u.sha1},s=20,c=1,l=!1,f=!1,p=!1,v=!1;for(let t of n)switch(t.tag){case 160:if(l)throw Error(`RSA-PSS parameters contain duplicate hashAlgorithm`);r=g(e,m(e,t,`hashAlgorithm`),`hashAlgorithm`),l=!0;break;case 161:if(f)throw Error(`RSA-PSS parameters contain duplicate maskGenAlgorithm`);a=_(e,m(e,t,`maskGenAlgorithm`)),f=!0;break;case 162:if(p)throw Error(`RSA-PSS parameters contain duplicate saltLength`);s=h(e,t,`saltLength`,`RSA-PSS saltLength`),p=!0;break;case 163:if(v)throw Error(`RSA-PSS parameters contain duplicate trailerField`);c=h(e,t,`trailerField`,`RSA-PSS trailerField`),v=!0;break;default:throw Error(`RSA-PSS parameters contain unexpected field tag 0x${t.tag.toString(16)}`)}let y=b(r);if(y===void 0)return r===u.sha1?x(`default_hash_sha1`):x(`unsupported_hash`);if(a.oid!==u.mgf1)return x(`unsupported_mgf_algorithm`);let S=b(a.hashOid);if(S===void 0)return x(`unsupported_mgf_hash`);if(S!==y)return x(`mgf_hash_mismatch`);let C=d(y);return s===C.saltLength?c===1?{ok:!0,value:C}:x(`unsupported_trailer_field`):x(`unsupported_salt_length`)}catch(e){return{ok:!1,code:`malformed_rsa_pss_parameters`,reason:e instanceof Error?e.message:`Malformed RSA-PSS parameters`}}}function m(e,t,n){let r=o(e,t);if(r.length!==1)throw Error(`RSA-PSS ${n} must wrap exactly one value`);return l(r[0],`RSA-PSS ${n}`)}function h(e,t,n,r){let i=m(e,t,n);if(i.tag!==2)throw Error(`RSA-PSS ${n} must be an INTEGER`);return s(i.value,r)}function g(e,t,n){if(t.tag!==48)throw Error(`RSA-PSS ${n} must be a SEQUENCE`);let r=o(e,t);if(r.length===0||r.length>2)throw Error(`Malformed RSA-PSS ${n} AlgorithmIdentifier`);let i=l(r[0],`${n} OID`);if(i.tag!==6)throw Error(`Malformed RSA-PSS ${n} AlgorithmIdentifier`);if(r.length===2&&l(r[1],`${n} parameters`).tag!==5)throw Error(`RSA-PSS ${n} AlgorithmIdentifier parameters must be NULL when present`);return c(i.value)}function _(e,t){if(t.tag!==48)throw Error(`RSA-PSS maskGenAlgorithm must be a SEQUENCE`);let n=o(e,t);if(n.length===0||n.length>2)throw Error(`Malformed RSA-PSS maskGenAlgorithm AlgorithmIdentifier`);let r=l(n[0],`maskGenAlgorithm OID`);if(r.tag!==6)throw Error(`Malformed RSA-PSS maskGenAlgorithm AlgorithmIdentifier`);let i=c(r.value);return i===u.mgf1?{oid:i,hashOid:g(e,l(n[1],`maskGenAlgorithm parameters`),`MGF1 hashAlgorithm`)}:{oid:i}}function v(e){return a([r(e),n()])}function y(e){switch(e){case`SHA-256`:return u.sha256;case`SHA-384`:return u.sha384;case`SHA-512`:return u.sha512}}function b(e){switch(e){case u.sha256:return`SHA-256`;case u.sha384:return`SHA-384`;case u.sha512:return`SHA-512`;default:return}}function x(e){return{ok:!1,code:`unsupported_rsa_pss_parameters`,reason:e}}export{f as encodeRsaPssParameters,p as parseRsaPssParameters,d as rsaPssParametersForHash};
|
|
2
|
+
//# sourceMappingURL=rsa-pss.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rsa-pss.js","names":[],"sources":["../../../src/internal/crypto/rsa-pss.ts"],"sourcesContent":["/**\n * RSA-PSS `AlgorithmIdentifier` parameter parsing, validation, and encoding.\n *\n * Only the three hash-matched profiles (SHA-256/384/512 with MGF1 and matching salt\n * length) are accepted; SHA-1 defaults and mismatched MGF hashes are rejected.\n *\n * @module\n */\n\nimport {\n\tchildrenOf,\n\tdecodeNonNegativeIntegerNumber,\n\tdecodeObjectIdentifier,\n\trequireElement,\n} from '#micro509/internal/asn1/asn1.ts';\nimport {\n\tDEFAULT_MAX_DER_DEPTH,\n\texplicitContext,\n\tintegerFromNumber,\n\tnullValue,\n\tobjectIdentifier,\n\treadRootElement,\n\tsequence,\n} from '#micro509/internal/asn1/der.ts';\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\n\n/** Hash algorithm accepted for RSA-PSS signatures in this library. */\nexport type RsaPssHash = 'SHA-256' | 'SHA-384' | 'SHA-512';\n\n/**\n * One of the three supported RSA-PSS parameter profiles.\n *\n * Hash, MGF1 hash, and salt length are always matched (e.g. SHA-256 / SHA-256 / 32).\n */\nexport type RsaPssParameters =\n\t| {\n\t\t\t/** Message digest algorithm. */\n\t\t\treadonly hash: 'SHA-256';\n\t\t\t/** MGF1 hash — always matches `hash`. */\n\t\t\treadonly mgfHash: 'SHA-256';\n\t\t\t/** Salt length in bytes — equals the hash digest size. */\n\t\t\treadonly saltLength: 32;\n\t\t\t/** Trailer field (always 0xBC per RFC 3447). */\n\t\t\treadonly trailerField: 1;\n\t }\n\t| {\n\t\t\t/** Message digest algorithm. */\n\t\t\treadonly hash: 'SHA-384';\n\t\t\t/** MGF1 hash — always matches `hash`. */\n\t\t\treadonly mgfHash: 'SHA-384';\n\t\t\t/** Salt length in bytes — equals the hash digest size. */\n\t\t\treadonly saltLength: 48;\n\t\t\t/** Trailer field (always 0xBC per RFC 3447). */\n\t\t\treadonly trailerField: 1;\n\t }\n\t| {\n\t\t\t/** Message digest algorithm. */\n\t\t\treadonly hash: 'SHA-512';\n\t\t\t/** MGF1 hash — always matches `hash`. */\n\t\t\treadonly mgfHash: 'SHA-512';\n\t\t\t/** Salt length in bytes — equals the hash digest size. */\n\t\t\treadonly saltLength: 64;\n\t\t\t/** Trailer field (always 0xBC per RFC 3447). */\n\t\t\treadonly trailerField: 1;\n\t };\n\n/** Machine-readable reason why an RSA-PSS parameter set is not supported. */\nexport type UnsupportedRsaPssParametersReason =\n\t| 'default_hash_sha1'\n\t| 'unsupported_hash'\n\t| 'unsupported_mgf_algorithm'\n\t| 'unsupported_mgf_hash'\n\t| 'mgf_hash_mismatch'\n\t| 'unsupported_salt_length'\n\t| 'unsupported_trailer_field';\n\n/** Success branch of {@linkcode ParsedRsaPssParametersResult}. */\nexport interface ParsedRsaPssParametersSuccess {\n\t/** Discriminant for the success branch. */\n\treadonly ok: true;\n\t/** The validated RSA-PSS parameter profile. */\n\treadonly value: RsaPssParameters;\n}\n\n/** Failure: valid ASN.1 but an unsupported parameter combination. */\nexport interface ParsedRsaPssParametersUnsupported {\n\t/** Discriminant for the failure branch. */\n\treadonly ok: false;\n\t/** Machine-readable failure code. */\n\treadonly code: 'unsupported_rsa_pss_parameters';\n\t/** Which specific parameter is unsupported. */\n\treadonly reason: UnsupportedRsaPssParametersReason;\n}\n\n/** Failure: the DER encoding is structurally invalid. */\nexport interface ParsedRsaPssParametersMalformed {\n\t/** Discriminant for the failure branch. */\n\treadonly ok: false;\n\t/** Machine-readable failure code. */\n\treadonly code: 'malformed_rsa_pss_parameters';\n\t/** Human-readable description of the structural defect. */\n\treadonly reason: string;\n}\n\n/** Result of {@linkcode parseRsaPssParameters}: success, unsupported, or malformed. */\nexport type ParsedRsaPssParametersResult =\n\t| ParsedRsaPssParametersSuccess\n\t| ParsedRsaPssParametersUnsupported\n\t| ParsedRsaPssParametersMalformed;\n\n/** Parsed MGF AlgorithmIdentifier from an RSA-PSS parameter SEQUENCE. */\ninterface ParsedMaskGenAlgorithm {\n\t/** OID of the mask generation function (expected: id-mgf1). */\n\treadonly oid: string;\n\t/** OID of the hash algorithm used by MGF1, if present. */\n\treadonly hashOid?: string;\n}\n\n/** RFC 3447 default salt length when hash is SHA-1 (used to detect the unsupported default). */\nconst SHA1_SALT_LENGTH = 20;\n\n/** Return the canonical {@linkcode RsaPssParameters} profile for a given hash algorithm. */\nexport function rsaPssParametersForHash(hash: RsaPssHash): RsaPssParameters {\n\tswitch (hash) {\n\t\tcase 'SHA-256':\n\t\t\treturn {\n\t\t\t\thash: 'SHA-256',\n\t\t\t\tmgfHash: 'SHA-256',\n\t\t\t\tsaltLength: 32,\n\t\t\t\ttrailerField: 1,\n\t\t\t};\n\t\tcase 'SHA-384':\n\t\t\treturn {\n\t\t\t\thash: 'SHA-384',\n\t\t\t\tmgfHash: 'SHA-384',\n\t\t\t\tsaltLength: 48,\n\t\t\t\ttrailerField: 1,\n\t\t\t};\n\t\tcase 'SHA-512':\n\t\t\treturn {\n\t\t\t\thash: 'SHA-512',\n\t\t\t\tmgfHash: 'SHA-512',\n\t\t\t\tsaltLength: 64,\n\t\t\t\ttrailerField: 1,\n\t\t\t};\n\t}\n}\n\n/** DER-encode an {@linkcode RsaPssParameters} profile as an ASN.1 RSASSA-PSS-params SEQUENCE. */\nexport function encodeRsaPssParameters(parameters: RsaPssParameters): Uint8Array {\n\tconst hashOid = hashOidForName(parameters.hash);\n\tconst hashAlgorithmIdentifier = encodeHashAlgorithmIdentifier(hashOid);\n\treturn sequence([\n\t\texplicitContext(0, hashAlgorithmIdentifier),\n\t\texplicitContext(\n\t\t\t1,\n\t\t\tsequence([objectIdentifier(OIDS.mgf1), encodeHashAlgorithmIdentifier(hashOid)]),\n\t\t),\n\t\texplicitContext(2, integerFromNumber(parameters.saltLength)),\n\t\t...(parameters.trailerField === 1\n\t\t\t? []\n\t\t\t: [explicitContext(3, integerFromNumber(parameters.trailerField))]),\n\t]);\n}\n\n/**\n * Parse and validate DER-encoded RSASSA-PSS-params.\n *\n * Returns a typed result: success with a supported profile, unsupported with\n * a reason code, or malformed with an error message.\n */\nexport function parseRsaPssParameters(\n\tparametersDer: Uint8Array | undefined,\n): ParsedRsaPssParametersResult {\n\tif (parametersDer === undefined) {\n\t\treturn unsupportedResult('default_hash_sha1');\n\t}\n\n\ttry {\n\t\tconst element = readRootElement(parametersDer, { maxDepth: DEFAULT_MAX_DER_DEPTH });\n\t\tif (element.tag !== 0x30) {\n\t\t\tthrow new Error('RSA-PSS parameters must be a SEQUENCE');\n\t\t}\n\t\tconst children = childrenOf(parametersDer, element);\n\t\tlet hashOid: string = OIDS.sha1;\n\t\tlet maskGenAlgorithm: ParsedMaskGenAlgorithm = {\n\t\t\toid: OIDS.mgf1,\n\t\t\thashOid: OIDS.sha1,\n\t\t};\n\t\tlet saltLength = SHA1_SALT_LENGTH;\n\t\tlet trailerField = 1;\n\t\tlet sawHash = false;\n\t\tlet sawMaskGen = false;\n\t\tlet sawSaltLength = false;\n\t\tlet sawTrailerField = false;\n\n\t\tfor (const child of children) {\n\t\t\tswitch (child.tag) {\n\t\t\t\tcase 0xa0:\n\t\t\t\t\tif (sawHash) {\n\t\t\t\t\t\tthrow new Error('RSA-PSS parameters contain duplicate hashAlgorithm');\n\t\t\t\t\t}\n\t\t\t\t\thashOid = parseHashAlgorithmIdentifier(\n\t\t\t\t\t\tparametersDer,\n\t\t\t\t\t\trequireSingleExplicitChild(parametersDer, child, 'hashAlgorithm'),\n\t\t\t\t\t\t'hashAlgorithm',\n\t\t\t\t\t);\n\t\t\t\t\tsawHash = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0xa1:\n\t\t\t\t\tif (sawMaskGen) {\n\t\t\t\t\t\tthrow new Error('RSA-PSS parameters contain duplicate maskGenAlgorithm');\n\t\t\t\t\t}\n\t\t\t\t\tmaskGenAlgorithm = parseMaskGenAlgorithmIdentifier(\n\t\t\t\t\t\tparametersDer,\n\t\t\t\t\t\trequireSingleExplicitChild(parametersDer, child, 'maskGenAlgorithm'),\n\t\t\t\t\t);\n\t\t\t\t\tsawMaskGen = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0xa2:\n\t\t\t\t\tif (sawSaltLength) {\n\t\t\t\t\t\tthrow new Error('RSA-PSS parameters contain duplicate saltLength');\n\t\t\t\t\t}\n\t\t\t\t\tsaltLength = parseExplicitInteger(\n\t\t\t\t\t\tparametersDer,\n\t\t\t\t\t\tchild,\n\t\t\t\t\t\t'saltLength',\n\t\t\t\t\t\t'RSA-PSS saltLength',\n\t\t\t\t\t);\n\t\t\t\t\tsawSaltLength = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0xa3:\n\t\t\t\t\tif (sawTrailerField) {\n\t\t\t\t\t\tthrow new Error('RSA-PSS parameters contain duplicate trailerField');\n\t\t\t\t\t}\n\t\t\t\t\ttrailerField = parseExplicitInteger(\n\t\t\t\t\t\tparametersDer,\n\t\t\t\t\t\tchild,\n\t\t\t\t\t\t'trailerField',\n\t\t\t\t\t\t'RSA-PSS trailerField',\n\t\t\t\t\t);\n\t\t\t\t\tsawTrailerField = true;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`RSA-PSS parameters contain unexpected field tag 0x${child.tag.toString(16)}`,\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst supportedHash = hashNameFromOid(hashOid);\n\t\tif (supportedHash === undefined) {\n\t\t\tif (hashOid === OIDS.sha1) {\n\t\t\t\treturn unsupportedResult('default_hash_sha1');\n\t\t\t}\n\t\t\treturn unsupportedResult('unsupported_hash');\n\t\t}\n\t\tif (maskGenAlgorithm.oid !== OIDS.mgf1) {\n\t\t\treturn unsupportedResult('unsupported_mgf_algorithm');\n\t\t}\n\t\tconst mgfHash = hashNameFromOid(maskGenAlgorithm.hashOid);\n\t\tif (mgfHash === undefined) {\n\t\t\treturn unsupportedResult('unsupported_mgf_hash');\n\t\t}\n\t\tif (mgfHash !== supportedHash) {\n\t\t\treturn unsupportedResult('mgf_hash_mismatch');\n\t\t}\n\t\tconst supported = rsaPssParametersForHash(supportedHash);\n\t\tif (saltLength !== supported.saltLength) {\n\t\t\treturn unsupportedResult('unsupported_salt_length');\n\t\t}\n\t\tif (trailerField !== 1) {\n\t\t\treturn unsupportedResult('unsupported_trailer_field');\n\t\t}\n\t\treturn { ok: true, value: supported };\n\t} catch (error) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tcode: 'malformed_rsa_pss_parameters',\n\t\t\treason: error instanceof Error ? error.message : 'Malformed RSA-PSS parameters',\n\t\t};\n\t}\n}\n\n/** Unwrap an explicit context tag and assert it contains exactly one child element. */\nfunction requireSingleExplicitChild(\n\tsource: Uint8Array,\n\telement: ReturnType<typeof readRootElement>,\n\tlabel: string,\n): ReturnType<typeof readRootElement> {\n\tconst children = childrenOf(source, element);\n\tif (children.length !== 1) {\n\t\tthrow new Error(`RSA-PSS ${label} must wrap exactly one value`);\n\t}\n\treturn requireElement(children[0], `RSA-PSS ${label}`);\n}\n\n/** Extract a non-negative integer from an explicit context-tagged wrapper. */\nfunction parseExplicitInteger(\n\tsource: Uint8Array,\n\telement: ReturnType<typeof readRootElement>,\n\tlabel: string,\n\tintegerLabel: string,\n): number {\n\tconst integerElement = requireSingleExplicitChild(source, element, label);\n\tif (integerElement.tag !== 0x02) {\n\t\tthrow new Error(`RSA-PSS ${label} must be an INTEGER`);\n\t}\n\treturn decodeNonNegativeIntegerNumber(integerElement.value, integerLabel);\n}\n\n/** Parse an AlgorithmIdentifier SEQUENCE and return the algorithm OID. */\nfunction parseHashAlgorithmIdentifier(\n\tsource: Uint8Array,\n\telement: ReturnType<typeof readRootElement>,\n\tlabel: string,\n): string {\n\tif (element.tag !== 0x30) {\n\t\tthrow new Error(`RSA-PSS ${label} must be a SEQUENCE`);\n\t}\n\tconst children = childrenOf(source, element);\n\tif (children.length === 0 || children.length > 2) {\n\t\tthrow new Error(`Malformed RSA-PSS ${label} AlgorithmIdentifier`);\n\t}\n\tconst oid = requireElement(children[0], `${label} OID`);\n\tif (oid.tag !== 0x06) {\n\t\tthrow new Error(`Malformed RSA-PSS ${label} AlgorithmIdentifier`);\n\t}\n\tif (children.length === 2 && requireElement(children[1], `${label} parameters`).tag !== 0x05) {\n\t\tthrow new Error(`RSA-PSS ${label} AlgorithmIdentifier parameters must be NULL when present`);\n\t}\n\treturn decodeObjectIdentifier(oid.value);\n}\n\n/** Parse an MGF AlgorithmIdentifier SEQUENCE into OID and optional inner hash OID. */\nfunction parseMaskGenAlgorithmIdentifier(\n\tsource: Uint8Array,\n\telement: ReturnType<typeof readRootElement>,\n): ParsedMaskGenAlgorithm {\n\tif (element.tag !== 0x30) {\n\t\tthrow new Error('RSA-PSS maskGenAlgorithm must be a SEQUENCE');\n\t}\n\tconst children = childrenOf(source, element);\n\tif (children.length === 0 || children.length > 2) {\n\t\tthrow new Error('Malformed RSA-PSS maskGenAlgorithm AlgorithmIdentifier');\n\t}\n\tconst oidElement = requireElement(children[0], 'maskGenAlgorithm OID');\n\tif (oidElement.tag !== 0x06) {\n\t\tthrow new Error('Malformed RSA-PSS maskGenAlgorithm AlgorithmIdentifier');\n\t}\n\tconst oid = decodeObjectIdentifier(oidElement.value);\n\tif (oid !== OIDS.mgf1) {\n\t\treturn { oid };\n\t}\n\tconst parameters = requireElement(children[1], 'maskGenAlgorithm parameters');\n\treturn { oid, hashOid: parseHashAlgorithmIdentifier(source, parameters, 'MGF1 hashAlgorithm') };\n}\n\n/** DER-encode a hash AlgorithmIdentifier as `SEQUENCE { OID, NULL }`. */\nfunction encodeHashAlgorithmIdentifier(oid: string): Uint8Array {\n\treturn sequence([objectIdentifier(oid), nullValue()]);\n}\n\n/** Map a hash algorithm name to its ASN.1 OID string. */\nfunction hashOidForName(hash: RsaPssHash): string {\n\tswitch (hash) {\n\t\tcase 'SHA-256':\n\t\t\treturn OIDS.sha256;\n\t\tcase 'SHA-384':\n\t\t\treturn OIDS.sha384;\n\t\tcase 'SHA-512':\n\t\t\treturn OIDS.sha512;\n\t}\n}\n\n/** Reverse-map a hash OID to a supported {@linkcode RsaPssHash} name, or `undefined` if unsupported. */\nfunction hashNameFromOid(oid: string | undefined): RsaPssHash | undefined {\n\tswitch (oid) {\n\t\tcase OIDS.sha256:\n\t\t\treturn 'SHA-256';\n\t\tcase OIDS.sha384:\n\t\t\treturn 'SHA-384';\n\t\tcase OIDS.sha512:\n\t\t\treturn 'SHA-512';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/** Build an unsupported-parameters failure result. */\nfunction unsupportedResult(\n\treason: UnsupportedRsaPssParametersReason,\n): ParsedRsaPssParametersUnsupported {\n\treturn {\n\t\tok: false,\n\t\tcode: 'unsupported_rsa_pss_parameters',\n\t\treason,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{readElement as e}from"../asn1/der.js";import{toArrayBuffer as t}from"../asn1/asn1.js";import{OIDS as n}from"../asn1/oids.js";import{getCrypto as r}from"./webcrypto.js";import{importSpkiDer as i}from"../../keys/keys.js";import{parseRsaPssParameters as a}from"./rsa-pss.js";import{alternateEcdsaSignatureEncoding as o}from"./ecdsa.js";function s(e,t,r,i,a=`issuer`){switch(e){case n.sha256WithRSAEncryption:{let e=f(t,`SHA-256 with RSA`);return e===void 0?c(r,`SHA-256`,`pkcs1-v1_5`,a):e}case n.sha384WithRSAEncryption:{let e=f(t,`SHA-384 with RSA`);return e===void 0?c(r,`SHA-384`,`pkcs1-v1_5`,a):e}case n.sha512WithRSAEncryption:{let e=f(t,`SHA-512 with RSA`);return e===void 0?c(r,`SHA-512`,`pkcs1-v1_5`,a):e}case n.rsassaPss:return d(t,r,a);case n.ecdsaWithSHA256:{let e=p(t,`ECDSA with SHA-256`);return e===void 0?l(r,i,`SHA-256`,a):e}case n.ecdsaWithSHA384:{let e=p(t,`ECDSA with SHA-384`);return e===void 0?l(r,i,`SHA-384`,a):e}case n.ecdsaWithSHA512:{let e=p(t,`ECDSA with SHA-512`);return e===void 0?l(r,i,`SHA-512`,a):e}case n.ed25519:{let e=p(t,`Ed25519`);return e===void 0?r===n.ed25519?m({importAlgorithm:{kind:`ed25519`},verifyParams:{name:`Ed25519`}}):h(`Ed25519`,`requires Ed25519 ${a} public key`):e}default:return h(e,`unrecognized signature algorithm OID`)}}function c(e,t,r=`pkcs1-v1_5`,i=`issuer`,a){return e===n.rsaEncryption?m({importAlgorithm:{kind:`rsa`,hash:t,scheme:r},verifyParams:r===`pss`?{name:`RSA-PSS`,saltLength:a??0}:{name:`RSASSA-PKCS1-v1_5`}}):h(`RSA`,`requires RSA ${i} public key`)}function l(e,t,r,i=`issuer`){if(e!==n.ecPublicKey)return h(`ECDSA`,`requires EC ${i} public key`);switch(t){case n.prime256v1:return m({importAlgorithm:{kind:`ecdsa`,curve:`P-256`},verifyParams:{name:`ECDSA`,hash:r},ecdsaRawSignatureBytes:64});case n.secp384r1:return m({importAlgorithm:{kind:`ecdsa`,curve:`P-384`},verifyParams:{name:`ECDSA`,hash:r},ecdsaRawSignatureBytes:96});case n.secp521r1:return m({importAlgorithm:{kind:`ecdsa`,curve:`P-521`},verifyParams:{name:`ECDSA`,hash:r},ecdsaRawSignatureBytes:132});default:return h(`ECDSA`,`unsupported EC curve OID: ${t??`missing`}`)}}async function u(e,n,a,c,l,u,d){let f=s(e,n,a,c);if(!f.ok)return f;try{let e=await i(l,f.value.importAlgorithm),n=r().subtle,a=t(u),s=t(d);if(await n.verify(f.value.verifyParams,e,a,s))return{ok:!0,valid:!0};if(f.value.ecdsaRawSignatureBytes!==void 0){let r=o(u,f.value.ecdsaRawSignatureBytes/2);if(r!==void 0)return{ok:!0,valid:await n.verify(f.value.verifyParams,e,t(r),s)}}return{ok:!0,valid:!1}}catch(e){return g(e instanceof Error?e.message:`signature verification failed`)}}function d(e,t,n){let r=a(e);return r.ok?c(t,r.value.hash,`pss`,n,r.value.saltLength):h(`RSA-PSS`,r.reason)}function f(t,n){if(t===void 0)return h(n,`signature AlgorithmIdentifier parameters must be DER NULL`);try{let r=e(t);return r.tag!==5||r.length!==0||r.end!==t.length?h(n,`signature AlgorithmIdentifier parameters must be DER NULL`):void 0}catch{return h(n,`signature AlgorithmIdentifier parameters must be DER NULL`)}}function p(e,t){if(e!==void 0)return h(t,`signature AlgorithmIdentifier parameters must be absent`)}function m(e){return{ok:!0,value:e}}function h(e,t){return{ok:!1,code:`unsupported_signature_algorithm_parameters`,reason:`${e} parameters unsupported: ${t}`}}function g(e){return{ok:!1,code:`verification_error`,reason:e}}export{s as getVerifySignatureConfigResult,l as requireEcPublicKey,c as requireRsaPublicKey,u as verifySignedDataDetailed};
|
|
2
|
+
//# sourceMappingURL=sig-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sig-verify.js","names":[],"sources":["../../../src/internal/crypto/sig-verify.ts"],"sourcesContent":["/**\n * Signature-verification helpers that resolve parsed algorithm OIDs into WebCrypto\n * verification configuration for higher-level validators.\n *\n * @module\n */\n\nimport { toArrayBuffer } from '#micro509/internal/asn1/asn1.ts';\nimport { readElement } from '#micro509/internal/asn1/der.ts';\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\nimport type { PublicKeyImportInput, RsaHash, RsaScheme } from '#micro509/keys/keys.ts';\nimport { importSpkiDer } from '#micro509/keys/keys.ts';\nimport { alternateEcdsaSignatureEncoding } from './ecdsa.ts';\nimport { parseRsaPssParameters } from './rsa-pss.ts';\nimport { getCrypto } from './webcrypto.ts';\n\nexport {\n\talternateEcdsaSignatureEncoding,\n\tconcatFixedWidth,\n\tderEcdsaSignatureToRaw,\n\trawEcdsaSignatureToDer,\n} from './ecdsa.ts';\n\n/** Resolved WebCrypto parameters needed to verify a signature. */\nexport interface VerifySignatureConfig {\n\t/** Algorithm descriptor for importing the signer's public key via {@linkcode importSpkiDer}. */\n\treadonly importAlgorithm: PublicKeyImportInput;\n\t/** WebCrypto `verify()` algorithm parameter. */\n\treadonly verifyParams: Algorithm | EcdsaParams | RsaPssParams;\n\t/** When set, raw ECDSA signatures are this many bytes and may need DER/raw conversion. */\n\treadonly ecdsaRawSignatureBytes?: number;\n}\n\n/** Failure: the signature algorithm or its parameters are not supported. */\nexport interface VerifySignatureConfigFailure {\n\t/** Discriminant for the failure branch. */\n\treadonly ok: false;\n\t/** Machine-readable failure code. */\n\treadonly code: 'unsupported_signature_algorithm_parameters';\n\t/** Human-readable explanation of why the algorithm is unsupported. */\n\treadonly reason: string;\n}\n\n/** Failure: signature verification could not run to completion. */\nexport interface VerifySignedDataFailure {\n\t/** Discriminant for the failure branch. */\n\treadonly ok: false;\n\t/** Machine-readable failure code. */\n\treadonly code: 'verification_error';\n\t/** Human-readable explanation of the verification failure. */\n\treadonly reason: string;\n}\n\n/** Success branch of {@linkcode VerifySignatureConfigResult}. */\ninterface VerifySignatureConfigSuccess {\n\t/** Discriminant for the success branch. */\n\treadonly ok: true;\n\t/** The resolved verification configuration. */\n\treadonly value: VerifySignatureConfig;\n}\n\n/** Result of resolving signature algorithm OIDs into a {@linkcode VerifySignatureConfig}. */\nexport type VerifySignatureConfigResult =\n\t| VerifySignatureConfigSuccess\n\t| VerifySignatureConfigFailure;\n\n/** Success branch of {@linkcode VerifySignedDataResult}. */\ninterface VerifySignedDataSuccess {\n\t/** Discriminant for the success branch. */\n\treadonly ok: true;\n\t/** Whether the cryptographic signature is valid for the given data and key. */\n\treadonly valid: boolean;\n}\n\n/** Result of a full signature verification: validity, unsupported params, or a runtime verification failure. */\nexport type VerifySignedDataResult =\n\t| VerifySignedDataSuccess\n\t| VerifySignatureConfigFailure\n\t| VerifySignedDataFailure;\n\n/**\n * Resolve algorithm OIDs into a {@linkcode VerifySignatureConfig}.\n *\n * Throws on unsupported algorithms. Use {@linkcode getVerifySignatureConfigResult} for\n * a non-throwing variant.\n */\nexport function getVerifySignatureConfig(\n\tsignatureAlgorithmOid: string,\n\tsignatureAlgorithmParametersDer: Uint8Array | undefined,\n\tpublicKeyAlgorithmOid: string,\n\tpublicKeyParametersOid: string | undefined,\n\tcontext = 'issuer',\n): VerifySignatureConfig {\n\tconst result = getVerifySignatureConfigResult(\n\t\tsignatureAlgorithmOid,\n\t\tsignatureAlgorithmParametersDer,\n\t\tpublicKeyAlgorithmOid,\n\t\tpublicKeyParametersOid,\n\t\tcontext,\n\t);\n\tif (!result.ok) {\n\t\tthrow new Error(result.reason);\n\t}\n\treturn result.value;\n}\n\n/** Non-throwing variant of {@linkcode getVerifySignatureConfig} — returns a typed result union. */\nexport function getVerifySignatureConfigResult(\n\tsignatureAlgorithmOid: string,\n\tsignatureAlgorithmParametersDer: Uint8Array | undefined,\n\tpublicKeyAlgorithmOid: string,\n\tpublicKeyParametersOid: string | undefined,\n\tcontext = 'issuer',\n): VerifySignatureConfigResult {\n\tswitch (signatureAlgorithmOid) {\n\t\tcase OIDS.sha256WithRSAEncryption: {\n\t\t\tconst parameters = requireDerNullSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'SHA-256 with RSA',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireRsaPublicKey(publicKeyAlgorithmOid, 'SHA-256', 'pkcs1-v1_5', context);\n\t\t}\n\t\tcase OIDS.sha384WithRSAEncryption: {\n\t\t\tconst parameters = requireDerNullSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'SHA-384 with RSA',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireRsaPublicKey(publicKeyAlgorithmOid, 'SHA-384', 'pkcs1-v1_5', context);\n\t\t}\n\t\tcase OIDS.sha512WithRSAEncryption: {\n\t\t\tconst parameters = requireDerNullSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'SHA-512 with RSA',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireRsaPublicKey(publicKeyAlgorithmOid, 'SHA-512', 'pkcs1-v1_5', context);\n\t\t}\n\t\tcase OIDS.rsassaPss:\n\t\t\treturn requireRsaPssVerifyConfig(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\tpublicKeyAlgorithmOid,\n\t\t\t\tcontext,\n\t\t\t);\n\t\tcase OIDS.ecdsaWithSHA256: {\n\t\t\tconst parameters = requireAbsentSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'ECDSA with SHA-256',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireEcPublicKey(publicKeyAlgorithmOid, publicKeyParametersOid, 'SHA-256', context);\n\t\t}\n\t\tcase OIDS.ecdsaWithSHA384: {\n\t\t\tconst parameters = requireAbsentSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'ECDSA with SHA-384',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireEcPublicKey(publicKeyAlgorithmOid, publicKeyParametersOid, 'SHA-384', context);\n\t\t}\n\t\tcase OIDS.ecdsaWithSHA512: {\n\t\t\tconst parameters = requireAbsentSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'ECDSA with SHA-512',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\treturn requireEcPublicKey(publicKeyAlgorithmOid, publicKeyParametersOid, 'SHA-512', context);\n\t\t}\n\t\tcase OIDS.ed25519: {\n\t\t\tconst parameters = requireAbsentSignatureAlgorithmParameters(\n\t\t\t\tsignatureAlgorithmParametersDer,\n\t\t\t\t'Ed25519',\n\t\t\t);\n\t\t\tif (parameters !== undefined) {\n\t\t\t\treturn parameters;\n\t\t\t}\n\t\t\tif (publicKeyAlgorithmOid !== OIDS.ed25519) {\n\t\t\t\treturn unsupported('Ed25519', `requires Ed25519 ${context} public key`);\n\t\t\t}\n\t\t\treturn ok({\n\t\t\t\timportAlgorithm: { kind: 'ed25519' },\n\t\t\t\tverifyParams: { name: 'Ed25519' },\n\t\t\t});\n\t\t}\n\t\tdefault:\n\t\t\treturn unsupported(signatureAlgorithmOid, 'unrecognized signature algorithm OID');\n\t}\n}\n\n/** Validate that the public key OID is `rsaEncryption` and return an RSA verification config result. */\nexport function requireRsaPublicKey(\n\talgorithmOid: string,\n\thash: RsaHash,\n\tscheme: RsaScheme = 'pkcs1-v1_5',\n\tcontext = 'issuer',\n\tsaltLength?: number,\n): VerifySignatureConfigResult {\n\tif (algorithmOid !== OIDS.rsaEncryption) {\n\t\treturn unsupported('RSA', `requires RSA ${context} public key`);\n\t}\n\tconst verifyParams: Algorithm | RsaPssParams =\n\t\tscheme === 'pss'\n\t\t\t? { name: 'RSA-PSS', saltLength: saltLength ?? 0 }\n\t\t\t: { name: 'RSASSA-PKCS1-v1_5' };\n\treturn ok({ importAlgorithm: { kind: 'rsa', hash, scheme }, verifyParams });\n}\n\n/** Validate that the public key OID is `ecPublicKey`, resolve the curve, and return an ECDSA verification config result. */\nexport function requireEcPublicKey(\n\talgorithmOid: string,\n\tparametersOid: string | undefined,\n\thash: string,\n\tcontext = 'issuer',\n): VerifySignatureConfigResult {\n\tif (algorithmOid !== OIDS.ecPublicKey) {\n\t\treturn unsupported('ECDSA', `requires EC ${context} public key`);\n\t}\n\tswitch (parametersOid) {\n\t\tcase OIDS.prime256v1:\n\t\t\treturn ok({\n\t\t\t\timportAlgorithm: { kind: 'ecdsa', curve: 'P-256' },\n\t\t\t\tverifyParams: { name: 'ECDSA', hash },\n\t\t\t\tecdsaRawSignatureBytes: 64,\n\t\t\t});\n\t\tcase OIDS.secp384r1:\n\t\t\treturn ok({\n\t\t\t\timportAlgorithm: { kind: 'ecdsa', curve: 'P-384' },\n\t\t\t\tverifyParams: { name: 'ECDSA', hash },\n\t\t\t\tecdsaRawSignatureBytes: 96,\n\t\t\t});\n\t\tcase OIDS.secp521r1:\n\t\t\treturn ok({\n\t\t\t\timportAlgorithm: { kind: 'ecdsa', curve: 'P-521' },\n\t\t\t\tverifyParams: { name: 'ECDSA', hash },\n\t\t\t\tecdsaRawSignatureBytes: 132,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn unsupported('ECDSA', `unsupported EC curve OID: ${parametersOid ?? 'missing'}`);\n\t}\n}\n\n/** Return the raw ECDSA signature byte count (r + s) for a given curve OID, if supported. */\nexport function curveBytes(parametersOid: string | undefined): number | undefined {\n\tswitch (parametersOid) {\n\t\tcase OIDS.prime256v1:\n\t\t\treturn 64;\n\t\tcase OIDS.secp384r1:\n\t\t\treturn 96;\n\t\tcase OIDS.secp521r1:\n\t\t\treturn 132;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Verify a signature against the signer's SPKI and the signed TBS bytes.\n *\n * Throws on unsupported algorithms. Use {@linkcode verifySignedDataDetailed} for\n * a non-throwing variant.\n */\nexport async function verifySignedData(\n\tsignatureAlgorithmOid: string,\n\tsignatureAlgorithmParametersDer: Uint8Array | undefined,\n\tpublicKeyAlgorithmOid: string,\n\tpublicKeyParametersOid: string | undefined,\n\tsubjectPublicKeyInfoDer: Uint8Array,\n\tsignature: Uint8Array,\n\tsignedData: Uint8Array,\n): Promise<boolean> {\n\tconst result = await verifySignedDataDetailed(\n\t\tsignatureAlgorithmOid,\n\t\tsignatureAlgorithmParametersDer,\n\t\tpublicKeyAlgorithmOid,\n\t\tpublicKeyParametersOid,\n\t\tsubjectPublicKeyInfoDer,\n\t\tsignature,\n\t\tsignedData,\n\t);\n\tif (!result.ok) {\n\t\tthrow new Error(result.reason);\n\t}\n\treturn result.valid;\n}\n\n/**\n * Non-throwing variant of {@linkcode verifySignedData} — returns a typed\n * {@linkcode VerifySignedDataResult} instead of throwing on unsupported algorithms\n * or runtime verification failures.\n *\n * Tries both DER and raw ECDSA encodings when the first attempt fails.\n */\nexport async function verifySignedDataDetailed(\n\tsignatureAlgorithmOid: string,\n\tsignatureAlgorithmParametersDer: Uint8Array | undefined,\n\tpublicKeyAlgorithmOid: string,\n\tpublicKeyParametersOid: string | undefined,\n\tsubjectPublicKeyInfoDer: Uint8Array,\n\tsignature: Uint8Array,\n\tsignedData: Uint8Array,\n): Promise<VerifySignedDataResult> {\n\tconst config = getVerifySignatureConfigResult(\n\t\tsignatureAlgorithmOid,\n\t\tsignatureAlgorithmParametersDer,\n\t\tpublicKeyAlgorithmOid,\n\t\tpublicKeyParametersOid,\n\t);\n\tif (!config.ok) {\n\t\treturn config;\n\t}\n\ttry {\n\t\tconst key = await importSpkiDer(subjectPublicKeyInfoDer, config.value.importAlgorithm);\n\t\tconst subtle = getCrypto().subtle;\n\t\tconst signatureView = toArrayBuffer(signature);\n\t\tconst dataView = toArrayBuffer(signedData);\n\t\tif (await subtle.verify(config.value.verifyParams, key, signatureView, dataView)) {\n\t\t\treturn { ok: true, valid: true };\n\t\t}\n\t\tif (config.value.ecdsaRawSignatureBytes !== undefined) {\n\t\t\tconst alternate = alternateEcdsaSignatureEncoding(\n\t\t\t\tsignature,\n\t\t\t\tconfig.value.ecdsaRawSignatureBytes / 2,\n\t\t\t);\n\t\t\tif (alternate !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tok: true,\n\t\t\t\t\tvalid: await subtle.verify(\n\t\t\t\t\t\tconfig.value.verifyParams,\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\ttoArrayBuffer(alternate),\n\t\t\t\t\t\tdataView,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn { ok: true, valid: false };\n\t} catch (error) {\n\t\treturn verificationError(\n\t\t\terror instanceof Error ? error.message : 'signature verification failed',\n\t\t);\n\t}\n}\n\n/** Parse RSA-PSS algorithm parameters and build a verification config, or return failure. */\nfunction requireRsaPssVerifyConfig(\n\tsignatureAlgorithmParametersDer: Uint8Array | undefined,\n\tpublicKeyAlgorithmOid: string,\n\tcontext: string,\n): VerifySignatureConfigResult {\n\tconst parameters = parseRsaPssParameters(signatureAlgorithmParametersDer);\n\tif (!parameters.ok) {\n\t\treturn unsupported('RSA-PSS', parameters.reason);\n\t}\n\treturn requireRsaPublicKey(\n\t\tpublicKeyAlgorithmOid,\n\t\tparameters.value.hash,\n\t\t'pss',\n\t\tcontext,\n\t\tparameters.value.saltLength,\n\t);\n}\n\nfunction requireDerNullSignatureAlgorithmParameters(\n\tparametersDer: Uint8Array | undefined,\n\talgorithm: string,\n): VerifySignatureConfigFailure | undefined {\n\tif (parametersDer === undefined) {\n\t\treturn unsupported(algorithm, 'signature AlgorithmIdentifier parameters must be DER NULL');\n\t}\n\ttry {\n\t\tconst element = readElement(parametersDer);\n\t\tif (element.tag !== 0x05 || element.length !== 0 || element.end !== parametersDer.length) {\n\t\t\treturn unsupported(algorithm, 'signature AlgorithmIdentifier parameters must be DER NULL');\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn unsupported(algorithm, 'signature AlgorithmIdentifier parameters must be DER NULL');\n\t}\n}\n\nfunction requireAbsentSignatureAlgorithmParameters(\n\tparametersDer: Uint8Array | undefined,\n\talgorithm: string,\n): VerifySignatureConfigFailure | undefined {\n\tif (parametersDer !== undefined) {\n\t\treturn unsupported(algorithm, 'signature AlgorithmIdentifier parameters must be absent');\n\t}\n\treturn undefined;\n}\n\n/** Wrap a config in a success result. */\nfunction ok(value: VerifySignatureConfig): VerifySignatureConfigSuccess {\n\treturn { ok: true, value };\n}\n\n/** Build an unsupported-algorithm failure result. */\nfunction unsupported(algorithm: string, reason: string): VerifySignatureConfigFailure {\n\treturn {\n\t\tok: false,\n\t\tcode: 'unsupported_signature_algorithm_parameters',\n\t\treason: `${algorithm} parameters unsupported: ${reason}`,\n\t};\n}\n\nfunction verificationError(reason: string): VerifySignedDataFailure {\n\treturn {\n\t\tok: false,\n\t\tcode: 'verification_error',\n\t\treason,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/internal/crypto/signing.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Controls how the signature algorithm is chosen.
|
|
4
|
+
*
|
|
5
|
+
* `'auto'` (default) infers the algorithm from the key. `'rsa-pss'` forces RSA-PSS
|
|
6
|
+
* padding and requires an RSA-PSS private key.
|
|
7
|
+
*/
|
|
8
|
+
type SignatureProfileInput = {
|
|
9
|
+
/** Infer the signature algorithm from the private key. */readonly kind?: "auto";
|
|
10
|
+
} | {
|
|
11
|
+
/** Force RSA-PSS padding. */readonly kind: "rsa-pss"; /** Salt length in bytes. Must match the key's hash digest size. */
|
|
12
|
+
readonly saltLength?: 32 | 48 | 64;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { SignatureProfileInput };
|
|
16
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{nullValue as e,objectIdentifier as t,sequence as n}from"../asn1/der.js";import{OIDS as r}from"../asn1/oids.js";import{getCrypto as i}from"./webcrypto.js";import{encodeRsaPssParameters as a,rsaPssParametersForHash as o}from"./rsa-pss.js";import{rawEcdsaSignatureToDer as s}from"./ecdsa.js";function c(t,n={}){let i=t.algorithm;if(n.kind===`rsa-pss`)return l(t,n.saltLength);if(i.name===`RSASSA-PKCS1-v1_5`){if(!f(i))throw Error(`RSA key is missing hash metadata`);switch(i.hash.name){case`SHA-256`:return{algorithmOid:r.sha256WithRSAEncryption,parameters:e(),signParams:{name:`RSASSA-PKCS1-v1_5`}};case`SHA-384`:return{algorithmOid:r.sha384WithRSAEncryption,parameters:e(),signParams:{name:`RSASSA-PKCS1-v1_5`}};case`SHA-512`:return{algorithmOid:r.sha512WithRSAEncryption,parameters:e(),signParams:{name:`RSASSA-PKCS1-v1_5`}};default:throw Error(`Unsupported RSA hash: ${i.hash.name}`)}}if(i.name===`RSA-PSS`)throw Error(`RSA-PSS signing requires an explicit signature profile`);if(i.name===`ECDSA`){if(!p(i))throw Error(`ECDSA key is missing namedCurve metadata`);switch(i.namedCurve){case`P-256`:return{algorithmOid:r.ecdsaWithSHA256,signParams:{name:`ECDSA`,hash:`SHA-256`},ecdsaRawSignatureBytes:64};case`P-384`:return{algorithmOid:r.ecdsaWithSHA384,signParams:{name:`ECDSA`,hash:`SHA-384`},ecdsaRawSignatureBytes:96};case`P-521`:return{algorithmOid:r.ecdsaWithSHA512,signParams:{name:`ECDSA`,hash:`SHA-512`},ecdsaRawSignatureBytes:132};default:throw Error(`Unsupported curve: ${i.namedCurve}`)}}if(i.name===`Ed25519`)return{algorithmOid:r.ed25519,signParams:{name:`Ed25519`}};throw Error(`Unsupported signing key algorithm: ${i.name}`)}function l(e,t){let n=e.algorithm;if(n.name!==`RSA-PSS`)throw Error(`RSA-PSS signature profile requires an RSA-PSS private key`);if(!f(n))throw Error(`RSA-PSS key is missing hash metadata`);let i=m(n.hash.name),s=o(i);if(t!==void 0&&t!==s.saltLength)throw Error(`Unsupported RSA-PSS saltLength ${t} for ${i}; expected ${s.saltLength}`);return{algorithmOid:r.rsassaPss,parameters:a(s),signParams:{name:`RSA-PSS`,saltLength:s.saltLength}}}function u(e){let r=[t(e.algorithmOid)];return e.parameters!==void 0&&r.push(e.parameters),n(r)}async function d(e,t,n){let r=new Uint8Array(n),a=new Uint8Array(await i().subtle.sign(t.signParams,e,r));return t.ecdsaRawSignatureBytes!==void 0&&a[0]!==48?s(a,t.ecdsaRawSignatureBytes/2):a}function f(e){return`hash`in e}function p(e){return`namedCurve`in e}function m(e){switch(e){case`SHA-256`:return`SHA-256`;case`SHA-384`:return`SHA-384`;case`SHA-512`:return`SHA-512`;default:throw Error(`Unsupported RSA hash: ${e}`)}}export{u as encodeAlgorithmIdentifier,c as getSignatureAlgorithm,d as signBytes};
|
|
2
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","names":[],"sources":["../../../src/internal/crypto/signing.ts"],"sourcesContent":["/**\n * Signing helpers that map WebCrypto keys and signature profiles to on-wire\n * `AlgorithmIdentifier` values. Used by certificate and CSR builders.\n *\n * @module\n */\n\nimport { nullValue, objectIdentifier, sequence } from '#micro509/internal/asn1/der.ts';\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\nimport { rawEcdsaSignatureToDer } from './ecdsa.ts';\nimport { encodeRsaPssParameters, type RsaPssHash, rsaPssParametersForHash } from './rsa-pss.ts';\nimport { getCrypto } from './webcrypto.ts';\n\n/**\n * Controls how the signature algorithm is chosen.\n *\n * `'auto'` (default) infers the algorithm from the key. `'rsa-pss'` forces RSA-PSS\n * padding and requires an RSA-PSS private key.\n */\nexport type SignatureProfileInput =\n\t| {\n\t\t\t/** Infer the signature algorithm from the private key. */\n\t\t\treadonly kind?: 'auto';\n\t }\n\t| {\n\t\t\t/** Force RSA-PSS padding. */\n\t\t\treadonly kind: 'rsa-pss';\n\t\t\t/** Salt length in bytes. Must match the key's hash digest size. */\n\t\t\treadonly saltLength?: 32 | 48 | 64;\n\t };\n\n/** Resolved signature algorithm: the OID/parameters for DER encoding and the WebCrypto sign params. */\nexport interface SignatureAlgorithmIdentifier {\n\t/** ASN.1 OID of the signature algorithm (e.g. sha256WithRSAEncryption). */\n\treadonly algorithmOid: string;\n\t/** DER-encoded algorithm parameters, if the algorithm requires them (e.g. RSA-PSS). */\n\treadonly parameters?: Uint8Array;\n\t/** WebCrypto `sign()` algorithm parameter. */\n\treadonly signParams: Algorithm | EcdsaParams | RsaPssParams;\n\t/** When set, the raw ECDSA signature is this many bytes and must be DER-converted. */\n\treadonly ecdsaRawSignatureBytes?: number;\n}\n\n/** Resolve a private key and optional profile into a {@linkcode SignatureAlgorithmIdentifier}. */\nexport function getSignatureAlgorithm(\n\tprivateKey: CryptoKey,\n\tprofile: SignatureProfileInput = {},\n): SignatureAlgorithmIdentifier {\n\tconst algorithm = privateKey.algorithm;\n\tif (profile.kind === 'rsa-pss') {\n\t\treturn getRsaPssSignatureAlgorithm(privateKey, profile.saltLength);\n\t}\n\tif (algorithm.name === 'RSASSA-PKCS1-v1_5') {\n\t\tif (!hasHash(algorithm)) {\n\t\t\tthrow new Error('RSA key is missing hash metadata');\n\t\t}\n\t\tswitch (algorithm.hash.name) {\n\t\t\tcase 'SHA-256':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.sha256WithRSAEncryption,\n\t\t\t\t\tparameters: nullValue(),\n\t\t\t\t\tsignParams: { name: 'RSASSA-PKCS1-v1_5' },\n\t\t\t\t};\n\t\t\tcase 'SHA-384':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.sha384WithRSAEncryption,\n\t\t\t\t\tparameters: nullValue(),\n\t\t\t\t\tsignParams: { name: 'RSASSA-PKCS1-v1_5' },\n\t\t\t\t};\n\t\t\tcase 'SHA-512':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.sha512WithRSAEncryption,\n\t\t\t\t\tparameters: nullValue(),\n\t\t\t\t\tsignParams: { name: 'RSASSA-PKCS1-v1_5' },\n\t\t\t\t};\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported RSA hash: ${algorithm.hash.name}`);\n\t\t}\n\t}\n\n\tif (algorithm.name === 'RSA-PSS') {\n\t\tthrow new Error('RSA-PSS signing requires an explicit signature profile');\n\t}\n\n\tif (algorithm.name === 'ECDSA') {\n\t\tif (!hasNamedCurve(algorithm)) {\n\t\t\tthrow new Error('ECDSA key is missing namedCurve metadata');\n\t\t}\n\t\tswitch (algorithm.namedCurve) {\n\t\t\tcase 'P-256':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.ecdsaWithSHA256,\n\t\t\t\t\tsignParams: { name: 'ECDSA', hash: 'SHA-256' },\n\t\t\t\t\tecdsaRawSignatureBytes: 64,\n\t\t\t\t};\n\t\t\tcase 'P-384':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.ecdsaWithSHA384,\n\t\t\t\t\tsignParams: { name: 'ECDSA', hash: 'SHA-384' },\n\t\t\t\t\tecdsaRawSignatureBytes: 96,\n\t\t\t\t};\n\t\t\tcase 'P-521':\n\t\t\t\treturn {\n\t\t\t\t\talgorithmOid: OIDS.ecdsaWithSHA512,\n\t\t\t\t\tsignParams: { name: 'ECDSA', hash: 'SHA-512' },\n\t\t\t\t\tecdsaRawSignatureBytes: 132,\n\t\t\t\t};\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unsupported curve: ${algorithm.namedCurve}`);\n\t\t}\n\t}\n\n\tif (algorithm.name === 'Ed25519') {\n\t\treturn {\n\t\t\talgorithmOid: OIDS.ed25519,\n\t\t\tsignParams: { name: 'Ed25519' },\n\t\t};\n\t}\n\n\tthrow new Error(`Unsupported signing key algorithm: ${algorithm.name}`);\n}\n\n/** Build an RSA-PSS {@linkcode SignatureAlgorithmIdentifier} from an RSA-PSS private key. */\nfunction getRsaPssSignatureAlgorithm(\n\tprivateKey: CryptoKey,\n\tsaltLength: number | undefined,\n): SignatureAlgorithmIdentifier {\n\tconst algorithm = privateKey.algorithm;\n\tif (algorithm.name !== 'RSA-PSS') {\n\t\tthrow new Error('RSA-PSS signature profile requires an RSA-PSS private key');\n\t}\n\tif (!hasHash(algorithm)) {\n\t\tthrow new Error('RSA-PSS key is missing hash metadata');\n\t}\n\tconst hash = rsaPssHashFromWebCryptoName(algorithm.hash.name);\n\tconst parameters = rsaPssParametersForHash(hash);\n\tif (saltLength !== undefined && saltLength !== parameters.saltLength) {\n\t\tthrow new Error(\n\t\t\t`Unsupported RSA-PSS saltLength ${saltLength} for ${hash}; expected ${parameters.saltLength}`,\n\t\t);\n\t}\n\treturn {\n\t\talgorithmOid: OIDS.rsassaPss,\n\t\tparameters: encodeRsaPssParameters(parameters),\n\t\tsignParams: {\n\t\t\tname: 'RSA-PSS',\n\t\t\tsaltLength: parameters.saltLength,\n\t\t},\n\t};\n}\n\n/** DER-encode a {@linkcode SignatureAlgorithmIdentifier} as an ASN.1 `AlgorithmIdentifier` SEQUENCE. */\nexport function encodeAlgorithmIdentifier(input: SignatureAlgorithmIdentifier): Uint8Array {\n\tconst parts = [objectIdentifier(input.algorithmOid)];\n\tif (input.parameters !== undefined) {\n\t\tparts.push(input.parameters);\n\t}\n\treturn sequence(parts);\n}\n\n/** Sign `data` and return a DER-encoded signature. ECDSA raw signatures are auto-converted to DER. */\nexport async function signBytes(\n\tprivateKey: CryptoKey,\n\talgorithm: SignatureAlgorithmIdentifier,\n\tdata: Uint8Array,\n): Promise<Uint8Array> {\n\tconst view = new Uint8Array(data);\n\tconst signature = new Uint8Array(\n\t\tawait getCrypto().subtle.sign(algorithm.signParams, privateKey, view),\n\t);\n\t// For ECDSA keys, WebCrypto may return either raw (r || s) or DER format.\n\t// If the signature doesn't start with SEQUENCE tag and we expect ECDSA, convert to DER.\n\tif (algorithm.ecdsaRawSignatureBytes !== undefined && signature[0] !== 0x30) {\n\t\treturn rawEcdsaSignatureToDer(signature, algorithm.ecdsaRawSignatureBytes / 2);\n\t}\n\treturn signature;\n}\n\n/** Type guard: does this key algorithm carry a `hash` property (RSA keys). */\nfunction hasHash(algorithm: KeyAlgorithm): algorithm is RsaHashedKeyAlgorithm {\n\treturn 'hash' in algorithm;\n}\n\n/** Type guard: does this key algorithm carry a `namedCurve` property (EC keys). */\nfunction hasNamedCurve(algorithm: KeyAlgorithm): algorithm is EcKeyAlgorithm {\n\treturn 'namedCurve' in algorithm;\n}\n\n/** Narrow a WebCrypto hash name string to the supported {@linkcode RsaPssHash} union. */\nfunction rsaPssHashFromWebCryptoName(hash: string): RsaPssHash {\n\tswitch (hash) {\n\t\tcase 'SHA-256':\n\t\t\treturn 'SHA-256';\n\t\tcase 'SHA-384':\n\t\t\treturn 'SHA-384';\n\t\tcase 'SHA-512':\n\t\t\treturn 'SHA-512';\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported RSA hash: ${hash}`);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webcrypto.js","names":[],"sources":["../../../src/internal/crypto/webcrypto.ts"],"sourcesContent":["/**\n * Shared WebCrypto runtime access helper.\n *\n * @module\n */\n\n/** Return the global `Crypto` object. Throws when WebCrypto is unavailable. */\nexport function getCrypto(): Crypto {\n\tconst c = globalThis.crypto;\n\tif (c?.subtle === undefined) {\n\t\tthrow new Error('WebCrypto subtle API is required');\n\t}\n\treturn c;\n}\n"],"mappings":"AAOA,SAAgB,GAAoB,CACnC,IAAM,EAAI,WAAW,OACrB,GAAI,GAAG,SAAW,IAAA,GACjB,MAAU,MAAM,kCAAkC,EAEnD,OAAO,CACR"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){let t=[];for(let n of e)t.push(String.fromCharCode(n));return btoa(t.join(``))}function t(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}export{t as base64Decode,e as base64Encode};
|
|
2
|
+
//# sourceMappingURL=base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.js","names":[],"sources":["../../../src/internal/shared/base64.ts"],"sourcesContent":["/** Encode raw bytes as standard base64 without line breaks. */\nexport function base64Encode(bytes: Uint8Array): string {\n\tconst parts: string[] = [];\n\tfor (const byte of bytes) {\n\t\tparts.push(String.fromCharCode(byte));\n\t}\n\treturn btoa(parts.join(''));\n}\n\n/** Decode a standard base64 string into raw bytes. */\nexport function base64Decode(value: string): Uint8Array {\n\tconst binary = atob(value);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n"],"mappings":"AACA,SAAgB,EAAa,EAA2B,CACvD,IAAM,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAQ,EAClB,EAAM,KAAK,OAAO,aAAa,CAAI,CAAC,EAErC,OAAO,KAAK,EAAM,KAAK,EAAE,CAAC,CAC3B,CAGA,SAAgB,EAAa,EAA2B,CACvD,IAAM,EAAS,KAAK,CAAK,EACnB,EAAQ,IAAI,WAAW,EAAO,MAAM,EAC1C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAClC,EAAM,GAAK,EAAO,WAAW,CAAC,EAE/B,OAAO,CACR"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){if(e.rdns.length!==t.rdns.length)return!1;for(let n=0;n<e.rdns.length;n+=1){let i=e.rdns[n],a=t.rdns[n];if(i===void 0||a===void 0||!r(i,a))return!1}return!0}function t(e){return e.rdns.map(s).join(`,`)}function n(e,t){if(t.rdns.length>e.rdns.length)return!1;for(let n=0;n<t.rdns.length;n+=1){let i=e.rdns[n],a=t.rdns[n];if(i===void 0||a===void 0||!r(i,a))return!1}return!0}function r(e,t){if(e.attributes.length!==t.attributes.length)return!1;let n=Array(t.attributes.length).fill(!1);for(let r of e.attributes){let e=!1;for(let a=0;a<t.attributes.length;a+=1){let o=t.attributes[a];if(!(o===void 0||n[a])&&i(r,o)){n[a]=!0,e=!0;break}}if(!e)return!1}return!0}function i(e,t){if(e.oid!==t.oid)return!1;if(a(e.valueTag)&&a(t.valueTag)){let n=o(e.value),r=o(t.value);return n===void 0||r===void 0?!1:n===r}return e.valueTag===t.valueTag&&e.value===t.value}function a(e){return e===12||e===19}function o(e){let t=e.normalize(`NFKC`);if(!/[^\P{Cc}\t\n\r]/u.test(t))return t.toLowerCase().trim().replace(/\s+/gu,` `)}function s(e){return e.attributes.map(e=>{let t=a(e.valueTag)?o(e.value)??`[raw:${e.valueTag}]${e.value}`:`[${String(e.valueTag)}]${e.value}`;return`${e.oid}=${c(t)}`}).sort().join(`+`)}function c(e){return e.replaceAll(`\\`,`\\\\`).replaceAll(`,`,`\\,`).replaceAll(`+`,`\\+`).replaceAll(`=`,`\\=`)}export{t as canonicalDnKey,e as compareDistinguishedNames,i as compareNameAttributeValue,r as compareRelativeDistinguishedNames,a as isDirectoryStringTag,n as isWithinDirectoryNameSubtree,o as prepareNameCompareString};
|
|
2
|
+
//# sourceMappingURL=dn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dn.js","names":[],"sources":["../../../src/internal/shared/dn.ts"],"sourcesContent":["/**\n * RFC 5280 §7.1 distinguished name comparison utilities.\n *\n * Provides semantic DN equality (case-folding, NFKC normalization,\n * whitespace collapse) and a canonical string key for O(1) Map lookups.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc5280#section-7.1 | RFC 5280 §7.1}. Internationalized Names in Distinguished Names\n * @module\n */\n\nimport type {\n\tParsedName,\n\tParsedNameAttribute,\n\tParsedRelativeDistinguishedName,\n} from '#micro509/x509/parse.ts';\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** RFC 5280 §7.1 semantic equality: same RDN count, each pair matches attribute-by-attribute. */\nexport function compareDistinguishedNames(left: ParsedName, right: ParsedName): boolean {\n\tif (left.rdns.length !== right.rdns.length) {\n\t\treturn false;\n\t}\n\tfor (let index = 0; index < left.rdns.length; index += 1) {\n\t\tconst leftRdn = left.rdns[index];\n\t\tconst rightRdn = right.rdns[index];\n\t\tif (leftRdn === undefined || rightRdn === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!compareRelativeDistinguishedNames(leftRdn, rightRdn)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/**\n * Produces a deterministic string key for a parsed DN, suitable as a Map key.\n * Semantically equal DNs (per RFC 5280 §7.1) produce identical keys.\n *\n * Format: RDNs joined by `,`, attributes within each RDN sorted by OID then\n * by prepared value, joined by `+`.\n */\nexport function canonicalDnKey(name: ParsedName): string {\n\treturn name.rdns.map(canonicalRdnKey).join(',');\n}\n\n/** True when `subject` equals or is subordinate to `constraint` (RDN prefix match). */\nexport function isWithinDirectoryNameSubtree(subject: ParsedName, constraint: ParsedName): boolean {\n\tif (constraint.rdns.length > subject.rdns.length) {\n\t\treturn false;\n\t}\n\tfor (let index = 0; index < constraint.rdns.length; index += 1) {\n\t\tconst subjectRdn = subject.rdns[index];\n\t\tconst constraintRdn = constraint.rdns[index];\n\t\tif (subjectRdn === undefined || constraintRdn === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!compareRelativeDistinguishedNames(subjectRdn, constraintRdn)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// RDN / attribute comparison\n// ---------------------------------------------------------------------------\n\n/** Order-independent RDN equality: same attribute count, each pair matched exactly once. */\nexport function compareRelativeDistinguishedNames(\n\tleft: ParsedRelativeDistinguishedName,\n\tright: ParsedRelativeDistinguishedName,\n): boolean {\n\tif (left.attributes.length !== right.attributes.length) {\n\t\treturn false;\n\t}\n\tconst matched = new Array<boolean>(right.attributes.length).fill(false);\n\tfor (const leftAttribute of left.attributes) {\n\t\tlet found = false;\n\t\tfor (let index = 0; index < right.attributes.length; index += 1) {\n\t\t\tconst rightAttribute = right.attributes[index];\n\t\t\tif (rightAttribute === undefined || matched[index]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!compareNameAttributeValue(leftAttribute, rightAttribute)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmatched[index] = true;\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t\tif (!found) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/** Compares two AttributeTypeAndValue pairs using RFC 5280 §7.1 string-prep for DirectoryString tags. */\nexport function compareNameAttributeValue(\n\tleft: ParsedNameAttribute,\n\tright: ParsedNameAttribute,\n): boolean {\n\tif (left.oid !== right.oid) {\n\t\treturn false;\n\t}\n\tif (isDirectoryStringTag(left.valueTag) && isDirectoryStringTag(right.valueTag)) {\n\t\tconst preparedLeft = prepareNameCompareString(left.value);\n\t\tconst preparedRight = prepareNameCompareString(right.value);\n\t\tif (preparedLeft === undefined || preparedRight === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\treturn preparedLeft === preparedRight;\n\t}\n\treturn left.valueTag === right.valueTag && left.value === right.value;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** True for UTF8String (0x0C) and PrintableString (0x13) — the DirectoryString types we normalize. */\nexport function isDirectoryStringTag(tag: number): boolean {\n\treturn tag === 0x0c || tag === 0x13;\n}\n\n/** NFKC-normalizes, lowercases, trims, and collapses whitespace for RFC 5280 §7.1 comparison. */\nexport function prepareNameCompareString(value: string): string | undefined {\n\tconst normalized = value.normalize('NFKC');\n\tif (/[^\\P{Cc}\\t\\n\\r]/u.test(normalized)) {\n\t\treturn undefined;\n\t}\n\treturn normalized.toLowerCase().trim().replace(/\\s+/gu, ' ');\n}\n\n/** Canonical string for a single RDN: attributes sorted, values prepared. */\nfunction canonicalRdnKey(rdn: ParsedRelativeDistinguishedName): string {\n\treturn rdn.attributes\n\t\t.map((attr) => {\n\t\t\tconst val = isDirectoryStringTag(attr.valueTag)\n\t\t\t\t? (prepareNameCompareString(attr.value) ?? `[raw:${attr.valueTag}]${attr.value}`)\n\t\t\t\t: `[${String(attr.valueTag)}]${attr.value}`;\n\t\t\treturn `${attr.oid}=${escapeCanonicalDnValue(val)}`;\n\t\t})\n\t\t.sort()\n\t\t.join('+');\n}\n\nfunction escapeCanonicalDnValue(value: string): string {\n\treturn value\n\t\t.replaceAll('\\\\', '\\\\\\\\')\n\t\t.replaceAll(',', '\\\\,')\n\t\t.replaceAll('+', '\\\\+')\n\t\t.replaceAll('=', '\\\\=');\n}\n"],"mappings":"AAqBA,SAAgB,EAA0B,EAAkB,EAA4B,CACvF,GAAI,EAAK,KAAK,SAAW,EAAM,KAAK,OACnC,MAAO,GAER,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,KAAK,OAAQ,GAAS,EAAG,CACzD,IAAM,EAAU,EAAK,KAAK,GACpB,EAAW,EAAM,KAAK,GAI5B,GAHI,IAAY,IAAA,IAAa,IAAa,IAAA,IAGtC,CAAC,EAAkC,EAAS,CAAQ,EACvD,MAAO,EAET,CACA,MAAO,EACR,CASA,SAAgB,EAAe,EAA0B,CACxD,OAAO,EAAK,KAAK,IAAI,CAAe,CAAC,CAAC,KAAK,GAAG,CAC/C,CAGA,SAAgB,EAA6B,EAAqB,EAAiC,CAClG,GAAI,EAAW,KAAK,OAAS,EAAQ,KAAK,OACzC,MAAO,GAER,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAW,KAAK,OAAQ,GAAS,EAAG,CAC/D,IAAM,EAAa,EAAQ,KAAK,GAC1B,EAAgB,EAAW,KAAK,GAItC,GAHI,IAAe,IAAA,IAAa,IAAkB,IAAA,IAG9C,CAAC,EAAkC,EAAY,CAAa,EAC/D,MAAO,EAET,CACA,MAAO,EACR,CAOA,SAAgB,EACf,EACA,EACU,CACV,GAAI,EAAK,WAAW,SAAW,EAAM,WAAW,OAC/C,MAAO,GAER,IAAM,EAAc,MAAe,EAAM,WAAW,MAAM,CAAC,CAAC,KAAK,EAAK,EACtE,IAAK,IAAM,KAAiB,EAAK,WAAY,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAM,WAAW,OAAQ,GAAS,EAAG,CAChE,IAAM,EAAiB,EAAM,WAAW,GACpC,SAAmB,IAAA,IAAa,EAAQ,KAGvC,EAA0B,EAAe,CAAc,EAI5D,CADA,EAAQ,GAAS,GACjB,EAAQ,GACR,KADQ,CAET,CACA,GAAI,CAAC,EACJ,MAAO,EAET,CACA,MAAO,EACR,CAGA,SAAgB,EACf,EACA,EACU,CACV,GAAI,EAAK,MAAQ,EAAM,IACtB,MAAO,GAER,GAAI,EAAqB,EAAK,QAAQ,GAAK,EAAqB,EAAM,QAAQ,EAAG,CAChF,IAAM,EAAe,EAAyB,EAAK,KAAK,EAClD,EAAgB,EAAyB,EAAM,KAAK,EAI1D,OAHI,IAAiB,IAAA,IAAa,IAAkB,IAAA,GAC5C,GAED,IAAiB,CACzB,CACA,OAAO,EAAK,WAAa,EAAM,UAAY,EAAK,QAAU,EAAM,KACjE,CAOA,SAAgB,EAAqB,EAAsB,CAC1D,OAAO,IAAQ,IAAQ,IAAQ,EAChC,CAGA,SAAgB,EAAyB,EAAmC,CAC3E,IAAM,EAAa,EAAM,UAAU,MAAM,EACrC,uBAAmB,KAAK,CAAU,EAGtC,OAAO,EAAW,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,QAAS,GAAG,CAC5D,CAGA,SAAS,EAAgB,EAA8C,CACtE,OAAO,EAAI,WACT,IAAK,GAAS,CACd,IAAM,EAAM,EAAqB,EAAK,QAAQ,EAC1C,EAAyB,EAAK,KAAK,GAAK,QAAQ,EAAK,SAAS,GAAG,EAAK,QACvE,IAAI,OAAO,EAAK,QAAQ,EAAE,GAAG,EAAK,QACrC,MAAO,GAAG,EAAK,IAAI,GAAG,EAAuB,CAAG,GACjD,CAAC,CAAC,CACD,KAAK,CAAC,CACN,KAAK,GAAG,CACX,CAEA,SAAS,EAAuB,EAAuB,CACtD,OAAO,EACL,WAAW,KAAM,MAAM,CAAC,CACxB,WAAW,IAAK,KAAK,CAAC,CACtB,WAAW,IAAK,KAAK,CAAC,CACtB,WAAW,IAAK,KAAK,CACxB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=/^[0-9a-f]{1,4}$/;function t(t){let n=t.toLowerCase(),r=n.split(`::`),i=r[0]??``,a=r[1];if(a!==void 0&&n.indexOf(`::`)!==n.lastIndexOf(`::`))throw Error(`Invalid IPv6 address: ${t}`);let o=i.length>0?i.split(`:`):[],s=a!==void 0&&a.length>0?a.split(`:`):[],c=8-(o.length+s.length);if(a===void 0&&o.length!==8||c<0)throw Error(`Invalid IPv6 address: ${t}`);let l=Array.from({length:c},()=>`0`),u=a===void 0?o:[...o,...l,...s];if(u.length!==8)throw Error(`Invalid IPv6 address: ${t}`);return u.map(n=>{if(!e.test(n))throw Error(`Invalid IPv6 address: ${t}`);return n.padStart(4,`0`)})}function n(e){if(e.includes(`:`))return a(e);let t=e.split(`.`);if(t.length!==4)throw Error(`Invalid IPv4 address: ${e}`);return Uint8Array.from(t.map(t=>{if(t.length===0)throw Error(`Invalid IPv4 address: ${e}`);let n=Number(t);if(!Number.isInteger(n)||n<0||n>255)throw Error(`Invalid IPv4 address: ${e}`);return n}))}function r(e){if(e.length===4)return Array.from(e,e=>String(e)).join(`.`);if(e.length===16){let t=[];for(let n=0;n<e.length;n+=2){let r=e[n]??0,i=e[n+1]??0;t.push((r<<8|i).toString(16))}return t.join(`:`)}throw Error(`Unsupported IP address length: ${e.length}`)}function i(e){let t=n(e),r=new Uint8Array(t.length);return r.fill(255),r}function a(e){let n=t(e),r=new Uint8Array(16);return n.forEach((e,t)=>{let n=Number.parseInt(e,16);r[t*2]=n>>8,r[t*2+1]=n&255}),r}export{i as allOnesMaskForIpAddress,r as decodeIpAddress,t as expandIpv6,n as parseIpAddressToBytes};
|
|
2
|
+
//# sourceMappingURL=ip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip.js","names":[],"sources":["../../../src/internal/shared/ip.ts"],"sourcesContent":["/**\n * IP address parsing and normalization helpers shared by parsing,\n * validation, and identity matching flows.\n *\n * These utilities keep IPv4 and IPv6 handling consistent for SAN\n * matching and name constraint evaluation.\n *\n * @example\n * ```ts\n * import {\n * \tallOnesMaskForIpAddress,\n * \tdecodeIpAddress,\n * \texpandIpv6,\n * \tnormalizeIpAddress,\n * \tparseIpAddressToBytes,\n * } from './ip.ts';\n *\n * normalizeIpAddress('::1');\n * // '0000:0000:0000:0000:0000:0000:0000:0001'\n *\n * expandIpv6('2001:db8::1');\n * // ['2001', '0db8', '0000', '0000', '0000', '0000', '0000', '0001']\n *\n * Array.from(parseIpAddressToBytes('127.0.0.1'));\n * // [127, 0, 0, 1]\n *\n * decodeIpAddress(Uint8Array.of(127, 0, 0, 1));\n * // '127.0.0.1'\n *\n * Array.from(allOnesMaskForIpAddress('127.0.0.1'));\n * // [255, 255, 255, 255]\n * ```\n *\n * @module\n */\n\n/** Matches a single valid IPv6 hex segment (1–4 hex digits). */\nconst IPV6_SEGMENT = /^[0-9a-f]{1,4}$/;\n\n/**\n * Normalizes an IP address string for comparison.\\\n * IPv4 addresses pass through unchanged; IPv6 addresses expand to 8\n * colon-separated zero-padded groups.\n *\n * @example\n * ```ts\n * normalizeIpAddress('::1');\n * // '0000:0000:0000:0000:0000:0000:0000:0001'\n * ```\n *\n * @param value IPv4 or IPv6 address string to normalize.\n * @returns The normalized address string.\n */\nexport function normalizeIpAddress(value: string): string {\n\tif (!value.includes(':')) {\n\t\treturn value;\n\t}\n\treturn expandIpv6(value).join(':');\n}\n\n/**\n * Expands an IPv6 address, including `::` shorthand, into exactly 8\n * zero-padded four-character hex segments.\\\n * Throws on malformed input.\n *\n * @example\n * ```ts\n * expandIpv6('2001:db8::1');\n * // ['2001', '0db8', '0000', '0000', '0000', '0000', '0000', '0001']\n * ```\n *\n * @param value IPv6 address string to expand.\n * @returns The 8 normalized IPv6 segments.\n */\nexport function expandIpv6(value: string): readonly string[] {\n\tconst normalized = value.toLowerCase();\n\tconst pieces = normalized.split('::');\n\tconst head = pieces[0] ?? '';\n\tconst tail = pieces[1];\n\tif (tail !== undefined && normalized.indexOf('::') !== normalized.lastIndexOf('::')) {\n\t\tthrow new Error(`Invalid IPv6 address: ${value}`);\n\t}\n\tconst headParts = head.length > 0 ? head.split(':') : [];\n\tconst tailParts = tail !== undefined && tail.length > 0 ? tail.split(':') : [];\n\tconst missing = 8 - (headParts.length + tailParts.length);\n\tif ((tail === undefined && headParts.length !== 8) || missing < 0) {\n\t\tthrow new Error(`Invalid IPv6 address: ${value}`);\n\t}\n\tconst zeroes = Array.from({ length: missing }, () => '0');\n\tconst parts = tail === undefined ? headParts : [...headParts, ...zeroes, ...tailParts];\n\tif (parts.length !== 8) {\n\t\tthrow new Error(`Invalid IPv6 address: ${value}`);\n\t}\n\treturn parts.map((segment) => {\n\t\tif (!IPV6_SEGMENT.test(segment)) {\n\t\t\tthrow new Error(`Invalid IPv6 address: ${value}`);\n\t\t}\n\t\treturn segment.padStart(4, '0');\n\t});\n}\n\n/**\n * Parses an IPv4 or IPv6 address string into raw bytes.\\\n * Returns 4 bytes for IPv4 and 16 bytes for IPv6.\n *\n * Suitable for encoding into SAN iPAddress octets or name-constraint ranges.\n *\n * @example\n * ```ts\n * Array.from(parseIpAddressToBytes('127.0.0.1'));\n * // [127, 0, 0, 1]\n * ```\n *\n * @param value IPv4 or IPv6 address string to parse.\n * @returns The raw address bytes.\n */\nexport function parseIpAddressToBytes(value: string): Uint8Array {\n\tif (value.includes(':')) {\n\t\treturn parseIpv6ToBytes(value);\n\t}\n\tconst segments = value.split('.');\n\tif (segments.length !== 4) {\n\t\tthrow new Error(`Invalid IPv4 address: ${value}`);\n\t}\n\treturn Uint8Array.from(\n\t\tsegments.map((segment) => {\n\t\t\tif (segment.length === 0) {\n\t\t\t\tthrow new Error(`Invalid IPv4 address: ${value}`);\n\t\t\t}\n\t\t\tconst parsed = Number(segment);\n\t\t\tif (!Number.isInteger(parsed) || parsed < 0 || parsed > 255) {\n\t\t\t\tthrow new Error(`Invalid IPv4 address: ${value}`);\n\t\t\t}\n\t\t\treturn parsed;\n\t\t}),\n\t);\n}\n\n/**\n * Converts raw IP address bytes back to a human-readable string.\\\n * Returns dotted-decimal IPv4 for 4-byte input and colon-separated hex\n * IPv6 for 16-byte input.\n *\n * Throws on any other length.\n *\n * @example\n * ```ts\n * decodeIpAddress(Uint8Array.of(127, 0, 0, 1));\n * // '127.0.0.1'\n * ```\n *\n * @param bytes Raw IPv4 or IPv6 address bytes.\n * @returns The decoded address string.\n */\nexport function decodeIpAddress(bytes: Uint8Array): string {\n\tif (bytes.length === 4) {\n\t\treturn Array.from(bytes, (value) => String(value)).join('.');\n\t}\n\tif (bytes.length === 16) {\n\t\tconst groups: string[] = [];\n\t\tfor (let index = 0; index < bytes.length; index += 2) {\n\t\t\tconst left = bytes[index] ?? 0;\n\t\t\tconst right = bytes[index + 1] ?? 0;\n\t\t\tgroups.push(((left << 8) | right).toString(16));\n\t\t}\n\t\treturn groups.join(':');\n\t}\n\tthrow new Error(`Unsupported IP address length: ${bytes.length}`);\n}\n\n/**\n * Returns an all-ones (`0xff`) mask of the appropriate length for the\n * given IP address string.\\\n * Returns 4 bytes for IPv4 and 16 bytes for IPv6.\n *\n * Used to build name-constraint subnet masks that match a single host.\n *\n * @example\n * ```ts\n * Array.from(allOnesMaskForIpAddress('127.0.0.1'));\n * // [255, 255, 255, 255]\n * ```\n *\n * @param value IPv4 or IPv6 address string used to choose mask length.\n * @returns An all-ones mask for the same address family.\n */\nexport function allOnesMaskForIpAddress(value: string): Uint8Array {\n\t// Validate and parse the IP address to determine its type\n\tconst parsed = parseIpAddressToBytes(value);\n\tconst mask = new Uint8Array(parsed.length);\n\tmask.fill(0xff);\n\treturn mask;\n}\n\n/** Parses an IPv6 address string into a 16-byte `Uint8Array`. */\nfunction parseIpv6ToBytes(value: string): Uint8Array {\n\tconst expanded = expandIpv6(value);\n\tconst bytes = new Uint8Array(16);\n\texpanded.forEach((segment, index) => {\n\t\tconst parsed = Number.parseInt(segment, 16);\n\t\tbytes[index * 2] = parsed >> 8;\n\t\tbytes[index * 2 + 1] = parsed & 0xff;\n\t});\n\treturn bytes;\n}\n"],"mappings":"AAqCA,MAAM,EAAe,kBAqCrB,SAAgB,EAAW,EAAkC,CAC5D,IAAM,EAAa,EAAM,YAAY,EAC/B,EAAS,EAAW,MAAM,IAAI,EAC9B,EAAO,EAAO,IAAM,GACpB,EAAO,EAAO,GACpB,GAAI,IAAS,IAAA,IAAa,EAAW,QAAQ,IAAI,IAAM,EAAW,YAAY,IAAI,EACjF,MAAU,MAAM,yBAAyB,GAAO,EAEjD,IAAM,EAAY,EAAK,OAAS,EAAI,EAAK,MAAM,GAAG,EAAI,CAAC,EACjD,EAAY,IAAS,IAAA,IAAa,EAAK,OAAS,EAAI,EAAK,MAAM,GAAG,EAAI,CAAC,EACvE,EAAU,GAAK,EAAU,OAAS,EAAU,QAClD,GAAK,IAAS,IAAA,IAAa,EAAU,SAAW,GAAM,EAAU,EAC/D,MAAU,MAAM,yBAAyB,GAAO,EAEjD,IAAM,EAAS,MAAM,KAAK,CAAE,OAAQ,CAAQ,MAAS,GAAG,EAClD,EAAQ,IAAS,IAAA,GAAY,EAAY,CAAC,GAAG,EAAW,GAAG,EAAQ,GAAG,CAAS,EACrF,GAAI,EAAM,SAAW,EACpB,MAAU,MAAM,yBAAyB,GAAO,EAEjD,OAAO,EAAM,IAAK,GAAY,CAC7B,GAAI,CAAC,EAAa,KAAK,CAAO,EAC7B,MAAU,MAAM,yBAAyB,GAAO,EAEjD,OAAO,EAAQ,SAAS,EAAG,GAAG,CAC/B,CAAC,CACF,CAiBA,SAAgB,EAAsB,EAA2B,CAChE,GAAI,EAAM,SAAS,GAAG,EACrB,OAAO,EAAiB,CAAK,EAE9B,IAAM,EAAW,EAAM,MAAM,GAAG,EAChC,GAAI,EAAS,SAAW,EACvB,MAAU,MAAM,yBAAyB,GAAO,EAEjD,OAAO,WAAW,KACjB,EAAS,IAAK,GAAY,CACzB,GAAI,EAAQ,SAAW,EACtB,MAAU,MAAM,yBAAyB,GAAO,EAEjD,IAAM,EAAS,OAAO,CAAO,EAC7B,GAAI,CAAC,OAAO,UAAU,CAAM,GAAK,EAAS,GAAK,EAAS,IACvD,MAAU,MAAM,yBAAyB,GAAO,EAEjD,OAAO,CACR,CAAC,CACF,CACD,CAkBA,SAAgB,EAAgB,EAA2B,CAC1D,GAAI,EAAM,SAAW,EACpB,OAAO,MAAM,KAAK,EAAQ,GAAU,OAAO,CAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAE5D,GAAI,EAAM,SAAW,GAAI,CACxB,IAAM,EAAmB,CAAC,EAC1B,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAM,OAAQ,GAAS,EAAG,CACrD,IAAM,EAAO,EAAM,IAAU,EACvB,EAAQ,EAAM,EAAQ,IAAM,EAClC,EAAO,MAAO,GAAQ,EAAK,EAAA,CAAO,SAAS,EAAE,CAAC,CAC/C,CACA,OAAO,EAAO,KAAK,GAAG,CACvB,CACA,MAAU,MAAM,kCAAkC,EAAM,QAAQ,CACjE,CAkBA,SAAgB,EAAwB,EAA2B,CAElE,IAAM,EAAS,EAAsB,CAAK,EACpC,EAAO,IAAI,WAAW,EAAO,MAAM,EAEzC,OADA,EAAK,KAAK,GAAI,EACP,CACR,CAGA,SAAS,EAAiB,EAA2B,CACpD,IAAM,EAAW,EAAW,CAAK,EAC3B,EAAQ,IAAI,WAAW,EAAE,EAM/B,OALA,EAAS,SAAS,EAAS,IAAU,CACpC,IAAM,EAAS,OAAO,SAAS,EAAS,EAAE,EAC1C,EAAM,EAAQ,GAAK,GAAU,EAC7B,EAAM,EAAQ,EAAI,GAAK,EAAS,GACjC,CAAC,EACM,CACR"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{readRootElement as e}from"../asn1/der.js";import{childrenOf as t,decodeObjectIdentifier as n,decodeString as r,hexToBytes as i,requireElement as a,toHex as o}from"../asn1/asn1.js";import{OIDS as s}from"../asn1/oids.js";import{allOnesMaskForIpAddress as c,decodeIpAddress as l,parseIpAddressToBytes as u}from"../shared/ip.js";import{nameFieldKeyFromOid as d}from"../x509/name-fields.js";import{compareDistinguishedNames as f,isWithinDirectoryNameSubtree as p}from"../shared/dn.js";function m(e,t,n,r){return{ok:!1,code:e,message:t,index:n,...r===void 0?{}:{details:r}}}function h(e){let t={...e.subjectCommonName===void 0?{}:{subjectCommonName:e.subjectCommonName},...e.actual===void 0?{}:{actual:e.actual}};return Object.keys(t).length===0?void 0:t}function g(e){return f(e.subject,e.issuer)}function _(e){return{initialPermittedSubtrees:e.permittedSubtrees?.map(e=>e.base)??[],initialExcludedSubtrees:e.excludedSubtrees?.map(e=>e.base)??[]}}function v(e,t){let n=y(t),r=e[e.length-1];if(r?.nameConstraints!==void 0){let t=x(r,e.length-1);if(!t.ok)return t;n=b(n,r.nameConstraints)}for(let t=e.length-2;t>=0;--t){let r=e[t];if(r===void 0)throw Error(`Certificate chain contains undefined at index ${String(t)}`);if(!g(r)||t===0){let e=w(r,n,t);if(!e.ok)return e}if(r.nameConstraints!==void 0){let e=x(r,t);if(!e.ok)return e;n=b(n,r.nameConstraints)}}return{ok:!0}}function y(e){return{permittedLevels:e.initialPermittedSubtrees.length>0?[e.initialPermittedSubtrees]:[],excluded:e.initialExcludedSubtrees}}function b(e,t){return{permittedLevels:t.permittedSubtrees!==void 0&&t.permittedSubtrees.length>0?[...e.permittedLevels,t.permittedSubtrees.flatMap(e=>C(e.base)?[e.base]:[])]:e.permittedLevels,excluded:t.excludedSubtrees!==void 0&&t.excludedSubtrees.length>0?[...e.excluded,...t.excludedSubtrees.flatMap(e=>C(e.base)?[e.base]:[])]:e.excluded}}function x(e,t){if(e.nameConstraints===void 0||!e.extensions.some(e=>e.oid===s.nameConstraints&&e.critical))return{ok:!0};let n=S(e.nameConstraints);return n.length===0?{ok:!0}:m(`unsupported_name_constraints`,`certificate contains unsupported critical name constraints`,t,h({subjectCommonName:e.subject.values.commonName,actual:n.join(`, `)}))}function S(e){let t=new Set;for(let n of e.permittedSubtrees??[])C(n.base)||t.add(n.base.type);for(let n of e.excludedSubtrees??[])C(n.base)||t.add(n.base.type);return[...t]}function C(e){switch(e.type){case`dns`:case`email`:case`uri`:case`ip`:case`directoryName`:return!0;case`otherName`:case`x400Address`:case`ediPartyName`:case`registeredID`:return!1;default:throw Error(`Unhandled NameConstraintForm type: ${String(e)}`)}}function w(e,t,n){if(e.subject.derHex!==`3000`&&!D({type:`directoryName`,derHex:e.subject.derHex},t))return m(`name_constraints_violated`,`subject distinguished name violates name constraints`,n,h({subjectCommonName:e.subject.values.commonName}));if(e.subjectAltNames!==void 0)for(let r of e.subjectAltNames){let i=E(r);if(!i.ok)return m(`name_constraints_violated`,`SAN ${i.actual} is malformed and cannot be checked against name constraints`,n,h({subjectCommonName:e.subject.values.commonName,actual:i.actual}));let a=i.value;if(a!==void 0&&!D(a,t))return m(`name_constraints_violated`,`SAN ${R(a)} violates name constraints`,n,h({subjectCommonName:e.subject.values.commonName,actual:R(a)}))}return T(t)&&!(e.subjectAltNames?.some(e=>e.type===`email`)??!1)&&e.subject.values.emailAddress!==void 0&&!D({type:`email`,value:e.subject.values.emailAddress},t)?m(`name_constraints_violated`,`subject emailAddress ${e.subject.values.emailAddress} violates name constraints`,n,h({subjectCommonName:e.subject.values.commonName,actual:e.subject.values.emailAddress})):{ok:!0}}function T(e){for(let t of e.permittedLevels)if(t.some(e=>e.type===`email`))return!0;return e.excluded.some(e=>e.type===`email`)}function E(e){switch(e.type){case`dns`:return{ok:!0,value:{type:`dns`,value:e.value}};case`email`:return{ok:!0,value:{type:`email`,value:e.value}};case`uri`:return{ok:!0,value:{type:`uri`,value:e.value}};case`srv`:return{ok:!0,value:void 0};case`ip`:try{return{ok:!0,value:{type:`ip`,addressBytes:u(e.value),maskBytes:c(e.value)}}}catch{return{ok:!1,actual:`ip:${e.value}`}}case`directoryName`:return{ok:!0,value:{type:`directoryName`,derHex:e.derHex}};case`unknown`:return{ok:!0,value:void 0};default:throw Error(`Unhandled SubjectAltName type: ${String(e)}`)}}function D(e,t){for(let n of t.excluded)if(O(e,n))return!1;for(let n of t.permittedLevels){let t=n.filter(t=>t.type===e.type);if(t.length!==0&&!t.some(t=>O(e,t)))return!1}return!0}function O(e,t){return e.type===`dns`&&t.type===`dns`?k(e.value,t.value):e.type===`email`&&t.type===`email`?A(e.value,t.value):e.type===`uri`&&t.type===`uri`?j(e.value,t.value):e.type===`ip`&&t.type===`ip`?N(e.addressBytes,t.addressBytes,t.maskBytes):e.type===`directoryName`&&t.type===`directoryName`?P(e.derHex,t.derHex):!1}function k(e,t){let n=e.toLowerCase(),r=t.toLowerCase();return r.length===0?!0:r.startsWith(`.`)?n.endsWith(r):n===r||n.endsWith(`.${r}`)}function A(e,t){let n=e.toLowerCase(),r=t.toLowerCase();if(r.includes(`@`))return n===r;let i=n.indexOf(`@`);if(i<0)return!1;let a=n.slice(i+1);return r.startsWith(`.`)?a.endsWith(r):a===r}function j(e,t){let n=M(e);if(n===void 0)return!1;let r=n.toLowerCase(),i=t.toLowerCase();return i.length===0?!0:i.startsWith(`.`)?r.endsWith(i):r===i}function M(e){try{return new URL(e).hostname}catch{return}}function N(e,t,n){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r+=1){let i=e[r]??0,a=t[r]??0,o=n[r]??0;if((i&o)!==(a&o))return!1}return!0}function P(e,t){let n=F(e),r=F(t);return n===void 0||r===void 0?!1:p(n,r)}function F(n){if(/^(?:[0-9a-fA-F]{2})+$/.test(n))try{let r=i(n),a=I(r,e(r,{maxDepth:64}));if(a.tag!==48)return;let s=[],c=[],l={};for(let e of t(r,a)){let t=L(r,e);if(t===void 0)return;s.push(t);for(let e of t.attributes)c.push(e),e.key!==void 0&&l[e.key]===void 0&&(l[e.key]=e.value)}return{derHex:o(r),rdns:s,attributes:c,values:l}}catch{return}}function I(e,n){if(n.tag!==48)return n;let r=t(e,n),i=r[0];return r.length===1&&i?.tag===48?i:n}function L(e,i){if(i.tag!==49)return;let s=t(e,i);if(s.length===0)return;let c=[],l={};for(let i of s){if(i.tag!==48)return;let o=t(e,i),s=o[0],u=o[1];if(s===void 0||u===void 0||o.length!==2||s.tag!==6)return;let f=n(a(s,`directoryName OID`).value),p;try{p=r(u.tag,a(u,`directoryName value`).value)}catch{return}let m=d(f),h=m===void 0?{oid:f,valueTag:u.tag,value:p}:{oid:f,key:m,valueTag:u.tag,value:p};c.push(h),m!==void 0&&l[m]===void 0&&(l[m]=p)}if(c.length!==0)return{derHex:o(e.slice(i.start-i.headerLength,i.end)),attributes:c,values:l}}function R(e){switch(e.type){case`dns`:return`dns:${e.value}`;case`email`:return`email:${e.value}`;case`uri`:return`uri:${e.value}`;case`ip`:return`ip:${l(e.addressBytes)}`;case`directoryName`:return`dn:${e.derHex.slice(0,20)}${e.derHex.length>20?`...`:``}`;default:throw Error(`Unhandled NameConstraintForm type: ${String(e)}`)}}export{_ as createNameConstraintValidationState,v as evaluateNameConstraints};
|
|
2
|
+
//# sourceMappingURL=name-constraints-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-constraints-engine.js","names":["exhaustive"],"sources":["../../../src/internal/verify/name-constraints-engine.ts"],"sourcesContent":["/**\n * Internal name-constraints evaluation engine.\n *\n * Accumulates and evaluates the shipped RFC 5280 §4.2.1.10 / §6.1\n * name-constraint subset during certificate path validation.\n *\n * @module\n */\n\nimport {\n\tchildrenOf,\n\tdecodeObjectIdentifier,\n\tdecodeString,\n\thexToBytes,\n\trequireElement,\n\ttoHex,\n} from '#micro509/internal/asn1/asn1.ts';\nimport {\n\tDEFAULT_MAX_DER_DEPTH,\n\ttype DerElement,\n\treadRootElement,\n} from '#micro509/internal/asn1/der.ts';\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\nimport {\n\tcompareDistinguishedNames,\n\tisWithinDirectoryNameSubtree,\n} from '#micro509/internal/shared/dn.ts';\nimport {\n\tallOnesMaskForIpAddress,\n\tdecodeIpAddress,\n\tparseIpAddressToBytes,\n} from '#micro509/internal/shared/ip.ts';\nimport type { Micro509Error } from '#micro509/result/result.ts';\nimport type { InitialNameConstraintsInput } from '#micro509/verify/name-constraints.ts';\nimport type {\n\tNameConstraintForm,\n\tNameConstraints,\n\tParsedNameConstraintForm,\n\tSubjectAltName,\n} from '#micro509/x509/extensions.ts';\nimport { nameFieldKeyFromOid } from '#micro509/x509/name.ts';\nimport type {\n\tParsedCertificate,\n\tParsedName,\n\tParsedNameAttribute,\n\tParsedRelativeDistinguishedName,\n} from '#micro509/x509/parse.ts';\n\n/**\n * Opaque state seeded from {@linkcode InitialNameConstraintsInput} and consumed\n * by {@linkcode evaluateNameConstraints}.\n */\nexport interface NameConstraintValidationState {\n\t/** Caller-supplied permitted subtree bases (pre-chain). */\n\treadonly initialPermittedSubtrees: readonly NameConstraintForm[];\n\t/** Caller-supplied excluded subtree bases (pre-chain). */\n\treadonly initialExcludedSubtrees: readonly NameConstraintForm[];\n}\n\n/** Discriminant codes for name-constraint validation failures. */\nexport type NameConstraintValidationFailureCode =\n\t| 'name_constraints_violated'\n\t| 'unsupported_name_constraints';\n\n/** Diagnostic context attached to a name-constraint validation failure. */\nexport interface NameConstraintValidationFailureDetails {\n\t/** CN of the certificate whose name violated constraints, if available. */\n\treadonly subjectCommonName?: string;\n\t/** The name or constraint-type string that caused the violation. */\n\treadonly actual?: string;\n}\n\n/** A name-constraint check that failed, with the offending certificate's chain index. */\nexport interface NameConstraintValidationFailure\n\textends Micro509Error<\n\t\tNameConstraintValidationFailureCode,\n\t\tNameConstraintValidationFailureDetails\n\t> {\n\t/** Always `false` for failures. */\n\treadonly ok: false;\n\t/** Zero-based index into the chain of the certificate that violated constraints. */\n\treadonly index: number;\n}\n\n/** Success or failure outcome of name-constraint evaluation across a chain. */\nexport type NameConstraintValidationResult =\n\t| {\n\t\t\t/** All names in the chain satisfy accumulated constraints. */\n\t\t\treadonly ok: true;\n\t }\n\t| NameConstraintValidationFailure;\n\n/** Builder input for assembling optional failure detail fields. */\ninterface NameConstraintValidationFailureDetailsInput {\n\treadonly subjectCommonName?: string | undefined;\n\treadonly actual?: string | undefined;\n}\n\ntype SubjectAltNameCheckableResult =\n\t| {\n\t\t\treadonly ok: true;\n\t\t\treadonly value: NameConstraintForm | undefined;\n\t }\n\t| {\n\t\t\treadonly ok: false;\n\t\t\treadonly actual: string;\n\t };\n\n/** Constructs a {@linkcode NameConstraintValidationFailure} with optional details. */\nfunction nameConstraintFailure(\n\tcode: NameConstraintValidationFailureCode,\n\tmessage: string,\n\tindex: number,\n\tdetails?: NameConstraintValidationFailureDetails,\n): NameConstraintValidationFailure {\n\treturn {\n\t\tok: false,\n\t\tcode,\n\t\tmessage,\n\t\tindex,\n\t\t...(details === undefined ? {} : { details }),\n\t};\n}\n\n/** Strips undefined fields and returns `undefined` when all fields are empty. */\nfunction nameConstraintDetails(\n\tinput: NameConstraintValidationFailureDetailsInput,\n): NameConstraintValidationFailureDetails | undefined {\n\tconst details: NameConstraintValidationFailureDetails = {\n\t\t...(input.subjectCommonName === undefined\n\t\t\t? {}\n\t\t\t: { subjectCommonName: input.subjectCommonName }),\n\t\t...(input.actual === undefined ? {} : { actual: input.actual }),\n\t};\n\treturn Object.keys(details).length === 0 ? undefined : details;\n}\n\n/** A certificate is self-issued when subject and issuer DNs are semantically equal (RFC 5280 §7.1). */\nfunction isSelfIssued(certificate: ParsedCertificate): boolean {\n\treturn compareDistinguishedNames(certificate.subject, certificate.issuer);\n}\n\n/**\n * Initializes validation state from caller-supplied initial constraints.\n *\n * Call once before {@linkcode evaluateNameConstraints}.\n */\nexport function createNameConstraintValidationState(\n\tinput: InitialNameConstraintsInput,\n): NameConstraintValidationState {\n\treturn {\n\t\tinitialPermittedSubtrees: input.permittedSubtrees?.map((subtree) => subtree.base) ?? [],\n\t\tinitialExcludedSubtrees: input.excludedSubtrees?.map((subtree) => subtree.base) ?? [],\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Private: name constraint validation (RFC 5280 §4.2.1.10 / §6.1)\n// ---------------------------------------------------------------------------\n\n/** Empty SEQUENCE DER hex — represents an empty subject DN. */\nconst EMPTY_SEQUENCE_HEX = '3000';\n\n/**\n * Accumulated name constraint state during root-to-leaf traversal.\n * - `permittedLevels`: each entry is one CA's permittedSubtrees. A name\n * must match at least one entry in *every* level (intersection semantics).\n * - `excluded`: flat list; a name must NOT match *any* entry.\n */\ninterface AccumulatedNameConstraints {\n\t/** One entry per CA that asserted permittedSubtrees; intersection semantics. */\n\treadonly permittedLevels: readonly (readonly NameConstraintForm[])[];\n\t/** Flat union of all excludedSubtrees seen so far. */\n\treadonly excluded: readonly NameConstraintForm[];\n}\n\n/**\n * Walks the chain root-to-leaf, accumulating nameConstraints from CA\n * certificates and checking each non-self-issued certificate's names\n * against the accumulated constraints.\n *\n * RFC 5280 §6.1.3(b)–(c) for intermediates, §6.1.5(g) for the leaf.\n */\n\nexport function evaluateNameConstraints(\n\tchain: readonly ParsedCertificate[],\n\tstate: NameConstraintValidationState,\n): NameConstraintValidationResult {\n\tlet accumulated = seedInitialNameConstraints(state);\n\n\t// Seed constraints from the root (trust anchor). The root's own\n\t// names are not checked, but its nameConstraints apply to all\n\t// certificates below it in the chain.\n\tconst root = chain[chain.length - 1];\n\tif (root?.nameConstraints !== undefined) {\n\t\tconst unsupportedRoot = failOnUnsupportedNameConstraints(root, chain.length - 1);\n\t\tif (!unsupportedRoot.ok) {\n\t\t\treturn unsupportedRoot;\n\t\t}\n\t\taccumulated = accumulateConstraints(accumulated, root.nameConstraints);\n\t}\n\n\t// Walk from just below root toward leaf.\n\tfor (let index = chain.length - 2; index >= 0; index -= 1) {\n\t\tconst current = chain[index];\n\t\tif (current === undefined) {\n\t\t\tthrow new Error(`Certificate chain contains undefined at index ${String(index)}`);\n\t\t}\n\n\t\t// (b) If not self-issued, check names against accumulated constraints.\n\t\t// RFC 5280 §4.2.1.10: self-issued certificates are exempt UNLESS\n\t\t// they are the final certificate (leaf) in the path.\n\t\tif (!isSelfIssued(current) || index === 0) {\n\t\t\tconst nameCheckResult = checkCertificateNames(current, accumulated, index);\n\t\t\tif (!nameCheckResult.ok) {\n\t\t\t\treturn nameCheckResult;\n\t\t\t}\n\t\t}\n\n\t\t// (c) If this cert has nameConstraints, accumulate them.\n\t\tif (current.nameConstraints !== undefined) {\n\t\t\tconst unsupportedCurrent = failOnUnsupportedNameConstraints(current, index);\n\t\t\tif (!unsupportedCurrent.ok) {\n\t\t\t\treturn unsupportedCurrent;\n\t\t\t}\n\t\t\taccumulated = accumulateConstraints(accumulated, current.nameConstraints);\n\t\t}\n\t}\n\n\treturn { ok: true };\n}\n\n/** Converts initial state into the starting accumulated-constraints snapshot. */\nfunction seedInitialNameConstraints(\n\tstate: NameConstraintValidationState,\n): AccumulatedNameConstraints {\n\treturn {\n\t\tpermittedLevels:\n\t\t\tstate.initialPermittedSubtrees.length > 0 ? [state.initialPermittedSubtrees] : [],\n\t\texcluded: state.initialExcludedSubtrees,\n\t};\n}\n\n/** Merges one certificate's nameConstraints extension into the running totals. */\nfunction accumulateConstraints(\n\tcurrent: AccumulatedNameConstraints,\n\tconstraints: NameConstraints<ParsedNameConstraintForm>,\n): AccumulatedNameConstraints {\n\tconst permittedLevels =\n\t\tconstraints.permittedSubtrees !== undefined && constraints.permittedSubtrees.length > 0\n\t\t\t? [\n\t\t\t\t\t...current.permittedLevels,\n\t\t\t\t\tconstraints.permittedSubtrees.flatMap((subtree) =>\n\t\t\t\t\t\tisSupportedNameConstraintForm(subtree.base) ? [subtree.base] : [],\n\t\t\t\t\t),\n\t\t\t\t]\n\t\t\t: current.permittedLevels;\n\tconst excluded =\n\t\tconstraints.excludedSubtrees !== undefined && constraints.excludedSubtrees.length > 0\n\t\t\t? [\n\t\t\t\t\t...current.excluded,\n\t\t\t\t\t...constraints.excludedSubtrees.flatMap((subtree) =>\n\t\t\t\t\t\tisSupportedNameConstraintForm(subtree.base) ? [subtree.base] : [],\n\t\t\t\t\t),\n\t\t\t\t]\n\t\t\t: current.excluded;\n\treturn { permittedLevels, excluded };\n}\n\n/** Rejects the chain if a critical nameConstraints extension uses unsupported name forms. */\nfunction failOnUnsupportedNameConstraints(\n\tcertificate: ParsedCertificate,\n\tindex: number,\n): NameConstraintValidationResult {\n\tif (certificate.nameConstraints === undefined) {\n\t\treturn { ok: true };\n\t}\n\tconst hasCriticalNameConstraintsExtension = certificate.extensions.some(\n\t\t(entry) => entry.oid === OIDS.nameConstraints && entry.critical,\n\t);\n\tif (!hasCriticalNameConstraintsExtension) {\n\t\treturn { ok: true };\n\t}\n\tconst unsupportedTypes = listUnsupportedNameConstraintTypes(certificate.nameConstraints);\n\tif (unsupportedTypes.length === 0) {\n\t\treturn { ok: true };\n\t}\n\treturn nameConstraintFailure(\n\t\t'unsupported_name_constraints',\n\t\t'certificate contains unsupported critical name constraints',\n\t\tindex,\n\t\tnameConstraintDetails({\n\t\t\tsubjectCommonName: certificate.subject.values.commonName,\n\t\t\tactual: unsupportedTypes.join(', '),\n\t\t}),\n\t);\n}\n\n/** Collects the distinct unsupported GeneralName type strings from a nameConstraints extension. */\nfunction listUnsupportedNameConstraintTypes(\n\tconstraints: NameConstraints<ParsedNameConstraintForm>,\n): readonly string[] {\n\tconst unsupportedTypes = new Set<string>();\n\tfor (const subtree of constraints.permittedSubtrees ?? []) {\n\t\tif (!isSupportedNameConstraintForm(subtree.base)) {\n\t\t\tunsupportedTypes.add(subtree.base.type);\n\t\t}\n\t}\n\tfor (const subtree of constraints.excludedSubtrees ?? []) {\n\t\tif (!isSupportedNameConstraintForm(subtree.base)) {\n\t\t\tunsupportedTypes.add(subtree.base.type);\n\t\t}\n\t}\n\treturn [...unsupportedTypes];\n}\n\n/** True for name forms this engine can evaluate: dns, email, uri, ip, directoryName. */\nfunction isSupportedNameConstraintForm(form: ParsedNameConstraintForm): form is NameConstraintForm {\n\tswitch (form.type) {\n\t\tcase 'dns':\n\t\tcase 'email':\n\t\tcase 'uri':\n\t\tcase 'ip':\n\t\tcase 'directoryName':\n\t\t\treturn true;\n\t\tcase 'otherName':\n\t\tcase 'x400Address':\n\t\tcase 'ediPartyName':\n\t\tcase 'registeredID':\n\t\t\treturn false;\n\t\tdefault: {\n\t\t\tconst exhaustive: never = form;\n\t\t\tthrow new Error(`Unhandled NameConstraintForm type: ${String(exhaustive)}`);\n\t\t}\n\t}\n}\n\n/**\n * Checks a certificate's subject DN and SANs against accumulated\n * name constraints. Returns a failure if any name violates constraints.\n */\nfunction checkCertificateNames(\n\tcertificate: ParsedCertificate,\n\taccumulated: AccumulatedNameConstraints,\n\tindex: number,\n): NameConstraintValidationResult {\n\t// Check subject DN as directoryName (if non-empty).\n\tif (certificate.subject.derHex !== EMPTY_SEQUENCE_HEX) {\n\t\tconst dnResult = isNamePermitted(\n\t\t\t{ type: 'directoryName', derHex: certificate.subject.derHex },\n\t\t\taccumulated,\n\t\t);\n\t\tif (!dnResult) {\n\t\t\treturn nameConstraintFailure(\n\t\t\t\t'name_constraints_violated',\n\t\t\t\t'subject distinguished name violates name constraints',\n\t\t\t\tindex,\n\t\t\t\tnameConstraintDetails({\n\t\t\t\t\tsubjectCommonName: certificate.subject.values.commonName,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t// Check each SAN.\n\tif (certificate.subjectAltNames !== undefined) {\n\t\tfor (const san of certificate.subjectAltNames) {\n\t\t\tconst checkableResult = sanToConstraintCheckable(san);\n\t\t\tif (!checkableResult.ok) {\n\t\t\t\treturn nameConstraintFailure(\n\t\t\t\t\t'name_constraints_violated',\n\t\t\t\t\t`SAN ${checkableResult.actual} is malformed and cannot be checked against name constraints`,\n\t\t\t\t\tindex,\n\t\t\t\t\tnameConstraintDetails({\n\t\t\t\t\t\tsubjectCommonName: certificate.subject.values.commonName,\n\t\t\t\t\t\tactual: checkableResult.actual,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst checkable = checkableResult.value;\n\t\t\tif (checkable === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isNamePermitted(checkable, accumulated)) {\n\t\t\t\treturn nameConstraintFailure(\n\t\t\t\t\t'name_constraints_violated',\n\t\t\t\t\t`SAN ${formatConstraintForm(checkable)} violates name constraints`,\n\t\t\t\t\tindex,\n\t\t\t\t\tnameConstraintDetails({\n\t\t\t\t\t\tsubjectCommonName: certificate.subject.values.commonName,\n\t\t\t\t\t\tactual: formatConstraintForm(checkable),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t// RFC 5280 §4.2.1.10: When constraints are imposed on the rfc822Name\n\t// name form, but the certificate does not include a SAN email, the\n\t// constraint MUST be applied to the emailAddress attribute in the\n\t// subject DN.\n\tconst hasEmailConstraints = accumulatedHasEmailConstraints(accumulated);\n\tif (hasEmailConstraints) {\n\t\tconst hasSanEmail = certificate.subjectAltNames?.some((san) => san.type === 'email') ?? false;\n\t\tif (!hasSanEmail && certificate.subject.values.emailAddress !== undefined) {\n\t\t\tconst emailForm: NameConstraintForm = {\n\t\t\t\ttype: 'email',\n\t\t\t\tvalue: certificate.subject.values.emailAddress,\n\t\t\t};\n\t\t\tif (!isNamePermitted(emailForm, accumulated)) {\n\t\t\t\treturn nameConstraintFailure(\n\t\t\t\t\t'name_constraints_violated',\n\t\t\t\t\t`subject emailAddress ${certificate.subject.values.emailAddress} violates name constraints`,\n\t\t\t\t\tindex,\n\t\t\t\t\tnameConstraintDetails({\n\t\t\t\t\t\tsubjectCommonName: certificate.subject.values.commonName,\n\t\t\t\t\t\tactual: certificate.subject.values.emailAddress,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { ok: true };\n}\n\n/** True when any level of accumulated constraints addresses the email name form. */\nfunction accumulatedHasEmailConstraints(accumulated: AccumulatedNameConstraints): boolean {\n\tfor (const level of accumulated.permittedLevels) {\n\t\tif (level.some((c) => c.type === 'email')) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn accumulated.excluded.some((c) => c.type === 'email');\n}\n\n/**\n * Converts a SubjectAltName to a NameConstraintForm for checking.\n * Returns `undefined` for name forms that don't participate in\n * constraint checking (unknown tags).\n */\nfunction sanToConstraintCheckable(san: SubjectAltName): SubjectAltNameCheckableResult {\n\tswitch (san.type) {\n\t\tcase 'dns':\n\t\t\treturn { ok: true, value: { type: 'dns', value: san.value } };\n\t\tcase 'email':\n\t\t\treturn { ok: true, value: { type: 'email', value: san.value } };\n\t\tcase 'uri':\n\t\t\treturn { ok: true, value: { type: 'uri', value: san.value } };\n\t\tcase 'srv':\n\t\t\treturn { ok: true, value: undefined };\n\t\tcase 'ip':\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tok: true,\n\t\t\t\t\tvalue: {\n\t\t\t\t\t\ttype: 'ip',\n\t\t\t\t\t\taddressBytes: parseIpAddressToBytes(san.value),\n\t\t\t\t\t\tmaskBytes: allOnesMaskForIpAddress(san.value),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tactual: `ip:${san.value}`,\n\t\t\t\t};\n\t\t\t}\n\t\tcase 'directoryName':\n\t\t\treturn { ok: true, value: { type: 'directoryName', derHex: san.derHex } };\n\t\tcase 'unknown':\n\t\t\treturn { ok: true, value: undefined };\n\t\tdefault: {\n\t\t\tconst exhaustive: never = san;\n\t\t\tthrow new Error(`Unhandled SubjectAltName type: ${String(exhaustive)}`);\n\t\t}\n\t}\n}\n\n/**\n * Checks whether a name is permitted by the accumulated constraints.\n * A name is permitted if:\n * 1. It does NOT match any excluded constraint, AND\n * 2. For every permitted level that contains constraints of the same\n * name form, it matches at least one.\n */\nfunction isNamePermitted(\n\tname: NameConstraintForm,\n\taccumulated: AccumulatedNameConstraints,\n): boolean {\n\t// Check excluded — if any match, reject.\n\tfor (const constraint of accumulated.excluded) {\n\t\tif (nameMatchesConstraint(name, constraint)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\t// Check permitted — for each level with relevant constraints,\n\t// the name must match at least one.\n\tfor (const level of accumulated.permittedLevels) {\n\t\tconst relevant = level.filter((constraint) => constraint.type === name.type);\n\t\tif (relevant.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!relevant.some((constraint) => nameMatchesConstraint(name, constraint))) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/** Dispatches to the type-specific matching function for the name form. */\nfunction nameMatchesConstraint(name: NameConstraintForm, constraint: NameConstraintForm): boolean {\n\tif (name.type === 'dns' && constraint.type === 'dns') {\n\t\treturn matchesDnsConstraint(name.value, constraint.value);\n\t}\n\tif (name.type === 'email' && constraint.type === 'email') {\n\t\treturn matchesEmailConstraint(name.value, constraint.value);\n\t}\n\tif (name.type === 'uri' && constraint.type === 'uri') {\n\t\treturn matchesUriConstraint(name.value, constraint.value);\n\t}\n\tif (name.type === 'ip' && constraint.type === 'ip') {\n\t\treturn matchesIpConstraint(name.addressBytes, constraint.addressBytes, constraint.maskBytes);\n\t}\n\tif (name.type === 'directoryName' && constraint.type === 'directoryName') {\n\t\treturn matchesDnConstraint(name.derHex, constraint.derHex);\n\t}\n\treturn false;\n}\n\n/**\n * RFC 5280 §4.2.1.10: DNS name constraint matching.\n * Constraint \"example.com\" matches \"example.com\" and any subdomain.\n * Constraint \".example.com\" matches only subdomains, not \"example.com\" itself.\n */\nfunction matchesDnsConstraint(name: string, constraint: string): boolean {\n\tconst lowerName = name.toLowerCase();\n\tconst lowerConstraint = constraint.toLowerCase();\n\tif (lowerConstraint.length === 0) {\n\t\treturn true;\n\t}\n\tif (lowerConstraint.startsWith('.')) {\n\t\treturn lowerName.endsWith(lowerConstraint);\n\t}\n\treturn lowerName === lowerConstraint || lowerName.endsWith(`.${lowerConstraint}`);\n}\n\n/**\n * RFC 5280 §4.2.1.10: Email constraint matching.\n * - \"user@example.com\" matches only that exact address.\n * - \"example.com\" matches any address @example.com.\n * - \".example.com\" matches any address @subdomain.example.com.\n */\nfunction matchesEmailConstraint(name: string, constraint: string): boolean {\n\tconst lowerName = name.toLowerCase();\n\tconst lowerConstraint = constraint.toLowerCase();\n\tif (lowerConstraint.includes('@')) {\n\t\treturn lowerName === lowerConstraint;\n\t}\n\tconst atIndex = lowerName.indexOf('@');\n\tif (atIndex < 0) {\n\t\treturn false;\n\t}\n\tconst host = lowerName.slice(atIndex + 1);\n\tif (lowerConstraint.startsWith('.')) {\n\t\treturn host.endsWith(lowerConstraint);\n\t}\n\treturn host === lowerConstraint;\n}\n\n/**\n * RFC 5280 §4.2.1.10: URI constraint matching.\n * Applied to the host part of the URI.\n * - Constraint \".example.com\" matches subdomains only.\n * - Constraint \"example.com\" matches ONLY that exact host (no subdomain\n * expansion, unlike DNS constraints).\n */\nfunction matchesUriConstraint(uri: string, constraint: string): boolean {\n\tconst host = extractUriHost(uri);\n\tif (host === undefined) {\n\t\treturn false;\n\t}\n\tconst lowerHost = host.toLowerCase();\n\tconst lowerConstraint = constraint.toLowerCase();\n\tif (lowerConstraint.length === 0) {\n\t\treturn true;\n\t}\n\tif (lowerConstraint.startsWith('.')) {\n\t\treturn lowerHost.endsWith(lowerConstraint);\n\t}\n\t// Non-period constraint: exact host match only (RFC 5280 §4.2.1.10).\n\treturn lowerHost === lowerConstraint;\n}\n\n/** Extracts the host (reg-name) portion of a URI, stripping scheme, userinfo, port, and path. */\nfunction extractUriHost(uri: string): string | undefined {\n\ttry {\n\t\tconst url = new URL(uri);\n\t\treturn url.hostname;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * RFC 5280 §4.2.1.10: IP constraint matching.\n * (nameiP & mask) == (constraintiP & mask)\n */\nfunction matchesIpConstraint(\n\tnameBytes: Uint8Array,\n\tconstraintAddr: Uint8Array,\n\tconstraintMask: Uint8Array,\n): boolean {\n\tif (nameBytes.length !== constraintAddr.length) {\n\t\treturn false;\n\t}\n\tfor (let i = 0; i < nameBytes.length; i += 1) {\n\t\tconst nameByte = nameBytes[i] ?? 0;\n\t\tconst addrByte = constraintAddr[i] ?? 0;\n\t\tconst maskByte = constraintMask[i] ?? 0;\n\t\tif ((nameByte & maskByte) !== (addrByte & maskByte)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/**\n * RFC 5280 §4.2.1.10: DirectoryName constraint matching.\n * The subject DN must equal or be subordinate to the constraint DN,\n * using RFC 5280 section 7.1 name comparison semantics.\n */\nfunction matchesDnConstraint(subjectDerHex: string, constraintDerHex: string): boolean {\n\tconst subjectName = parseDirectoryNameDerHex(subjectDerHex);\n\tconst constraintName = parseDirectoryNameDerHex(constraintDerHex);\n\tif (subjectName === undefined || constraintName === undefined) {\n\t\treturn false;\n\t}\n\treturn isWithinDirectoryNameSubtree(subjectName, constraintName);\n}\n\n/** Re-parses a hex-encoded DER Name for RDN-by-RDN comparison. Returns `undefined` on malformed input. */\nfunction parseDirectoryNameDerHex(derHex: string): ParsedName | undefined {\n\tif (!/^(?:[0-9a-fA-F]{2})+$/.test(derHex)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst bytes = hexToBytes(derHex);\n\t\tconst root = readRootElement(bytes, { maxDepth: DEFAULT_MAX_DER_DEPTH });\n\t\tconst element = unwrapDirectoryNameElement(bytes, root);\n\t\tif (element.tag !== 0x30) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst rdns: ParsedRelativeDistinguishedName[] = [];\n\t\tconst attributes: ParsedNameAttribute[] = [];\n\t\tconst values: ParsedName['values'] = {};\n\t\tfor (const setElement of childrenOf(bytes, element)) {\n\t\t\tconst rdn = parseDirectoryNameRdn(bytes, setElement);\n\t\t\tif (rdn === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\trdns.push(rdn);\n\t\t\tfor (const attribute of rdn.attributes) {\n\t\t\t\tattributes.push(attribute);\n\t\t\t\tif (attribute.key !== undefined && values[attribute.key] === undefined) {\n\t\t\t\t\tvalues[attribute.key] = attribute.value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tderHex: toHex(bytes),\n\t\t\trdns,\n\t\t\tattributes,\n\t\t\tvalues,\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Unwraps doubly-nested directoryName SEQUENCE encoding.\n *\n * Some encoders produce `[4] { SEQ { SEQ { SET... } } }` instead of `[4] { SEQ { SET... } }`.\n * A single SEQUENCE child indicates the extra wrapper (valid Names have SET children).\n */\nfunction unwrapDirectoryNameElement(source: Uint8Array, element: DerElement): DerElement {\n\tif (element.tag !== 0x30) {\n\t\treturn element;\n\t}\n\tconst children = childrenOf(source, element);\n\tconst child = children[0];\n\tif (children.length === 1 && child?.tag === 0x30) {\n\t\treturn child;\n\t}\n\treturn element;\n}\n\n/** Parses one SET element (a single RDN) from the DER Name SEQUENCE. */\nfunction parseDirectoryNameRdn(\n\tsource: Uint8Array,\n\tsetElement: DerElement,\n): ParsedRelativeDistinguishedName | undefined {\n\tif (setElement.tag !== 0x31) {\n\t\treturn undefined;\n\t}\n\tconst children = childrenOf(source, setElement);\n\tif (children.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst attributes: ParsedNameAttribute[] = [];\n\tconst values: ParsedName['values'] = {};\n\tfor (const attributeSequence of children) {\n\t\tif (attributeSequence.tag !== 0x30) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst parts = childrenOf(source, attributeSequence);\n\t\tconst oidElement = parts[0];\n\t\tconst valueElement = parts[1];\n\t\tif (oidElement === undefined || valueElement === undefined || parts.length !== 2) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (oidElement.tag !== 0x06) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst oid = decodeObjectIdentifier(requireElement(oidElement, 'directoryName OID').value);\n\t\tlet fieldValue: string;\n\t\ttry {\n\t\t\tfieldValue = decodeString(\n\t\t\t\tvalueElement.tag,\n\t\t\t\trequireElement(valueElement, 'directoryName value').value,\n\t\t\t);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst fieldKey = nameFieldKeyFromOid(oid);\n\t\tconst attribute: ParsedNameAttribute =\n\t\t\tfieldKey !== undefined\n\t\t\t\t? { oid, key: fieldKey, valueTag: valueElement.tag, value: fieldValue }\n\t\t\t\t: { oid, valueTag: valueElement.tag, value: fieldValue };\n\t\tattributes.push(attribute);\n\t\tif (fieldKey !== undefined && values[fieldKey] === undefined) {\n\t\t\tvalues[fieldKey] = fieldValue;\n\t\t}\n\t}\n\tif (attributes.length === 0) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tderHex: toHex(source.slice(setElement.start - setElement.headerLength, setElement.end)),\n\t\tattributes,\n\t\tvalues,\n\t};\n}\n\n/** Human-readable label for a constraint form, used in error messages. */\nfunction formatConstraintForm(form: NameConstraintForm): string {\n\tswitch (form.type) {\n\t\tcase 'dns':\n\t\t\treturn `dns:${form.value}`;\n\t\tcase 'email':\n\t\t\treturn `email:${form.value}`;\n\t\tcase 'uri':\n\t\t\treturn `uri:${form.value}`;\n\t\tcase 'ip':\n\t\t\treturn `ip:${decodeIpAddress(form.addressBytes)}`;\n\t\tcase 'directoryName':\n\t\t\treturn `dn:${form.derHex.slice(0, 20)}${form.derHex.length > 20 ? '...' : ''}`;\n\t\tdefault: {\n\t\t\tconst exhaustive = form;\n\t\t\tthrow new Error(`Unhandled NameConstraintForm type: ${String(exhaustive)}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{OIDS as e}from"../asn1/oids.js";import{compareDistinguishedNames as t}from"../shared/dn.js";function n(e,t){let n=t+1;return{initialPolicySet:e.initialPolicySet??`any`,explicitPolicy:e.requireExplicitPolicy===!0?0:n,inhibitPolicyMapping:e.inhibitPolicyMapping===!0?0:n,inhibitAnyPolicy:e.inhibitAnyPolicy===!0?0:n,validPolicyGraph:i()}}function r(e,t){if(e.length===0)throw RangeError(`policy validation requires at least one certificate`);s(e,t);let n=c(e,t);return t.explicitPolicy===0&&n.userConstrainedPolicies.length===0?{ok:!1,error:{code:`explicit_policy_required`,message:`policy validation requires an explicit permitted policy`,details:{expected:t.initialPolicySet===`any`?`explicit policy`:t.initialPolicySet.join(`,`),actual:g(n.userConstrainedPolicies)}}}:t.initialPolicySet!==`any`&&n.userConstrainedPolicies.length===0?{ok:!1,error:{code:`initial_policy_set_not_satisfied`,message:`certificate chain does not satisfy the requested initial policy set`,details:{expected:t.initialPolicySet.join(`,`),actual:g(n.userConstrainedPolicies)}}}:{ok:!0,value:n}}function i(){let t=a(0,e.anyPolicy,void 0,[e.anyPolicy],[]);return{nodesByDepth:[new Map([[o(0,e.anyPolicy),t]])]}}function a(e,t,n,r,i){return{depth:e,validPolicy:t,...n===void 0?{}:{qualifierSet:n},expectedPolicySet:new Set(r),parentKeys:new Set(i),childKeys:new Set}}function o(e,t){return`${String(e)}:${t}`}function s(e,t){if(e.length===1){let n=e[0];if(n===void 0)throw Error(`missing certificate at chain index 0 (chain length ${String(e.length)})`);_(t,n,1,!0);return}let n=e.length-1;for(let r=e.length-2;r>=0;--r){let i=e[r];if(i===void 0)throw Error(`missing certificate at chain index ${String(r)} (chain length ${String(e.length)})`);let a=n-r;_(t,i,a,a===n)}}function c(e,t){let n=l(e,t.validPolicyGraph),r=d(e,t.validPolicyGraph);return{authorityConstrainedPolicies:[...n.values()].sort(h),userConstrainedPolicies:p(n,r,t.initialPolicySet)}}function l(t,n){if(n===null)return new Map;let r=Math.max(1,t.length-1),i=n.nodesByDepth[r];if(i===void 0)return new Map;let a=new Map;for(let[t,r]of i){if(r.validPolicy===e.anyPolicy){a.set(e.anyPolicy,m(e.anyPolicy,r.qualifierSet));continue}u(n,t)&&a.set(r.validPolicy,m(r.validPolicy,r.qualifierSet))}return a}function u(t,n){let r=[n],i=new Set;for(;r.length>0;){let n=r.pop();if(n===void 0||i.has(n))continue;i.add(n);let a=C(t,n);if(a!==void 0)for(let n of a.parentKeys){let i=C(t,n);if(i!==void 0){if(i.depth===0&&i.validPolicy===e.anyPolicy)return!0;r.push(n)}}}return!1}function d(t,n){if(n===null)return new Map;let r=Math.max(1,t.length-1),i=n.nodesByDepth[r];if(i===void 0)return new Map;let a=new Map;for(let[t,r]of i){if(r.validPolicy===e.anyPolicy){a.set(e.anyPolicy,m(e.anyPolicy,r.qualifierSet));continue}f(n,t,a)}return a}function f(t,n,r){let i=[n],a=new Set;for(;i.length>0;){let o=i.pop();if(o===void 0||a.has(o))continue;a.add(o);let s=C(t,o);if(s!==void 0)for(let a of s.parentKeys){let c=C(t,a);if(c!==void 0){if(c.depth===0&&c.validPolicy===e.anyPolicy){r.set(s.validPolicy,m(s.validPolicy,o===n?s.qualifierSet:void 0));continue}i.push(a)}}}}function p(t,n,r){if(r===`any`)return[...t.values()].sort(h);let i=n.get(e.anyPolicy),a=t.has(e.anyPolicy),o=new Map;for(let e of r){let r=n.get(e);if(r!==void 0){o.set(e,r);continue}i!==void 0&&(t.has(e)||a)&&o.set(e,m(e,i.policyQualifiers))}return[...o.values()].sort(h)}function m(e,t){return{policyIdentifier:e,...t===void 0?{}:{policyQualifiers:t}}}function h(e,t){return e.policyIdentifier.localeCompare(t.policyIdentifier)}function g(e){return e.length===0?`<none>`:e.map(e=>e.policyIdentifier).join(`,`)}function _(e,t,n,r){let i=v(t.certificatePolicies);e.validPolicyGraph!==null&&i===void 0?e.validPolicyGraph=null:e.validPolicyGraph!==null&&i!==void 0&&(y(e.validPolicyGraph,i,n,e.inhibitAnyPolicy>0||!r&&k(t)),t.policyMappings!==void 0&&E(e.validPolicyGraph,n,t.policyMappings,e.inhibitPolicyMapping>0)),D(e,t,r)}function v(e){if(e===void 0)return;let t=new Map;for(let n of e)t.has(n.policyIdentifier)||t.set(n.policyIdentifier,n);return t}function y(t,n,r,i){let a=t.nodesByDepth[r-1]??new Map,s=new Map;t.nodesByDepth[r]=s;let c=n.get(e.anyPolicy),l=a.get(o(r-1,e.anyPolicy));for(let i of n.values()){if(i.policyIdentifier===e.anyPolicy)continue;let n=b(a,i.policyIdentifier);n.length>0&&S(t,s,r,i.policyIdentifier,i.policyQualifiers,n,[i.policyIdentifier])}if(l!==void 0){let i=o(r-1,e.anyPolicy);for(let a of n.values())a.policyIdentifier!==e.anyPolicy&&(s.has(o(r,a.policyIdentifier))||S(t,s,r,a.policyIdentifier,a.policyQualifiers,[i],[a.policyIdentifier]))}if(c!==void 0&&i)for(let[n,i]of x(a)){if(s.has(o(r,n)))continue;let a=n===e.anyPolicy?l===void 0?[]:[o(r-1,e.anyPolicy)]:i;a.length!==0&&S(t,s,r,n,c.policyQualifiers,a,[n])}w(t,r-1)}function b(e,t){let n=[];for(let[r,i]of e)i.expectedPolicySet.has(t)&&n.push(r);return n}function x(e){let t=new Map;for(let[n,r]of e)for(let e of r.expectedPolicySet){let r=t.get(e);if(r===void 0){t.set(e,[n]);continue}r.push(n)}return t}function S(e,t,n,r,i,s,c){let l=o(n,r),u=t.get(l);if(u!==void 0){for(let t of s)u.parentKeys.add(t),C(e,t)?.childKeys.add(l);for(let e of c)u.expectedPolicySet.add(e);u.qualifierSet===void 0&&i!==void 0&&(u.qualifierSet=i);return}let d=a(n,r,i,c,s);t.set(l,d);for(let t of s)C(e,t)?.childKeys.add(l)}function C(e,t){let n=t.indexOf(`:`);if(n<=0)return;let r=t.slice(0,n),i=Number.parseInt(r,10);if(!Number.isNaN(i))return e.nodesByDepth[i]?.get(t)}function w(e,t){for(let n=t;n>=0;--n){let t=e.nodesByDepth[n];if(t!==void 0)for(let n of[...t.keys()]){let r=t.get(n);r===void 0||r.childKeys.size>0||T(e,n)}}}function T(e,t){let n=C(e,t);if(n!==void 0){e.nodesByDepth[n.depth]?.delete(t);for(let r of n.parentKeys){let n=C(e,r);n!==void 0&&(n.childKeys.delete(t),n.childKeys.size===0&&T(e,r))}}}function E(t,n,r,i){let a=t.nodesByDepth[n];if(a===void 0)return;let s=new Map;for(let t of r){if(t.issuerDomainPolicy===e.anyPolicy||t.subjectDomainPolicy===e.anyPolicy)continue;let n=s.get(t.issuerDomainPolicy);if(n===void 0){s.set(t.issuerDomainPolicy,[t.subjectDomainPolicy]);continue}n.push(t.subjectDomainPolicy)}let c=a.get(o(n,e.anyPolicy));for(let[e,r]of s){let s=o(n,e),l=a.get(s);if(i){if(l!==void 0){l.expectedPolicySet=new Set(r);continue}c!==void 0&&S(t,a,n,e,c.qualifierSet,[...c.parentKeys],r);continue}l!==void 0&&T(t,s)}w(t,n-1)}function D(e,t,n){if(n){e.explicitPolicy>0&&--e.explicitPolicy,O(t.policyConstraints?.requireExplicitPolicy)&&t.policyConstraints.requireExplicitPolicy===0&&(e.explicitPolicy=0);return}k(t)||(e.explicitPolicy>0&&--e.explicitPolicy,e.inhibitPolicyMapping>0&&--e.inhibitPolicyMapping,e.inhibitAnyPolicy>0&&--e.inhibitAnyPolicy);let r=t.policyConstraints;O(r?.requireExplicitPolicy)&&r.requireExplicitPolicy<e.explicitPolicy&&(e.explicitPolicy=r.requireExplicitPolicy),O(r?.inhibitPolicyMapping)&&r.inhibitPolicyMapping<e.inhibitPolicyMapping&&(e.inhibitPolicyMapping=r.inhibitPolicyMapping),O(t.inhibitAnyPolicy?.skipCerts)&&t.inhibitAnyPolicy.skipCerts<e.inhibitAnyPolicy&&(e.inhibitAnyPolicy=t.inhibitAnyPolicy.skipCerts)}function O(e){return e!==void 0&&Number.isInteger(e)&&e>=0}function k(e){return t(e.subject,e.issuer)}export{n as createPolicyValidationState,r as evaluatePolicyChain};
|
|
2
|
+
//# sourceMappingURL=policy-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-engine.js","names":[],"sources":["../../../src/internal/verify/policy-engine.ts"],"sourcesContent":["/**\n * Internal RFC 9618 policy-validation engine.\n *\n * Tracks the policy graph and counter state through each certificate in a\n * chain, then derives the {@linkcode PolicyValidationOutcome}.\n *\n * @module\n */\n\nimport { OIDS } from '#micro509/internal/asn1/oids.ts';\nimport { compareDistinguishedNames } from '#micro509/internal/shared/dn.ts';\nimport type { Micro509Error, Result } from '#micro509/result/result.ts';\nimport type {\n\tConstrainedPolicy,\n\tPolicyValidationInput,\n\tPolicyValidationOutcome,\n} from '#micro509/verify/policy.ts';\nimport type { PolicyInformation, PolicyQualifierInfo } from '#micro509/x509/extensions.ts';\nimport type { ParsedCertificate } from '#micro509/x509/parse.ts';\n\n/**\n * Mutable state threaded through each step of the policy-validation walk.\n *\n * Created by {@linkcode createPolicyValidationState}, consumed by\n * {@linkcode evaluatePolicyChain}.\n */\nexport interface PolicyValidationState {\n\t/** Caller-requested acceptable policies, or `'any'`. */\n\treadonly initialPolicySet: readonly string[] | 'any';\n\t/** Remaining certificates before explicit-policy enforcement kicks in. */\n\texplicitPolicy: number;\n\t/** Remaining certificates before policy mappings are disallowed. */\n\tinhibitPolicyMapping: number;\n\t/** Remaining certificates before anyPolicy expansion stops. */\n\tinhibitAnyPolicy: number;\n\t/** The RFC 9618 policy graph; `null` when the graph has been emptied. */\n\tvalidPolicyGraph: PolicyGraph | null;\n}\n\n/** Discriminant codes for policy-validation failures. */\nexport type PolicyValidationFailureCode =\n\t| 'explicit_policy_required'\n\t| 'initial_policy_set_not_satisfied';\n\n/** Diagnostic context for a policy-validation failure. */\nexport interface PolicyValidationFailureDetails {\n\t/** Description of the policies the caller expected. */\n\treadonly expected: string;\n\t/** Comma-joined OIDs (or `\"<none>\"`) that actually survived processing. */\n\treadonly actual: string;\n}\n\n/** A policy-validation failure with structured diagnostic details. */\nexport interface PolicyValidationFailure\n\textends Micro509Error<PolicyValidationFailureCode, PolicyValidationFailureDetails> {}\n\n/** Success with {@linkcode PolicyValidationOutcome}, or a {@linkcode PolicyValidationFailure}. */\nexport type PolicyValidationResult = Result<PolicyValidationOutcome, PolicyValidationFailure>;\n\n/** One node in the RFC 9618 policy graph, keyed by `depth:policyOID`. */\ninterface PolicyGraphNode {\n\t/** Graph depth (0 = root anyPolicy node). */\n\tdepth: number;\n\t/** The policy OID this node represents at its depth. */\n\tvalidPolicy: string;\n\t/** Policy qualifiers inherited from the certificate that created this node. */\n\tqualifierSet?: readonly PolicyQualifierInfo[];\n\t/** OIDs this node expects to see in the next certificate's policies. */\n\texpectedPolicySet: Set<string>;\n\t/** Keys of parent nodes in the previous depth level. */\n\tparentKeys: Set<string>;\n\t/** Keys of child nodes in the next depth level. */\n\tchildKeys: Set<string>;\n}\n\n/** The full RFC 9618 valid-policy graph, indexed by depth level. */\ninterface PolicyGraph {\n\t/** Each index `i` holds the nodes at depth `i`, keyed by `\"depth:oid\"`. */\n\tnodesByDepth: Map<string, PolicyGraphNode>[];\n}\n\n/**\n * Initializes mutable policy state from caller options.\n *\n * Counter values default to `chainLength + 1` (effectively disabled) unless\n * the corresponding input flag is `true`.\n */\nexport function createPolicyValidationState(\n\tinput: PolicyValidationInput,\n\tchainLength: number,\n): PolicyValidationState {\n\tconst disabledCounter = chainLength + 1;\n\treturn {\n\t\tinitialPolicySet: input.initialPolicySet ?? 'any',\n\t\texplicitPolicy: input.requireExplicitPolicy === true ? 0 : disabledCounter,\n\t\tinhibitPolicyMapping: input.inhibitPolicyMapping === true ? 0 : disabledCounter,\n\t\tinhibitAnyPolicy: input.inhibitAnyPolicy === true ? 0 : disabledCounter,\n\t\tvalidPolicyGraph: createInitialPolicyGraph(),\n\t};\n}\n\n/**\n * Walks the chain root-to-leaf, updating policy state per certificate,\n * then checks whether the resulting policy set satisfies caller requirements.\n */\nexport function evaluatePolicyChain(\n\tchain: readonly ParsedCertificate[],\n\tstate: PolicyValidationState,\n): PolicyValidationResult {\n\tif (chain.length === 0) {\n\t\tthrow new RangeError('policy validation requires at least one certificate');\n\t}\n\tprocessPolicyState(chain, state);\n\tconst outcome = derivePolicyValidationOutcome(chain, state);\n\tif (state.explicitPolicy === 0 && outcome.userConstrainedPolicies.length === 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: 'explicit_policy_required',\n\t\t\t\tmessage: 'policy validation requires an explicit permitted policy',\n\t\t\t\tdetails: {\n\t\t\t\t\texpected:\n\t\t\t\t\t\tstate.initialPolicySet === 'any' ? 'explicit policy' : state.initialPolicySet.join(','),\n\t\t\t\t\tactual: describeFinalPolicies(outcome.userConstrainedPolicies),\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (state.initialPolicySet !== 'any' && outcome.userConstrainedPolicies.length === 0) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: 'initial_policy_set_not_satisfied',\n\t\t\t\tmessage: 'certificate chain does not satisfy the requested initial policy set',\n\t\t\t\tdetails: {\n\t\t\t\t\texpected: state.initialPolicySet.join(','),\n\t\t\t\t\tactual: describeFinalPolicies(outcome.userConstrainedPolicies),\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\treturn { ok: true, value: outcome };\n}\n\n/** Builds the depth-0 graph with a single anyPolicy root node. */\nfunction createInitialPolicyGraph(): PolicyGraph {\n\tconst rootNode = createPolicyGraphNode(0, OIDS.anyPolicy, undefined, [OIDS.anyPolicy], []);\n\treturn {\n\t\tnodesByDepth: [new Map([[policyNodeKey(0, OIDS.anyPolicy), rootNode]])],\n\t};\n}\n\n/** Constructs a new {@linkcode PolicyGraphNode} with empty child-key set. */\nfunction createPolicyGraphNode(\n\tdepth: number,\n\tvalidPolicy: string,\n\tqualifierSet: readonly PolicyQualifierInfo[] | undefined,\n\texpectedPolicySet: readonly string[],\n\tparentKeys: readonly string[],\n): PolicyGraphNode {\n\treturn {\n\t\tdepth,\n\t\tvalidPolicy,\n\t\t...(qualifierSet === undefined ? {} : { qualifierSet }),\n\t\texpectedPolicySet: new Set(expectedPolicySet),\n\t\tparentKeys: new Set(parentKeys),\n\t\tchildKeys: new Set<string>(),\n\t};\n}\n\n/** Canonical map key for a graph node: `\"depth:policyOID\"`. */\nfunction policyNodeKey(depth: number, validPolicy: string): string {\n\treturn `${String(depth)}:${validPolicy}`;\n}\n\n/** Iterates root-to-leaf (skipping root), applying each certificate's policies to the state. */\nfunction processPolicyState(\n\tchain: readonly ParsedCertificate[],\n\tstate: PolicyValidationState,\n): void {\n\tif (chain.length === 1) {\n\t\tconst certificate = chain[0];\n\t\tif (certificate === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`missing certificate at chain index 0 (chain length ${String(chain.length)})`,\n\t\t\t);\n\t\t}\n\t\tprocessPolicyCertificate(state, certificate, 1, true);\n\t\treturn;\n\t}\n\tconst leafDepth = chain.length - 1;\n\tfor (let index = chain.length - 2; index >= 0; index -= 1) {\n\t\tconst certificate = chain[index];\n\t\tif (certificate === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`missing certificate at chain index ${String(index)} (chain length ${String(chain.length)})`,\n\t\t\t);\n\t\t}\n\t\tconst depth = leafDepth - index;\n\t\tprocessPolicyCertificate(state, certificate, depth, depth === leafDepth);\n\t}\n}\n\n/** Extracts the final authority- and user-constrained policy sets from the completed graph. */\nfunction derivePolicyValidationOutcome(\n\tchain: readonly ParsedCertificate[],\n\tstate: PolicyValidationState,\n): PolicyValidationOutcome {\n\tconst authorityConstrainedPolicies = collectAuthorityConstrainedPolicies(\n\t\tchain,\n\t\tstate.validPolicyGraph,\n\t);\n\tconst rootDomainPolicies = collectRootDomainPolicies(chain, state.validPolicyGraph);\n\treturn {\n\t\tauthorityConstrainedPolicies: [...authorityConstrainedPolicies.values()].sort(comparePolicies),\n\t\tuserConstrainedPolicies: deriveUserConstrainedPolicies(\n\t\t\tauthorityConstrainedPolicies,\n\t\t\trootDomainPolicies,\n\t\t\tstate.initialPolicySet,\n\t\t),\n\t};\n}\n\n/** Collects leaf-depth policies whose graph paths trace back to the anyPolicy root. */\nfunction collectAuthorityConstrainedPolicies(\n\tchain: readonly ParsedCertificate[],\n\tgraph: PolicyGraph | null,\n): ReadonlyMap<string, ConstrainedPolicy> {\n\tif (graph === null) {\n\t\treturn new Map<string, ConstrainedPolicy>();\n\t}\n\tconst leafDepth = Math.max(1, chain.length - 1);\n\tconst finalDepth = graph.nodesByDepth[leafDepth];\n\tif (finalDepth === undefined) {\n\t\treturn new Map<string, ConstrainedPolicy>();\n\t}\n\tconst authorityPolicies = new Map<string, ConstrainedPolicy>();\n\tfor (const [key, node] of finalDepth) {\n\t\tif (node.validPolicy === OIDS.anyPolicy) {\n\t\t\tauthorityPolicies.set(\n\t\t\t\tOIDS.anyPolicy,\n\t\t\t\tbuildConstrainedPolicy(OIDS.anyPolicy, node.qualifierSet),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tif (reachesAuthorityRoot(graph, key)) {\n\t\t\tauthorityPolicies.set(\n\t\t\t\tnode.validPolicy,\n\t\t\t\tbuildConstrainedPolicy(node.validPolicy, node.qualifierSet),\n\t\t\t);\n\t\t}\n\t}\n\treturn authorityPolicies;\n}\n\n/** DFS upward through parent links to check if the node ultimately connects to the depth-0 anyPolicy root. */\nfunction reachesAuthorityRoot(graph: PolicyGraph, nodeKey: string): boolean {\n\tconst pending = [nodeKey];\n\tconst visited = new Set<string>();\n\twhile (pending.length > 0) {\n\t\tconst currentKey = pending.pop();\n\t\tif (currentKey === undefined || visited.has(currentKey)) {\n\t\t\tcontinue;\n\t\t}\n\t\tvisited.add(currentKey);\n\t\tconst node = getPolicyGraphNode(graph, currentKey);\n\t\tif (node === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const parentKey of node.parentKeys) {\n\t\t\tconst parent = getPolicyGraphNode(graph, parentKey);\n\t\t\tif (parent === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (parent.depth === 0 && parent.validPolicy === OIDS.anyPolicy) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tpending.push(parentKey);\n\t\t}\n\t}\n\treturn false;\n}\n\n/** Like authority-constrained, but records the OID at the depth-1 node that connects to root. */\nfunction collectRootDomainPolicies(\n\tchain: readonly ParsedCertificate[],\n\tgraph: PolicyGraph | null,\n): ReadonlyMap<string, ConstrainedPolicy> {\n\tif (graph === null) {\n\t\treturn new Map<string, ConstrainedPolicy>();\n\t}\n\tconst leafDepth = Math.max(1, chain.length - 1);\n\tconst finalDepth = graph.nodesByDepth[leafDepth];\n\tif (finalDepth === undefined) {\n\t\treturn new Map<string, ConstrainedPolicy>();\n\t}\n\tconst rootPolicies = new Map<string, ConstrainedPolicy>();\n\tfor (const [key, node] of finalDepth) {\n\t\tif (node.validPolicy === OIDS.anyPolicy) {\n\t\t\trootPolicies.set(OIDS.anyPolicy, buildConstrainedPolicy(OIDS.anyPolicy, node.qualifierSet));\n\t\t\tcontinue;\n\t\t}\n\t\tcollectAuthorityConstrainedPolicyRoots(graph, key, rootPolicies);\n\t}\n\treturn rootPolicies;\n}\n\n/** Walks parent links upward, collecting the depth-1 OID that first connects to the root anyPolicy. */\nfunction collectAuthorityConstrainedPolicyRoots(\n\tgraph: PolicyGraph,\n\tnodeKey: string,\n\tauthorityPolicies: Map<string, ConstrainedPolicy>,\n): void {\n\tconst pending = [nodeKey];\n\tconst visited = new Set<string>();\n\twhile (pending.length > 0) {\n\t\tconst currentKey = pending.pop();\n\t\tif (currentKey === undefined || visited.has(currentKey)) {\n\t\t\tcontinue;\n\t\t}\n\t\tvisited.add(currentKey);\n\t\tconst node = getPolicyGraphNode(graph, currentKey);\n\t\tif (node === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const parentKey of node.parentKeys) {\n\t\t\tconst parent = getPolicyGraphNode(graph, parentKey);\n\t\t\tif (parent === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (parent.depth === 0 && parent.validPolicy === OIDS.anyPolicy) {\n\t\t\t\t// Record the depth-1 policy that connects to root\n\t\t\t\t// If the depth-1 node is anyPolicy itself, record anyPolicy so it can satisfy\n\t\t\t\t// any policy in the initial-policy-set via deriveUserConstrainedPolicies\n\t\t\t\tauthorityPolicies.set(\n\t\t\t\t\tnode.validPolicy,\n\t\t\t\t\tbuildConstrainedPolicy(\n\t\t\t\t\t\tnode.validPolicy,\n\t\t\t\t\t\tcurrentKey === nodeKey ? node.qualifierSet : undefined,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpending.push(parentKey);\n\t\t}\n\t}\n}\n\n/** Intersects root-domain policies with the caller's initial-policy-set. */\nfunction deriveUserConstrainedPolicies(\n\tfinalAuthorityConstrainedPolicies: ReadonlyMap<string, ConstrainedPolicy>,\n\trootDomainPolicies: ReadonlyMap<string, ConstrainedPolicy>,\n\tinitialPolicySet: readonly string[] | 'any',\n): readonly ConstrainedPolicy[] {\n\tif (initialPolicySet === 'any') {\n\t\treturn [...finalAuthorityConstrainedPolicies.values()].sort(comparePolicies);\n\t}\n\tconst anyPolicy = rootDomainPolicies.get(OIDS.anyPolicy);\n\t// If the EE asserts anyPolicy, it satisfies any policy in initial-policy-set\n\tconst eeHasAnyPolicy = finalAuthorityConstrainedPolicies.has(OIDS.anyPolicy);\n\tconst constrained = new Map<string, ConstrainedPolicy>();\n\tfor (const policyIdentifier of initialPolicySet) {\n\t\tconst direct = rootDomainPolicies.get(policyIdentifier);\n\t\tif (direct !== undefined) {\n\t\t\tconstrained.set(policyIdentifier, direct);\n\t\t\tcontinue;\n\t\t}\n\t\t// anyPolicy can satisfy a requested policy if:\n\t\t// 1. The EE asserts the specific policy (finalAuthorityConstrainedPolicies.has), OR\n\t\t// 2. The EE itself asserts anyPolicy (eeHasAnyPolicy)\n\t\tif (\n\t\t\tanyPolicy !== undefined &&\n\t\t\t(finalAuthorityConstrainedPolicies.has(policyIdentifier) || eeHasAnyPolicy)\n\t\t) {\n\t\t\tconstrained.set(\n\t\t\t\tpolicyIdentifier,\n\t\t\t\tbuildConstrainedPolicy(policyIdentifier, anyPolicy.policyQualifiers),\n\t\t\t);\n\t\t}\n\t}\n\treturn [...constrained.values()].sort(comparePolicies);\n}\n\n/** Constructs a {@linkcode ConstrainedPolicy}, omitting qualifiers when absent. */\nfunction buildConstrainedPolicy(\n\tpolicyIdentifier: string,\n\tpolicyQualifiers: readonly PolicyQualifierInfo[] | undefined,\n): ConstrainedPolicy {\n\treturn {\n\t\tpolicyIdentifier,\n\t\t...(policyQualifiers === undefined ? {} : { policyQualifiers }),\n\t};\n}\n\n/** Lexicographic comparator for sorting policies by OID string. */\nfunction comparePolicies(left: ConstrainedPolicy, right: ConstrainedPolicy): number {\n\treturn left.policyIdentifier.localeCompare(right.policyIdentifier);\n}\n\n/** Human-readable comma-joined OID list for error messages; `\"<none>\"` when empty. */\nfunction describeFinalPolicies(policies: readonly ConstrainedPolicy[]): string {\n\treturn policies.length === 0\n\t\t? '<none>'\n\t\t: policies.map((policy) => policy.policyIdentifier).join(',');\n}\n\n/** Applies one certificate's policies, mappings, and constraints to the running state. */\nfunction processPolicyCertificate(\n\tstate: PolicyValidationState,\n\tcertificate: ParsedCertificate,\n\tdepth: number,\n\tisLeaf: boolean,\n): void {\n\tconst certificatePolicies = normalizeCertificatePolicies(certificate.certificatePolicies);\n\tif (state.validPolicyGraph !== null && certificatePolicies === undefined) {\n\t\tstate.validPolicyGraph = null;\n\t} else if (state.validPolicyGraph !== null && certificatePolicies !== undefined) {\n\t\tapplyCertificatePolicyStep(\n\t\t\tstate.validPolicyGraph,\n\t\t\tcertificatePolicies,\n\t\t\tdepth,\n\t\t\tstate.inhibitAnyPolicy > 0 || (!isLeaf && isSelfIssued(certificate)),\n\t\t);\n\t\tif (certificate.policyMappings !== undefined) {\n\t\t\tapplyPolicyMappingsStep(\n\t\t\t\tstate.validPolicyGraph,\n\t\t\t\tdepth,\n\t\t\t\tcertificate.policyMappings,\n\t\t\t\tstate.inhibitPolicyMapping > 0,\n\t\t\t);\n\t\t}\n\t}\n\tupdatePolicyCounters(state, certificate, isLeaf);\n}\n\n/** Deduplicates certificate policies by OID, keeping the first occurrence. */\nfunction normalizeCertificatePolicies(\n\tpolicies: readonly PolicyInformation[] | undefined,\n): Map<string, PolicyInformation> | undefined {\n\tif (policies === undefined) {\n\t\treturn undefined;\n\t}\n\tconst byOid = new Map<string, PolicyInformation>();\n\tfor (const policy of policies) {\n\t\tif (!byOid.has(policy.policyIdentifier)) {\n\t\t\tbyOid.set(policy.policyIdentifier, policy);\n\t\t}\n\t}\n\treturn byOid;\n}\n\n/** Extends the policy graph by one depth level using a certificate's policy extension. */\nfunction applyCertificatePolicyStep(\n\tgraph: PolicyGraph,\n\tcertificatePolicies: ReadonlyMap<string, PolicyInformation>,\n\tdepth: number,\n\tallowAnyPolicyExpansion: boolean,\n): void {\n\tconst previousDepth = graph.nodesByDepth[depth - 1] ?? new Map<string, PolicyGraphNode>();\n\tconst currentDepth = new Map<string, PolicyGraphNode>();\n\tgraph.nodesByDepth[depth] = currentDepth;\n\tconst anyPolicyInfo = certificatePolicies.get(OIDS.anyPolicy);\n\tconst previousAnyPolicy = previousDepth.get(policyNodeKey(depth - 1, OIDS.anyPolicy));\n\n\tfor (const policy of certificatePolicies.values()) {\n\t\tif (policy.policyIdentifier === OIDS.anyPolicy) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst matchingParents = collectParentsForExpectedPolicy(previousDepth, policy.policyIdentifier);\n\t\tif (matchingParents.length > 0) {\n\t\t\taddOrMergePolicyNode(\n\t\t\t\tgraph,\n\t\t\t\tcurrentDepth,\n\t\t\t\tdepth,\n\t\t\t\tpolicy.policyIdentifier,\n\t\t\t\tpolicy.policyQualifiers,\n\t\t\t\tmatchingParents,\n\t\t\t\t[policy.policyIdentifier],\n\t\t\t);\n\t\t}\n\t}\n\n\tif (previousAnyPolicy !== undefined) {\n\t\tconst previousAnyPolicyKey = policyNodeKey(depth - 1, OIDS.anyPolicy);\n\t\tfor (const policy of certificatePolicies.values()) {\n\t\t\tif (policy.policyIdentifier === OIDS.anyPolicy) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (currentDepth.has(policyNodeKey(depth, policy.policyIdentifier))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\taddOrMergePolicyNode(\n\t\t\t\tgraph,\n\t\t\t\tcurrentDepth,\n\t\t\t\tdepth,\n\t\t\t\tpolicy.policyIdentifier,\n\t\t\t\tpolicy.policyQualifiers,\n\t\t\t\t[previousAnyPolicyKey],\n\t\t\t\t[policy.policyIdentifier],\n\t\t\t);\n\t\t}\n\t}\n\n\tif (anyPolicyInfo !== undefined && allowAnyPolicyExpansion) {\n\t\tfor (const [validPolicy, parentKeys] of collectExpectedPolicyParents(previousDepth)) {\n\t\t\tif (currentDepth.has(policyNodeKey(depth, validPolicy))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst expansionParents =\n\t\t\t\tvalidPolicy === OIDS.anyPolicy\n\t\t\t\t\t? previousAnyPolicy === undefined\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [policyNodeKey(depth - 1, OIDS.anyPolicy)]\n\t\t\t\t\t: parentKeys;\n\t\t\tif (expansionParents.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\taddOrMergePolicyNode(\n\t\t\t\tgraph,\n\t\t\t\tcurrentDepth,\n\t\t\t\tdepth,\n\t\t\t\tvalidPolicy,\n\t\t\t\tanyPolicyInfo.policyQualifiers,\n\t\t\t\texpansionParents,\n\t\t\t\t[validPolicy],\n\t\t\t);\n\t\t}\n\t}\n\n\tprunePolicyGraph(graph, depth - 1);\n}\n\n/** Finds all previous-depth nodes whose expectedPolicySet contains the given OID. */\nfunction collectParentsForExpectedPolicy(\n\tnodes: ReadonlyMap<string, PolicyGraphNode>,\n\tpolicyIdentifier: string,\n): string[] {\n\tconst parents: string[] = [];\n\tfor (const [key, node] of nodes) {\n\t\tif (node.expectedPolicySet.has(policyIdentifier)) {\n\t\t\tparents.push(key);\n\t\t}\n\t}\n\treturn parents;\n}\n\n/** Groups all previous-depth nodes by each OID in their expectedPolicySet. */\nfunction collectExpectedPolicyParents(\n\tnodes: ReadonlyMap<string, PolicyGraphNode>,\n): Map<string, string[]> {\n\tconst parentsByPolicy = new Map<string, string[]>();\n\tfor (const [key, node] of nodes) {\n\t\tfor (const expectedPolicy of node.expectedPolicySet) {\n\t\t\tconst parents = parentsByPolicy.get(expectedPolicy);\n\t\t\tif (parents === undefined) {\n\t\t\t\tparentsByPolicy.set(expectedPolicy, [key]);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tparents.push(key);\n\t\t}\n\t}\n\treturn parentsByPolicy;\n}\n\n/** Inserts a node at the current depth, or merges parents/expected-policies into an existing one. */\nfunction addOrMergePolicyNode(\n\tgraph: PolicyGraph,\n\tcurrentDepth: Map<string, PolicyGraphNode>,\n\tdepth: number,\n\tvalidPolicy: string,\n\tqualifierSet: readonly PolicyQualifierInfo[] | undefined,\n\tparentKeys: readonly string[],\n\texpectedPolicySet: readonly string[],\n): void {\n\tconst key = policyNodeKey(depth, validPolicy);\n\tconst existing = currentDepth.get(key);\n\tif (existing !== undefined) {\n\t\tfor (const parentKey of parentKeys) {\n\t\t\texisting.parentKeys.add(parentKey);\n\t\t\tgetPolicyGraphNode(graph, parentKey)?.childKeys.add(key);\n\t\t}\n\t\tfor (const expectedPolicy of expectedPolicySet) {\n\t\t\texisting.expectedPolicySet.add(expectedPolicy);\n\t\t}\n\t\tif (existing.qualifierSet === undefined && qualifierSet !== undefined) {\n\t\t\texisting.qualifierSet = qualifierSet;\n\t\t}\n\t\treturn;\n\t}\n\tconst node = createPolicyGraphNode(\n\t\tdepth,\n\t\tvalidPolicy,\n\t\tqualifierSet,\n\t\texpectedPolicySet,\n\t\tparentKeys,\n\t);\n\tcurrentDepth.set(key, node);\n\tfor (const parentKey of parentKeys) {\n\t\tgetPolicyGraphNode(graph, parentKey)?.childKeys.add(key);\n\t}\n}\n\n/** Looks up a node by its `\"depth:oid\"` key, parsing the depth prefix. */\nfunction getPolicyGraphNode(graph: PolicyGraph, key: string): PolicyGraphNode | undefined {\n\tconst separator = key.indexOf(':');\n\tif (separator <= 0) {\n\t\treturn undefined;\n\t}\n\tconst depthString = key.slice(0, separator);\n\tconst depth = Number.parseInt(depthString, 10);\n\tif (Number.isNaN(depth)) {\n\t\treturn undefined;\n\t}\n\treturn graph.nodesByDepth[depth]?.get(key);\n}\n\n/** Removes childless leaf nodes bottom-up, cascading deletions through parent links. */\nfunction prunePolicyGraph(graph: PolicyGraph, maxDepth: number): void {\n\tfor (let depth = maxDepth; depth >= 0; depth -= 1) {\n\t\tconst nodes = graph.nodesByDepth[depth];\n\t\tif (nodes === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const key of [...nodes.keys()]) {\n\t\t\tconst node = nodes.get(key);\n\t\t\tif (node === undefined || node.childKeys.size > 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tdeletePolicyGraphNode(graph, key);\n\t\t}\n\t}\n}\n\n/** Removes a node and recursively prunes its parents if they become childless. */\nfunction deletePolicyGraphNode(graph: PolicyGraph, key: string): void {\n\tconst node = getPolicyGraphNode(graph, key);\n\tif (node === undefined) {\n\t\treturn;\n\t}\n\tgraph.nodesByDepth[node.depth]?.delete(key);\n\tfor (const parentKey of node.parentKeys) {\n\t\tconst parent = getPolicyGraphNode(graph, parentKey);\n\t\tif (parent === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tparent.childKeys.delete(key);\n\t\tif (parent.childKeys.size === 0) {\n\t\t\tdeletePolicyGraphNode(graph, parentKey);\n\t\t}\n\t}\n}\n\n/** Applies issuer→subject policy mappings at the current depth, or deletes mapped nodes when disallowed. */\nfunction applyPolicyMappingsStep(\n\tgraph: PolicyGraph,\n\tdepth: number,\n\tmappings: readonly {\n\t\treadonly issuerDomainPolicy: string;\n\t\treadonly subjectDomainPolicy: string;\n\t}[],\n\tmappingAllowed: boolean,\n): void {\n\tconst currentDepth = graph.nodesByDepth[depth];\n\tif (currentDepth === undefined) {\n\t\treturn;\n\t}\n\tconst groupedMappings = new Map<string, string[]>();\n\tfor (const mapping of mappings) {\n\t\tif (\n\t\t\tmapping.issuerDomainPolicy === OIDS.anyPolicy ||\n\t\t\tmapping.subjectDomainPolicy === OIDS.anyPolicy\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst subjectPolicies = groupedMappings.get(mapping.issuerDomainPolicy);\n\t\tif (subjectPolicies === undefined) {\n\t\t\tgroupedMappings.set(mapping.issuerDomainPolicy, [mapping.subjectDomainPolicy]);\n\t\t\tcontinue;\n\t\t}\n\t\tsubjectPolicies.push(mapping.subjectDomainPolicy);\n\t}\n\tconst anyPolicyNode = currentDepth.get(policyNodeKey(depth, OIDS.anyPolicy));\n\tfor (const [issuerDomainPolicy, subjectDomainPolicies] of groupedMappings) {\n\t\tconst nodeKey = policyNodeKey(depth, issuerDomainPolicy);\n\t\tconst node = currentDepth.get(nodeKey);\n\t\tif (mappingAllowed) {\n\t\t\tif (node !== undefined) {\n\t\t\t\tnode.expectedPolicySet = new Set(subjectDomainPolicies);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (anyPolicyNode !== undefined) {\n\t\t\t\taddOrMergePolicyNode(\n\t\t\t\t\tgraph,\n\t\t\t\t\tcurrentDepth,\n\t\t\t\t\tdepth,\n\t\t\t\t\tissuerDomainPolicy,\n\t\t\t\t\tanyPolicyNode.qualifierSet,\n\t\t\t\t\t[...anyPolicyNode.parentKeys],\n\t\t\t\t\tsubjectDomainPolicies,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (node !== undefined) {\n\t\t\tdeletePolicyGraphNode(graph, nodeKey);\n\t\t}\n\t}\n\tprunePolicyGraph(graph, depth - 1);\n}\n\n/** Decrements explicitPolicy / inhibitPolicyMapping / inhibitAnyPolicy and applies policyConstraints overrides. */\nfunction updatePolicyCounters(\n\tstate: PolicyValidationState,\n\tcertificate: ParsedCertificate,\n\tisLeaf: boolean,\n): void {\n\tif (isLeaf) {\n\t\tif (state.explicitPolicy > 0) {\n\t\t\tstate.explicitPolicy -= 1;\n\t\t}\n\t\tif (isNonNegativeInteger(certificate.policyConstraints?.requireExplicitPolicy)) {\n\t\t\tif (certificate.policyConstraints.requireExplicitPolicy === 0) {\n\t\t\t\tstate.explicitPolicy = 0;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\tif (!isSelfIssued(certificate)) {\n\t\tif (state.explicitPolicy > 0) {\n\t\t\tstate.explicitPolicy -= 1;\n\t\t}\n\t\tif (state.inhibitPolicyMapping > 0) {\n\t\t\tstate.inhibitPolicyMapping -= 1;\n\t\t}\n\t\tif (state.inhibitAnyPolicy > 0) {\n\t\t\tstate.inhibitAnyPolicy -= 1;\n\t\t}\n\t}\n\tconst policyConstraints = certificate.policyConstraints;\n\tif (\n\t\tisNonNegativeInteger(policyConstraints?.requireExplicitPolicy) &&\n\t\tpolicyConstraints.requireExplicitPolicy < state.explicitPolicy\n\t) {\n\t\tstate.explicitPolicy = policyConstraints.requireExplicitPolicy;\n\t}\n\tif (\n\t\tisNonNegativeInteger(policyConstraints?.inhibitPolicyMapping) &&\n\t\tpolicyConstraints.inhibitPolicyMapping < state.inhibitPolicyMapping\n\t) {\n\t\tstate.inhibitPolicyMapping = policyConstraints.inhibitPolicyMapping;\n\t}\n\tif (\n\t\tisNonNegativeInteger(certificate.inhibitAnyPolicy?.skipCerts) &&\n\t\tcertificate.inhibitAnyPolicy.skipCerts < state.inhibitAnyPolicy\n\t) {\n\t\tstate.inhibitAnyPolicy = certificate.inhibitAnyPolicy.skipCerts;\n\t}\n}\n\nfunction isNonNegativeInteger(value: number | undefined): value is number {\n\treturn value !== undefined && Number.isInteger(value) && value >= 0;\n}\n\n/** A certificate is self-issued when subject and issuer DNs are semantically equal (RFC 5280 §7.1). */\nfunction isSelfIssued(certificate: ParsedCertificate): boolean {\n\treturn compareDistinguishedNames(certificate.subject, certificate.issuer);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{toHex as e}from"../asn1/asn1.js";import{parseCertificatesFromSource as t}from"../../x509/parse.js";import{verifySignedDataDetailed as n}from"../crypto/sig-verify.js";import{canonicalDnKey as r,compareDistinguishedNames as i}from"../shared/dn.js";function a(e,t){return e.notBefore.getTime()<=t.getTime()&&t.getTime()<=e.notAfter.getTime()}function o(e){return i(e.subject,e.issuer)}function s(e,t){let n=0;for(let r=1;r<t;r+=1){let t=e[r];t?.basicConstraints?.ca===!0&&!o(t)&&(n+=1)}return n}function c(e){let t=[];for(let n of e)t.push(...f(n));return t}function l(e){let t=f(e),n=t[0];if(n===void 0)throw Error(`No certificate found`);if(t.length!==1)throw Error(`Expected a single certificate source`);return n}async function u(e,t){let r=await n(e.signatureAlgorithmOid,e.signatureAlgorithmParametersDer,t.publicKeyAlgorithmOid,t.publicKeyParametersOid,t.subjectPublicKeyInfoDer,e.signatureValue,e.tbsCertificateDer);return!r.ok&&r.code===`verification_error`?{ok:!1,code:`signature_invalid`,reason:r.reason}:r}async function d(e,t,n,i,s,c){let l=[...t,...n],d=new Map,f=new Map,m=new Set(n.map(e=>x(e))),h=new Map;for(let e of i){let t=r(e.subject),n=h.get(t);n===void 0?h.set(t,[e]):n.push(e)}let g=!1,b=[e],S,C,w=new Set;l.forEach((e,t)=>{let n=r(e.subject),i=d.get(n);i===void 0?d.set(n,[e]):i.push(e),f.set(x(e),t)});let T=l.length+1,E=x(e),D=await O(e,[e],new Set([E]),0);if(D!==void 0)return{chain:D,foundTrustedRoot:!0};if(C!==void 0)return{chain:b,foundTrustedRoot:!1,failure:C};if(g)return{chain:b,foundTrustedRoot:!1};return S===void 0?{chain:b,foundTrustedRoot:!1}:{chain:b,foundTrustedRoot:!1,missingIssuerAt:S};async function O(e,t,n,i){if(m.has(x(e)))return t;let l=await _(e,h,c,t.length-1);if(l.failure!==void 0&&A(l.failure,t),l.matched)return t;if(t.length>T)return;let C=[...n].sort().join(`,`),E=`${x(e)}:${i}:${C}`;if(w.has(E))return;let D=p(e,d.get(r(e.issuer))??[],f,m);if(D.length===0){let n=t.length>b.length;k(t),o(e)?g=!0:n&&(S=t.length-1),w.add(E);return}for(let r of D){let l=x(r);if(n.has(l))continue;if(!a(r,s)){A(c.failure(`certificate_expired`,`certificate not valid at requested time`,t.length,c.detail({subjectCommonName:r.subject.values.commonName,expected:v(s),actual:`${v(r.notBefore)}..${v(r.notAfter)}`})),t);continue}if(r.basicConstraints?.ca!==!0){A(c.failure(`ca_required`,`issuer must be a CA certificate`,t.length,c.detail({subjectCommonName:r.subject.values.commonName})),t);continue}if(r.keyUsage!==void 0&&!r.keyUsage.flags.includes(`keyCertSign`)){A(c.failure(`key_cert_sign_required`,`issuer missing keyCertSign`,t.length,c.detail({subjectCommonName:r.subject.values.commonName})),t);continue}if(e.authorityKeyIdentifier!==void 0&&r.subjectKeyIdentifier!==void 0&&e.authorityKeyIdentifier!==r.subjectKeyIdentifier){A(c.failure(`authority_key_identifier_mismatch`,`authorityKeyIdentifier does not match issuer subjectKeyIdentifier`,t.length-1,c.detail({subjectCommonName:e.subject.values.commonName,issuerCommonName:r.subject.values.commonName,expected:r.subjectKeyIdentifier,actual:e.authorityKeyIdentifier})),t);continue}let d=i+ +(e.basicConstraints?.ca===!0&&!o(e)&&t.length>1),f=r.basicConstraints?.pathLength;if(y(f)&&d>f){A(c.failure(`path_length_exceeded`,`path length constraint exceeded`,t.length,c.detail({subjectCommonName:r.subject.values.commonName,expected:String(f),actual:String(d)})),t);continue}let p=await u(e,r);if(!p.ok){A(c.failure(p.code,p.reason,t.length-1,c.detail({subjectCommonName:e.subject.values.commonName,issuerCommonName:r.subject.values.commonName,actual:p.reason})),t);continue}if(!p.valid){A(c.failure(`signature_invalid`,`certificate signature does not verify`,t.length-1,c.detail({subjectCommonName:e.subject.values.commonName,issuerCommonName:r.subject.values.commonName})),t);continue}let m=new Set(n);m.add(l);let h=await O(r,[...t,r],m,d);if(h!==void 0)return h}w.add(E),k(t)}function k(e){return e.length>b.length?(b=e,!0):!1}function A(e,t){(C===void 0||t.length>b.length)&&(C=e,b=t)}}function f(e){return t(e)}function p(e,t,n,r){let i=e.authorityKeyIdentifier,a=[...t].filter(t=>g(t,e)),o=new Map;for(let e of a)o.set(e,x(e));return a.sort((e,t)=>{let a=h(m(e,i),m(t,i));if(a!==0)return a;let s=o.get(e)??``,c=o.get(t)??``,l=h(r.has(s),r.has(c));return l===0?(n.get(s)??2**53-1)-(n.get(c)??2**53-1):l})}function m(e,t){return t!==void 0&&e.subjectKeyIdentifier!==void 0&&e.subjectKeyIdentifier===t}function h(e,t){return e===t?0:e?-1:1}function g(e,t){return i(t.issuer,e.subject)}async function _(e,t,i,a){let o=t.get(r(e.issuer));if(o===void 0)return{matched:!1};let s;for(let t of o){if(t.subjectKeyIdentifier!==void 0&&e.authorityKeyIdentifier!==void 0&&t.subjectKeyIdentifier!==e.authorityKeyIdentifier)continue;let r;try{let i=await n(e.signatureAlgorithmOid,e.signatureAlgorithmParametersDer,t.publicKeyAlgorithmOid,t.publicKeyParametersOid,t.subjectPublicKeyInfoDer,e.signatureValue,e.tbsCertificateDer);r=!i.ok&&i.code===`verification_error`?{ok:!1,code:`signature_invalid`,reason:i.reason}:i}catch(t){s===void 0&&(s=i.failure(`signature_invalid`,`certificate signature does not verify`,a,i.detail({subjectCommonName:e.subject.values.commonName,actual:t instanceof Error?t.message:`trust anchor key is malformed`})));continue}if(!r.ok){s===void 0&&(s=i.failure(r.code,r.reason,a,i.detail({subjectCommonName:e.subject.values.commonName,actual:r.reason})));continue}if(!r.valid){s===void 0&&(s=i.failure(`signature_invalid`,`certificate signature does not verify`,a,i.detail({subjectCommonName:e.subject.values.commonName,actual:`certificate signature does not verify`})));continue}return{matched:!0}}return s===void 0?{matched:!1}:{matched:!1,failure:s}}function v(e){return Number.isNaN(e.getTime())?`<invalid date>`:e.toISOString()}function y(e){return e!==void 0&&Number.isInteger(e)&&e>=0}const b=new WeakMap;function x(t){let n=b.get(t);return n===void 0&&(n=e(t.der),b.set(t,n)),n}export{d as buildChainInternal,s as countCaCertificatesBelowParsed,o as isSelfIssued,a as isWithinValidity,c as loadCertificates,l as loadSingleCertificate,u as verifyCertificateSignature};
|
|
2
|
+
//# sourceMappingURL=verify-path.js.map
|