@blamejs/pki 0.2.21 → 0.2.23

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.
@@ -38,6 +38,7 @@ var schema = require("./schema-engine");
38
38
  var x509 = require("./schema-x509");
39
39
  var crl = require("./schema-crl");
40
40
  var ocsp = require("./schema-ocsp");
41
+ var ocspVerify = require("./ocsp-verify");
41
42
  var guard = require("./guard-all");
42
43
  var constants = require("./constants");
43
44
  var validator = require("./validator-all");
@@ -1839,163 +1840,19 @@ function verifyCrlSignature(theCrl, issuer) {
1839
1840
 
1840
1841
  // ---- the OCSP revocation checker (RFC 6960) -------------------------------
1841
1842
 
1842
- // CertID identity-hash algorithms (RFC 6960 sec. 4.1.1). Kept SEPARATE from the
1843
- // signature SIG_ALGS/HASH_BY_OID set (which omits SHA-1 -- a SHA-1 *signature* is
1844
- // refused, SHAttered): a CertID hash is an identity binding of an already-known
1845
- // issuer, not a signature, so collision resistance is irrelevant to the lookup
1846
- // and RFC 6960's default SHA-1 CertID MUST interoperate. A hash OID outside this
1847
- // set cannot be reproduced -> no CertID match (fail closed), never an assumption.
1848
- var OCSP_CERTID_HASHES = {};
1849
- OCSP_CERTID_HASHES[oid.byName("sha1")] = "SHA-1";
1850
- OCSP_CERTID_HASHES[oid.byName("sha256")] = "SHA-256";
1851
- OCSP_CERTID_HASHES[oid.byName("sha384")] = "SHA-384";
1852
- OCSP_CERTID_HASHES[oid.byName("sha512")] = "SHA-512";
1853
- var OID_OCSP_SIGNING = oid.byName("ocspSigning");
1854
- var OID_OCSP_NOCHECK = oid.byName("ocspNoCheck");
1855
-
1856
- // The subjectPublicKey BIT STRING VALUE (excluding the unused-bits octet) of an
1857
- // SPKI DER -- the exact bytes an OCSP CertID issuerKeyHash / byKey KeyHash hash
1858
- // over (RFC 6960 sec. 4.1.1). Throws on a malformed SPKI; the caller fails closed.
1859
- function ocspKeyValue(spkiDer) {
1860
- return asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
1861
- }
1862
-
1863
- // The delegate responder's importable SPKI. If its key omits algorithm parameters
1864
- // and inherits them from the issuing CA (same key algorithm -- an EC public key
1865
- // whose SPKI omits the namedCurve, RFC 5280 sec. 4.1.2.7), splice the issuer's
1866
- // parameters in so importKey("spki", ...) has a complete key; otherwise a valid
1867
- // response would verify as unknown. Mirrors updateWorkingKey's inheritance.
1868
- function ocspResponderSpki(rc, issuer) {
1869
- var keyAlg = rc.subjectPublicKeyInfo.algorithm;
1870
- if (!isNullOrAbsentParams(keyAlg.parameters)) return rc.subjectPublicKeyInfo.bytes;
1871
- var issuerOid, issuerParams;
1872
- // Coverage residual -- the catch is unreachable: this function runs only after the
1873
- // delegate's signature verified under issuer.workingPublicKey, so that SPKI already
1874
- // imported and decodes cleanly here. (The children[1] ? : null false side IS covered.)
1875
- try {
1876
- var alg = asn1.decode(issuer.workingPublicKey).children[0];
1877
- issuerOid = asn1.read.oid(alg.children[0]);
1878
- issuerParams = alg.children[1] ? alg.children[1].bytes : null;
1879
- } catch (_e) { return rc.subjectPublicKeyInfo.bytes; }
1880
- if (issuerOid === keyAlg.oid && !isNullOrAbsentParams(issuerParams)) {
1881
- return spliceSpkiParameters(rc.subjectPublicKeyInfo, keyAlg.oid, issuerParams);
1882
- }
1883
- return rc.subjectPublicKeyInfo.bytes;
1884
- }
1885
- function ocspDigest(alg, buf) { return subtle.digest(alg, buf).then(function (h) { return Buffer.from(h); }); }
1886
-
1887
- // The checker implements NO OCSP response-extension semantics (the nonce is the
1888
- // live client's, RFC 6960 sec. 4.4.1; CRL References / Archive Cutoff / Service
1889
- // Locator are not consulted), so a CRITICAL responseExtension / singleExtension
1890
- // may change a response's meaning in a way this code cannot honor -- treat it as
1891
- // unusable (RFC 6960 sec. 4.4 / the RFC 5280 critical-extension contract, the OCSP
1892
- // analogue of the crlChecker unhandled-critical skip). Returns true if `extList`
1893
- // carries any critical extension.
1894
- function ocspHasCriticalExtension(extList) {
1895
- if (!extList) return false;
1896
- for (var i = 0; i < extList.length; i++) if (extList[i].critical) return true;
1897
- return false;
1898
- }
1899
-
1900
- // A SingleResponse's CertID names the target cert IFF its serial equals the
1901
- // target's AND issuerNameHash + issuerKeyHash, recomputed under the CertID's OWN
1902
- // hashAlgorithm, match the issuer (RFC 6960 sec. 4.1.1). A serial-only match would
1903
- // let a "good" for issuer A serial N answer a query for issuer B serial N
1904
- // (cross-CA substitution), so BOTH issuer hashes must match under the responder's
1905
- // chosen algorithm -- never an assumed SHA-1.
1906
- //
1907
- // `issuerNameCandidates` is every byte encoding of the (name-chaining-validated)
1908
- // issuer DN to try: the checked cert's issuer field (RFC 6960 sec. 4.1.1) plus,
1909
- // when available, the issuer certificate's own subject encoding. These are RFC
1910
- // 5280 sec. 7.1-equal (they chained) but MAY differ in DER -- case, whitespace, a
1911
- // PrintableString-vs-UTF8String DirectoryString choice -- and a responder MAY have
1912
- // hashed any of them; matching the CertID against any is still the SAME validated
1913
- // issuer, because the issuerKeyHash + serial + authorized signature carry the
1914
- // binding. The key (raw octets, no encoding variance) is matched once.
1915
- async function ocspCertIdMatches(certID, cert, issuerNameCandidates, issuerKeyBits) {
1916
- if (certID.serialNumberHex !== cert.serialNumberHex) return false;
1917
- var hashName = OCSP_CERTID_HASHES[certID.hashAlgorithm.oid];
1918
- if (!hashName) return false; // an unreproducible hash -> the match cannot be confirmed
1919
- var keyHash = await ocspDigest(hashName, issuerKeyBits);
1920
- if (!certID.issuerKeyHash.equals(keyHash)) return false;
1921
- for (var i = 0; i < issuerNameCandidates.length; i++) {
1922
- if (certID.issuerNameHash.equals(await ocspDigest(hashName, issuerNameCandidates[i]))) return true;
1923
- }
1924
- return false;
1925
- }
1926
-
1927
- // Resolve the signer of a BasicOCSPResponse to an AUTHORIZED responder's SPKI DER,
1928
- // or null when none is authorized (RFC 6960 sec. 4.2.2.2). Two models: the issuing
1929
- // CA signing directly (responderID identifies the issuer), or a CA-delegated
1930
- // responder -- a certificate in `certs` issued by the SAME CA, valid at `time`,
1931
- // bearing id-kp-OCSPSigning in its extendedKeyUsage. anyExtendedKeyUsage and an
1932
- // ABSENT EKU do NOT authorize an OCSP delegate (the opposite of the path-validation
1933
- // EKU default), so an ordinary leaf the CA issued cannot forge revocation status.
1934
- // Fails closed at every branch (a malformed delegate, a control-byte DN, an
1935
- // unreadable EKU -> skip that candidate).
1936
- async function ocspAuthorizeResponder(basicResponse, cert, issuer, issuerKeyBits, time) {
1937
- var rid = basicResponse.responderID;
1938
- var matchesIssuer = false;
1939
- try {
1940
- if (rid.byName) matchesIssuer = dnEqual(rid.byName.rdns, cert.issuer.rdns);
1941
- else if (rid.byKey) matchesIssuer = rid.byKey.equals(await ocspDigest("SHA-1", issuerKeyBits));
1942
- } catch (_e) { matchesIssuer = false; }
1943
- if (matchesIssuer) return issuer.workingPublicKey;
1944
-
1945
- for (var i = 0; i < basicResponse.certs.length; i++) {
1946
- var rc;
1947
- try { rc = x509.parse(basicResponse.certs[i]); }
1948
- catch (_e) { continue; }
1949
- var identifies = false;
1950
- try {
1951
- if (rid.byName) identifies = dnEqual(rid.byName.rdns, rc.subject.rdns);
1952
- else if (rid.byKey) identifies = rid.byKey.equals(await ocspDigest("SHA-1", ocspKeyValue(rc.subjectPublicKeyInfo.bytes)));
1953
- } catch (_e) { identifies = false; }
1954
- if (!identifies) continue;
1955
- // The delegate MUST be issued directly by the CA that issued the target.
1956
- var issuedByCa;
1957
- try { issuedByCa = dnEqual(rc.issuer.rdns, cert.issuer.rdns); }
1958
- catch (_e) { continue; }
1959
- if (!issuedByCa) continue;
1960
- if (!guard.crypto.isOctetAligned(rc.signatureValue)) continue;
1961
- if (!(await _verifyWithSpki(rc.signatureAlgorithm, rc.signatureValue.bytes, issuer.workingPublicKey, rc.tbsBytes))) continue;
1962
- // The delegate certificate MUST itself be valid at the validation instant.
1963
- if (time < rc.validity.notBefore || time > rc.validity.notAfter) continue;
1964
- // The delegate MUST assert id-kp-OCSPSigning; anyEKU / an absent EKU do not.
1965
- var eku;
1966
- try { eku = decodeExt(rc, OID.extKeyUsage); }
1967
- catch (_e) { continue; }
1968
- if (!eku || eku.value.indexOf(OID_OCSP_SIGNING) === -1) continue;
1969
- // A delegate asserting keyUsage MUST permit digitalSignature -- signing an OCSP
1970
- // response is a digitalSignature operation (RFC 5280 sec. 4.2.1.3); an absent
1971
- // keyUsage is unrestricted, an unreadable one is not authoritative.
1972
- var ku;
1973
- try { ku = decodeExt(rc, OID.keyUsage); }
1974
- catch (_e) { continue; }
1975
- if (ku && ku.value.digitalSignature !== true) continue;
1976
- // A delegate carrying a critical extension this code does not process is
1977
- // unusable, the same fail-closed rule the path validator applies to any cert
1978
- // (RFC 5280 sec. 6.1.4(o)) -- an unknown critical constraint must not be ignored
1979
- // while its key is trusted to authenticate revocation status. A RECOGNIZED
1980
- // critical extension whose value is malformed is likewise rejected via the
1981
- // shared structure validation, exactly as the path validator rejects it.
1982
- if (unrecognizedCriticalExtension(rc, false)) continue;
1983
- if (validateCriticalExtensionStructure(rc)) continue;
1984
- // The delegate MUST carry id-pkix-ocsp-nocheck (RFC 6960 sec. 4.2.2.2.1): a
1985
- // transport-free checker cannot otherwise confirm the responder certificate has
1986
- // not itself been revoked, and a revoked OCSP-signing certificate would keep
1987
- // signing "good" until its notAfter. A deployment that instead supplies the
1988
- // responder's own status opts in through a future checker; absent nocheck, fail
1989
- // closed (unknown) rather than trust an unvalidated responder.
1990
- if (!findExt(rc, OID_OCSP_NOCHECK)) continue;
1991
- // A composite-keyed delegate is an out-of-path signer cert and gets the same
1992
- // composite keyUsage gate the path certificates do (draft sec. 5.2): a dual-usage
1993
- // composite responder key (a forbidden encryption bit set) is not authorized.
1994
- if (compositeSig.COMPOSITE_ALGS[rc.subjectPublicKeyInfo.algorithm.oid] && !compositeKeyUsageCheck(rc).ok) continue;
1995
- return ocspResponderSpki(rc, issuer);
1996
- }
1997
- return null;
1998
- }
1843
+ // The OCSP response-verification core (responder authorization + CertID binding + currency +
1844
+ // status) lives ONCE in lib/ocsp-verify.js, composed here by pki.path.ocspChecker AND by
1845
+ // pki.ocsp.verify -- there is no second, weaker OCSP verify path. This binds the path-validate-
1846
+ // owned signature engine + RFC 5280 cert-profile gates into that shared core.
1847
+ var ocspCore = ocspVerify.makeOcspVerify({
1848
+ verifyWithSpki: _verifyWithSpki,
1849
+ decodeExt: decodeExt, findExt: findExt,
1850
+ unrecognizedCriticalExtension: unrecognizedCriticalExtension,
1851
+ validateCriticalExtensionStructure: validateCriticalExtensionStructure,
1852
+ compositeKeyUsageCheck: compositeKeyUsageCheck,
1853
+ isNullOrAbsentParams: isNullOrAbsentParams, spliceSpkiParameters: spliceSpkiParameters,
1854
+ dnEqual: dnEqual,
1855
+ });
1999
1856
 
2000
1857
  /**
2001
1858
  * @primitive pki.path.ocspChecker
@@ -2043,57 +1900,26 @@ function ocspChecker(responses) {
2043
1900
  if (issuer.issuerCert) addNameCandidate(issuer.issuerCert.subject);
2044
1901
  addNameCandidate(issuer.workingIssuerName);
2045
1902
  var issuerKeyBits;
2046
- try { issuerKeyBits = ocspKeyValue(issuer.workingPublicKey); }
1903
+ try { issuerKeyBits = ocspVerify.ocspKeyValue(issuer.workingPublicKey); }
2047
1904
  catch (_e) { return { status: "unknown", reason: "the issuer public key could not be read to recompute the OCSP CertID" }; }
2048
1905
 
2049
1906
  // A serial is revoked if ANY authoritative, verified, current response says
2050
1907
  // so -- a clean response must never shadow a revoking one (the crlChecker
2051
1908
  // fail-closed law). "good" needs at least one authoritative match; every
2052
- // other outcome is undetermined.
1909
+ // other outcome is undetermined. The shared verify core evaluates each
1910
+ // response (responder authorization + signature + CertID + currency +
1911
+ // status) and returns a per-response summary this aggregates.
2053
1912
  var revokedResult = null;
2054
1913
  var sawGood = false;
2055
1914
  var sawUnknownStatus = false;
2056
1915
 
2057
1916
  for (var k = 0; k < parsed.length; k++) {
2058
- var resp = parsed[k];
2059
- if (resp.responseStatus.code !== 0) continue; // non-successful -> conveys no status
2060
- var br = resp.basicResponse;
2061
- // Coverage residual -- unreachable for a parsed response: schema-ocsp enforces
2062
- // the successful-status <-> responseBytes biconditional, so a code-0 response
2063
- // always carries a basicResponse. Backstop for a hand-built object.
2064
- if (!br) continue;
2065
- var signerSpki = await ocspAuthorizeResponder(br, cert, issuer, issuerKeyBits, time);
2066
- if (!signerSpki) continue; // unauthorized responder
2067
- if (!(await _verifyWithSpki(br.signatureAlgorithm, br.signature, signerSpki, br.tbsResponseDataBytes))) continue;
2068
- // A critical responseExtension changes the meaning of the WHOLE response
2069
- // and this code processes none -> the response is unusable.
2070
- if (ocspHasCriticalExtension(br.responseExtensions)) continue;
2071
- for (var s = 0; s < br.responses.length; s++) {
2072
- var sr = br.responses[s];
2073
- if (!(await ocspCertIdMatches(sr.certID, cert, issuerNameCandidates, issuerKeyBits))) continue; // not about this cert
2074
- if (ocspHasCriticalExtension(sr.singleExtensions)) continue; // a critical per-response extension this code cannot process -> skip
2075
- if (sr.thisUpdate > time) continue; // not yet valid
2076
- if (!sr.nextUpdate || sr.nextUpdate < time) continue; // no bounded validity / stale -> fail closed
2077
- var st = sr.certStatus;
2078
- if (st.type === "revoked") {
2079
- // A revocation is effective as of its revocationTime. Present-time
2080
- // validation revokes regardless (a future revocationTime is
2081
- // post-dating/skew, never "good"); only an EXPLICIT historical
2082
- // validation defers a strictly-future revocation.
2083
- if (historical && st.revocationTime instanceof Date && st.revocationTime.getTime() > time.getTime()) { sawGood = true; }
2084
- else {
2085
- revokedResult = {
2086
- status: "revoked",
2087
- revocationReason: st.revocationReason || null,
2088
- reason: "certificate reported revoked by an authorized OCSP responder" + (st.revocationReason ? " (" + st.revocationReason + ")" : ""),
2089
- };
2090
- }
2091
- } else if (st.type === "good") {
2092
- sawGood = true;
2093
- } else {
2094
- sawUnknownStatus = true; // an explicit responder "unknown"
2095
- }
1917
+ var v = await ocspCore.evaluateResponse(parsed[k], cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical);
1918
+ if (v.revoked && !revokedResult) {
1919
+ revokedResult = { status: "revoked", revocationReason: v.revoked.revocationReason, reason: v.revoked.reason };
2096
1920
  }
1921
+ if (v.sawGood) sawGood = true;
1922
+ if (v.sawUnknownStatus) sawUnknownStatus = true;
2097
1923
  }
2098
1924
  if (revokedResult) return revokedResult;
2099
1925
  if (sawGood) return { status: "good" };
@@ -2107,8 +1933,82 @@ function ocspChecker(responses) {
2107
1933
  };
2108
1934
  }
2109
1935
 
1936
+ /**
1937
+ * @primitive pki.path.verifyOcspResponse
1938
+ * @signature pki.path.verifyOcspResponse(parsedResponse, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, reason }>
1939
+ * @since 0.2.22
1940
+ * @status experimental
1941
+ * @spec RFC 6960
1942
+ * @related pki.ocsp.verify, pki.path.ocspChecker
1943
+ *
1944
+ * Verify a single already-parsed OCSP response for one certificate against its
1945
+ * already-parsed issuer certificate at `time` -- the lower-level primitive
1946
+ * `pki.ocsp.verify` composes after parsing its inputs (most callers want that
1947
+ * ergonomic entry, which also handles DER/PEM decoding and request-nonce
1948
+ * matching). It runs the EXACT SAME gates the path validator's `ocspChecker`
1949
+ * does: it locates the SingleResponse whose CertID binds this cert's serial to
1950
+ * its issuer (recomputing `issuerNameHash`/`issuerKeyHash` under the CertID's
1951
+ * own hashAlgorithm), confirms the responder is authorized (the issuing CA
1952
+ * directly, or a CA-issued delegate bearing both id-kp-OCSPSigning and
1953
+ * id-pkix-ocsp-nocheck and passing the full out-of-path certificate gates),
1954
+ * verifies the response signature over `tbsResponseDataBytes`, and checks
1955
+ * currency (`thisUpdate`/`nextUpdate`) -- there is no weaker second OCSP verify
1956
+ * path. It is fail-closed and never throws on an unauthorized, stale, or
1957
+ * unverifiable response: those yield `{ status: "unknown" }` with the granular
1958
+ * `responderAuthorized`/`signatureValid`/`matched` flags and a `reason`; a
1959
+ * `revoked` status surfaces its `revocationReason`. Setting `opts.historicalMode`
1960
+ * treats a revocation whose `revocationTime` is strictly after `time` as not-yet-
1961
+ * revoked (`good`) -- for validating a signature as of a past `time`, before the
1962
+ * certificate was later revoked; the responder certificate is still validated at
1963
+ * `time` either way. `time` must be a valid `Date`. A malformed response's parse
1964
+ * fault surfaces as the parser's typed `ocsp/*` / `asn1/*` error.
1965
+ *
1966
+ * @example
1967
+ * var resp = pki.schema.ocsp.parseResponse(der);
1968
+ * var v = await pki.path.verifyOcspResponse(resp, cert, issuerCert, new Date());
1969
+ * v.status; // "good" | "revoked" | "unknown"
1970
+ */
1971
+ function verifyOcspResponse(parsedResponse, cert, issuerCert, time, opts) {
1972
+ opts = opts || {};
1973
+ // The currency + responder-cert validity windows compare against `time`; a missing or invalid
1974
+ // check date must fail closed (a NaN compares false against every bound), never silently pass.
1975
+ if (!(time instanceof Date) || isNaN(time.getTime())) {
1976
+ return Promise.reject(E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
1977
+ }
1978
+ // Bind the supplied issuer certificate to the target: the target's issuer DN must equal the
1979
+ // issuer cert's subject DN AND the target's signature must verify under the issuer's key. A
1980
+ // direct-CA responder is authorized by exactly this issuer identity and the CertID is recomputed
1981
+ // under the issuer's key, so without the cryptographic binding a rogue certificate sharing the
1982
+ // issuer's subject DN (but a different key) could recompute a matching CertID and sign a "good"
1983
+ // response for a certificate that CA never issued. ocspChecker gets an issuer already chained by
1984
+ // the path validator; the standalone entry must establish the binding itself. Fail closed.
1985
+ function unbound(reason) { return { status: "unknown", responderAuthorized: false, signatureValid: false, matched: false, reason: reason }; }
1986
+ var boundName;
1987
+ try { boundName = dnEqual(cert.issuer.rdns, issuerCert.subject.rdns); }
1988
+ catch (e) { return Promise.reject(e); } // an embedded control byte in a DN -> path/name-chaining
1989
+ if (!boundName) {
1990
+ return Promise.resolve(unbound("the supplied issuer certificate's subject does not match the target certificate's issuer"));
1991
+ }
1992
+ return builtinVerify({ workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes }, cert).then(function (sig) {
1993
+ if (!sig.ok) return unbound("the target certificate's signature does not verify under the supplied issuer certificate's key");
1994
+ var issuerCtx = { workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes, workingIssuerName: issuerCert.subject, issuerCert: issuerCert };
1995
+ var issuerNameCandidates = [cert.issuer.bytes];
1996
+ function add(nm) { if (nm && nm.bytes && !issuerNameCandidates.some(function (e) { return e.equals(nm.bytes); })) issuerNameCandidates.push(nm.bytes); }
1997
+ add(issuerCert.subject);
1998
+ var issuerKeyBits;
1999
+ try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
2000
+ catch (_e) { return unbound("the issuer public key could not be read to recompute the OCSP CertID"); }
2001
+ return ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits, issuerNameCandidates, time, opts.historicalMode === true).then(function (v) {
2002
+ if (v.revoked) return { status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, reason: v.revoked.reason };
2003
+ if (v.sawGood) return { status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" };
2004
+ return { status: "unknown", responderAuthorized: v.responderAuthorized === true, signatureValid: v.signatureValid === true, matched: v.matched === true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: v.reason };
2005
+ });
2006
+ });
2007
+ }
2008
+
2110
2009
  module.exports = {
2111
2010
  validate: validate,
2112
2011
  crlChecker: crlChecker,
2113
2012
  ocspChecker: ocspChecker,
2013
+ verifyOcspResponse: verifyOcspResponse,
2114
2014
  };
package/lib/schema-cms.js CHANGED
@@ -1203,4 +1203,13 @@ module.exports = {
1203
1203
  walkSignedData: walkSignedData,
1204
1204
  walkEncryptedData: walkEncryptedData,
1205
1205
  assertAttachedCiphertext: assertAttachedCiphertext,
1206
+ // The structure's own algorithm tables, exported (like the walk* helpers) as the single source
1207
+ // of truth the crypto layer (cms-encrypt / cms-decrypt) shares -- so the wrap<->KEK-length, the
1208
+ // ML-KEM ciphertext-length, and the AEAD-mode/ICV-length rules the parser enforces cannot drift
1209
+ // from what the producer/consumer emit and accept.
1210
+ WRAP_KEK_LENGTHS: WRAP_KEK_LENGTHS,
1211
+ KEM_CT_LENGTHS: KEM_CT_LENGTHS,
1212
+ AEAD_ALGS: AEAD_ALGS,
1213
+ AEAD_GCM_ICVLENS: AEAD_GCM_ICVLENS,
1214
+ AEAD_CCM_ICVLENS: AEAD_CCM_ICVLENS,
1206
1215
  };
@@ -0,0 +1,206 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ //
5
+ // @internal -- no operator-facing namespace. The signature-scheme resolver + signer shared by
6
+ // every producer that signs a raw preimage with a certificate's key: pki.cms.sign (SignerInfo),
7
+ // pki.ocsp.sign (BasicOCSPResponse), and future X.509 / CRL issuance. It owns the ONE key ->
8
+ // { signatureAlgorithm, WebCrypto import/sign params } dispatch across the whole algorithm set
9
+ // (RSA-PKCS1 / RSA-PSS / ECDSA P-256/384/521 / Ed25519 / Ed448 / ML-DSA / SLH-DSA / composite
10
+ // ML-DSA), so a new signer surface inherits the full registry -- PQC included -- instead of
11
+ // re-deriving a partial, drifting copy (Hard rule #2: registry not switch; and no second signer).
12
+ //
13
+ // Error-parameterized like the guard / validator families: the caller passes its typed error
14
+ // factory `E(kind, message, cause)` where kind is "bad-input" | "unsupported-algorithm", so every
15
+ // domain keeps its own code (cms/bad-input vs ocsp/bad-input). The digest choice (a CMS
16
+ // digestAlgorithm concern) is surfaced as `scheme.digest`; the caller that has a digestAlgorithm
17
+ // field (CMS) builds it, the caller that signs raw bytes (OCSP) ignores it.
18
+
19
+ var asn1 = require("./asn1-der");
20
+ var oid = require("./oid");
21
+ var pkcs8 = require("./schema-pkcs8");
22
+ var webcrypto = require("./webcrypto");
23
+ var subtle = webcrypto.webcrypto.subtle;
24
+ var validator = require("./validator-all");
25
+ var compositeSig = require("./composite-sig");
26
+ var b = asn1.build;
27
+ function O(name) { return oid.byName(name); }
28
+
29
+ var HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
30
+ var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", shake128: "shake128", shake256: "shake256" };
31
+ var PSS_SALT = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
32
+ var ECDSA_ALG = { sha256: "ecdsaWithSHA256", sha384: "ecdsaWithSHA384", sha512: "ecdsaWithSHA512" };
33
+ // RSA-PKCS1 signatures fold the digest into a combined signature OID when there is NO separate
34
+ // digestAlgorithm field to carry it (X.509 / CRL / OCSP -- RFC 5280 sec. 4.1.1.2, RFC 6960). CMS
35
+ // SignerInfo has that field, so it signs under the bare rsaEncryption key OID instead.
36
+ var RSA_PKCS1_SIG = { sha256: "sha256WithRSAEncryption", sha384: "sha384WithRSAEncryption", sha512: "sha512WithRSAEncryption" };
37
+ var HASH_NAME_BY_OID = {};
38
+ HASH_NAME_BY_OID[O("sha256")] = "sha256";
39
+ HASH_NAME_BY_OID[O("sha384")] = "sha384";
40
+ HASH_NAME_BY_OID[O("sha512")] = "sha512";
41
+ var EC_BY_CURVE_OID = {};
42
+ EC_BY_CURVE_OID[O("prime256v1")] = { curve: "P-256", coordLen: 32 };
43
+ EC_BY_CURVE_OID[O("secp384r1")] = { curve: "P-384", coordLen: 48 };
44
+ EC_BY_CURVE_OID[O("secp521r1")] = { curve: "P-521", coordLen: 66 };
45
+ var MLDSA_BY_OID = {};
46
+ MLDSA_BY_OID[O("id-ml-dsa-44")] = "ML-DSA-44";
47
+ MLDSA_BY_OID[O("id-ml-dsa-65")] = "ML-DSA-65";
48
+ MLDSA_BY_OID[O("id-ml-dsa-87")] = "ML-DSA-87";
49
+ var MLDSA_SUITABLE_DIGEST = {
50
+ "ML-DSA-44": { sha256: 1, sha384: 1, sha512: 1, shake256: 1 },
51
+ "ML-DSA-65": { sha384: 1, sha512: 1, shake256: 1 },
52
+ "ML-DSA-87": { sha512: 1, shake256: 1 },
53
+ };
54
+ var SLHDSA_BY_OID = {};
55
+ [["sha2-128s", "sha256"], ["sha2-128f", "sha256"], ["sha2-192s", "sha512"], ["sha2-192f", "sha512"],
56
+ ["sha2-256s", "sha512"], ["sha2-256f", "sha512"], ["shake-128s", "shake128"], ["shake-128f", "shake128"],
57
+ ["shake-192s", "shake256"], ["shake-192f", "shake256"], ["shake-256s", "shake256"], ["shake-256f", "shake256"]
58
+ ].forEach(function (r) { SLHDSA_BY_OID[O("id-slh-dsa-" + r[0])] = { wc: "SLH-DSA-" + r[0].toUpperCase(), digest: r[1] }; });
59
+
60
+ // An AlgorithmIdentifier { OID } (absent parameters) or { OID, NULL }.
61
+ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(name)), b.nullValue()]) : b.sequence([b.oid(O(name))]); }
62
+ // The RSASSA-PSS AlgorithmIdentifier: explicit SHA-2 hash, MGF1 keyed to it, hash-length salt,
63
+ // default trailerField omitted (RFC 4055).
64
+ function _pssAlgId(digestName) {
65
+ var hashAlg = b.sequence([b.oid(O(digestName)), b.nullValue()]);
66
+ var mgf = b.sequence([b.oid(O("mgf1")), hashAlg]);
67
+ var params = b.sequence([b.explicit(0, hashAlg), b.explicit(1, mgf), b.explicit(2, b.integer(BigInt(PSS_SALT[HASH[digestName]])))]);
68
+ return b.sequence([b.oid(O("rsassaPss")), params]);
69
+ }
70
+ // An id-RSASSA-PSS SPKI MAY pin its permitted hash in the params (RFC 4055 sec. 1.2 / 3.1). Read
71
+ // it so signing honors the restriction; absent params or an unrecognized hash returns null.
72
+ function _pssHashFromSpki(cert, E) {
73
+ var params = cert.subjectPublicKeyInfo.algorithm.parameters;
74
+ if (params == null) return null;
75
+ var node = asn1.decode(params);
76
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children) return null;
77
+ var hashField = node.children.filter(function (c) { return c.tagClass === "context" && c.tagNumber === 0; })[0];
78
+ if (!hashField || !hashField.children || !hashField.children[0] || !hashField.children[0].children) return null;
79
+ var oidNode = hashField.children[0].children[0];
80
+ if (!oidNode || oidNode.tagClass !== "universal" || oidNode.tagNumber !== asn1.TAGS.OBJECT_IDENTIFIER) return null;
81
+ var pinnedOid = asn1.read.oid(oidNode);
82
+ var name = HASH_NAME_BY_OID[pinnedOid];
83
+ if (!name) throw E("unsupported-algorithm", "the id-RSASSA-PSS signer key pins an unsupported hash algorithm (" + pinnedOid + ")");
84
+ return name;
85
+ }
86
+
87
+ // resolveSignScheme(cert, so, noSignedAttrs, E) -> the signature scheme from the signer cert's
88
+ // public-key algorithm + per-signer opts (so.digestAlgorithm / so.pss / so.combinedRsaSig -- the
89
+ // last folds the digest into a combined RSA signature OID for a caller with no digestAlgorithm
90
+ // field). Returns
91
+ // { composite?, digest, digestAlgId, sigAlgId, imp?, sign?, ecdsaDer?, coordLen? }. `digest`/
92
+ // `digestAlgId` matter only to a caller with a CMS digestAlgorithm field; OCSP ignores them.
93
+ function resolveSignScheme(cert, so, noSignedAttrs, E) {
94
+ so = so || {};
95
+ var alg = cert.subjectPublicKeyInfo.algorithm;
96
+ var keyOid = alg.oid;
97
+ var comp = compositeSig.COMPOSITE_ALGS[keyOid];
98
+ if (comp) {
99
+ if (comp.trad.unsupported) throw E("unsupported-algorithm", "composite " + comp.name + ": " + comp.trad.unsupported);
100
+ if (so.digestAlgorithm && so.digestAlgorithm !== comp.phCms) throw E("bad-input", "composite " + comp.name + " fixes the digestAlgorithm to " + comp.phCms + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4); " + JSON.stringify(so.digestAlgorithm) + " conflicts");
101
+ return { composite: comp, digest: comp.phCms, digestAlgId: _algId(comp.phCms, "absent"), sigAlgId: _algId(comp.name, "absent") };
102
+ }
103
+ if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) {
104
+ var isPssKey = keyOid === O("rsassaPss");
105
+ var pinned = isPssKey ? _pssHashFromSpki(cert, E) : null;
106
+ if (pinned && so.digestAlgorithm && so.digestAlgorithm !== pinned) throw E("bad-input", "the signer key restricts the RSASSA-PSS digest to " + pinned + ", but digestAlgorithm " + JSON.stringify(so.digestAlgorithm) + " was requested");
107
+ var d = so.digestAlgorithm || pinned || "sha256";
108
+ if (!HASH[d]) throw E("unsupported-algorithm", "unsupported RSA digest algorithm " + JSON.stringify(d));
109
+ if (so.pss || isPssKey) return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: _pssAlgId(d), imp: { name: "RSA-PSS", hash: HASH[d] }, sign: { name: "RSA-PSS", saltLength: PSS_SALT[HASH[d]] }, ecdsaDer: false };
110
+ var rsaSigAlgId = so.combinedRsaSig ? _algId(RSA_PKCS1_SIG[d], "null") : _algId("rsaEncryption", "null");
111
+ return { digest: d, digestAlgId: _algId(d, "absent"), sigAlgId: rsaSigAlgId, imp: { name: "RSASSA-PKCS1-v1_5", hash: HASH[d] }, sign: { name: "RSASSA-PKCS1-v1_5" }, ecdsaDer: false };
112
+ }
113
+ if (keyOid === O("ecPublicKey")) {
114
+ var curveOid;
115
+ try { curveOid = asn1.read.oid(asn1.decode(alg.parameters)); }
116
+ catch (e) { throw E("unsupported-algorithm", "the signer EC key parameters are not a named-curve OID", e); }
117
+ var ec = EC_BY_CURVE_OID[curveOid];
118
+ if (!ec) throw E("unsupported-algorithm", "the signer key is on an unsupported EC curve");
119
+ var de = so.digestAlgorithm || "sha256";
120
+ if (!HASH[de]) throw E("unsupported-algorithm", "unsupported ECDSA digest algorithm " + JSON.stringify(de));
121
+ return { digest: de, digestAlgId: _algId(de, "absent"), sigAlgId: _algId(ECDSA_ALG[de], "absent"), imp: { name: "ECDSA", namedCurve: ec.curve }, sign: { name: "ECDSA", hash: HASH[de] }, ecdsaDer: true, coordLen: ec.coordLen };
122
+ }
123
+ if (keyOid === O("Ed25519") || keyOid === O("Ed448")) {
124
+ var name = keyOid === O("Ed25519") ? "Ed25519" : "Ed448";
125
+ var dd = so.digestAlgorithm || (name === "Ed25519" ? "sha512" : "shake256");
126
+ if (!NODE_DIGEST[dd]) throw E("unsupported-algorithm", "unsupported " + name + " digest algorithm " + JSON.stringify(dd));
127
+ return { digest: dd, digestAlgId: _algId(dd, "absent"), sigAlgId: _algId(name, "absent"), imp: { name: name }, sign: { name: name }, ecdsaDer: false };
128
+ }
129
+ if (MLDSA_BY_OID[keyOid]) {
130
+ var mlName = MLDSA_BY_OID[keyOid];
131
+ var md;
132
+ if (noSignedAttrs) {
133
+ md = "sha512";
134
+ } else {
135
+ md = so.digestAlgorithm || "sha512";
136
+ if (!NODE_DIGEST[md]) throw E("unsupported-algorithm", "unsupported ML-DSA message digest " + JSON.stringify(md));
137
+ if (!MLDSA_SUITABLE_DIGEST[mlName][md]) throw E("unsupported-algorithm", "the " + md + " message digest is below the security strength of " + mlName + " (RFC 9882 sec. 3.3)");
138
+ }
139
+ return { digest: md, digestAlgId: _algId(md, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: mlName }, sign: { name: mlName }, ecdsaDer: false };
140
+ }
141
+ if (SLHDSA_BY_OID[keyOid]) {
142
+ var slh = SLHDSA_BY_OID[keyOid];
143
+ if (so.digestAlgorithm && so.digestAlgorithm !== slh.digest) throw E("bad-input", "SLH-DSA " + slh.wc + " requires the " + slh.digest + " message digest (RFC 9814 sec. 4); digestAlgorithm " + JSON.stringify(so.digestAlgorithm) + " conflicts");
144
+ return { digest: slh.digest, digestAlgId: _algId(slh.digest, "absent"), sigAlgId: _algId(oid.name(keyOid), "absent"), imp: { name: slh.wc }, sign: { name: slh.wc }, ecdsaDer: false };
145
+ }
146
+ throw E("unsupported-algorithm", "unsupported signer key algorithm " + keyOid);
147
+ }
148
+
149
+ // A CryptoKey's algorithm must match the resolved scheme (name / hash / curve).
150
+ function _assertKeyMatchesScheme(key, imp, E) {
151
+ var ka = key.algorithm || {};
152
+ if (ka.name !== imp.name) throw E("bad-input", "the signer CryptoKey algorithm (" + ka.name + ") does not match the certificate's key algorithm (" + imp.name + ")");
153
+ if (imp.hash && (!ka.hash || ka.hash.name !== imp.hash)) throw E("bad-input", "the signer CryptoKey hash (" + (ka.hash && ka.hash.name) + ") does not match the signing digest (" + imp.hash + ")");
154
+ if (imp.namedCurve && ka.namedCurve !== imp.namedCurve) throw E("bad-input", "the signer CryptoKey curve (" + ka.namedCurve + ") does not match the certificate curve (" + imp.namedCurve + ")");
155
+ }
156
+ function _normPkcs8(k, label, E) {
157
+ if (Buffer.isBuffer(k)) return k;
158
+ if (k instanceof Uint8Array) return Buffer.from(k);
159
+ if (typeof k === "string") { try { return pkcs8.pemDecode(k); } catch (e) { throw E("bad-input", label + " PEM could not be decoded", e); } }
160
+ throw E("bad-input", label + " must be a PKCS#8 DER Buffer, Uint8Array, or PEM string");
161
+ }
162
+ function _normCompositeKeys(key, comp, E) {
163
+ if (!key || typeof key !== "object" || Buffer.isBuffer(key) || key instanceof Uint8Array || key.mldsa == null || key.trad == null) {
164
+ throw E("bad-input", "a composite " + comp.name + " signer key must be { mldsa: <PKCS#8>, trad: <PKCS#8> }");
165
+ }
166
+ return { mldsa: _normPkcs8(key.mldsa, "the composite ML-DSA component key", E), trad: _normPkcs8(key.trad, "the composite traditional component key", E) };
167
+ }
168
+ function _importKey(key, imp, E) {
169
+ if (key && typeof key === "object" && !Buffer.isBuffer(key) && !(key instanceof Uint8Array) && key.type === "private") {
170
+ _assertKeyMatchesScheme(key, imp, E);
171
+ return Promise.resolve(key);
172
+ }
173
+ var der;
174
+ if (Buffer.isBuffer(key)) der = key;
175
+ else if (key instanceof Uint8Array) der = Buffer.from(key);
176
+ else if (typeof key === "string") { try { der = pkcs8.pemDecode(key); } catch (e) { throw E("bad-input", "the signer PEM private key could not be decoded", e); } }
177
+ else throw E("bad-input", "a signer key must be a CryptoKey, a PKCS#8 DER Buffer, or a PKCS#8 PEM string");
178
+ return subtle.importKey("pkcs8", der, imp, false, ["sign"]);
179
+ }
180
+
181
+ // signOverTbs(scheme, key, signedBytes, E) -> Promise<Buffer> the raw signature over signedBytes.
182
+ // The classical path imports the key + signs (re-encoding ECDSA to canonical DER); the composite
183
+ // path signs BOTH component keys and returns mldsaSig || tradSig (composite-sig.js owns it).
184
+ function signOverTbs(scheme, key, signedBytes, E) {
185
+ if (scheme.composite) {
186
+ return compositeSig.compositeSign(scheme.composite, _normCompositeKeys(key, scheme.composite, E), signedBytes).then(function (sig) { return Buffer.from(sig); });
187
+ }
188
+ return _importKey(key, scheme.imp, E).then(function (priv) {
189
+ return subtle.sign(scheme.sign, priv, signedBytes).then(function (sigRaw) {
190
+ var sig = Buffer.from(sigRaw);
191
+ if (scheme.ecdsaDer) sig = validator.sig.rawToEcdsaDer(sig, scheme.coordLen);
192
+ return sig;
193
+ });
194
+ });
195
+ }
196
+
197
+ // MLDSA_SUITABLE_DIGEST + SLHDSA_BY_OID are the RFC 9882 sec. 3.3 / RFC 9814 sec. 4 per-parameter-
198
+ // set digest policy, exported so the VERIFY side (cms-verify) shares the EXACT same table this
199
+ // resolver signs under -- a digest accepted on sign is precisely the set accepted on verify, with
200
+ // no drift between the two.
201
+ module.exports = {
202
+ resolveSignScheme: resolveSignScheme,
203
+ signOverTbs: signOverTbs,
204
+ MLDSA_SUITABLE_DIGEST: MLDSA_SUITABLE_DIGEST,
205
+ SLHDSA_BY_OID: SLHDSA_BY_OID,
206
+ };