@blamejs/pki 0.2.29 → 0.2.31

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/lib/smime.js CHANGED
@@ -168,7 +168,7 @@ async function sign(content, signers, opts) {
168
168
  // Coverage residual: the throw only fires on a >16 MiB assembled message -- a real cap, exercised by no
169
169
  // unit vector (driving it would sign 16 MiB).
170
170
  function _capped(msg) {
171
- if (msg.length > C.LIMITS.MIME_MAX_BYTES) throw _err("smime/too-large", "the assembled S/MIME message (" + msg.length + " bytes) exceeds the " + C.LIMITS.MIME_MAX_BYTES + "-byte cap that verify enforces");
171
+ guard.limits.byteCap(msg, C.LIMITS.MIME_MAX_BYTES, _err, "smime/too-large", "the assembled S/MIME message");
172
172
  return msg;
173
173
  }
174
174
 
package/lib/tsp-sign.js CHANGED
@@ -119,9 +119,7 @@ function sign(messageImprint, tsa, opts) {
119
119
  var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(Buffer.from(mi.hashedMessage))]);
120
120
  // genTime defaults to now; a supplied value MUST be a valid Date (never a silently-ignored
121
121
  // non-Date or an Invalid Date that would encode a garbage GeneralizedTime).
122
- if (opts.genTime != null && (!(opts.genTime instanceof Date) || isNaN(opts.genTime.getTime()))) {
123
- throw _err("tsp/bad-input", "genTime must be a valid Date");
124
- }
122
+ if (opts.genTime != null) guard.time.assertValid(opts.genTime, _err, "tsp/bad-input", "genTime");
125
123
  var genTime = opts.genTime instanceof Date ? opts.genTime : new Date();
126
124
  var fields = [b.integer(1n), _policy(opts.policy), imprint, b.integer(BigInt(opts.serialNumber)), b.generalizedTime(genTime)];
127
125
  if (opts.accuracy) fields.push(_accuracy(opts.accuracy));
@@ -15,8 +15,9 @@
15
15
  // -- the COMPLETE WebAuthn credential COSE_Key rule
16
16
  // set (RFC 9052/9053 + WebAuthn sec. 6.5.1 +
17
17
  // CTAP2 canonical + on-curve), one home
18
- // validator.sig.ecdsaSigToRaw -- the COMPLETE DER ECDSA-Sig-Value conformance
19
- // (RFC 3279 + X.690 strict-DER) + raw r||s conversion
18
+ // validator.sig.ecdsaDerToP1363 -- the COMPLETE order-aware DER ECDSA-Sig-Value
19
+ // conformance (RFC 3279 + X.690 strict-DER +
20
+ // FIPS 186-5 [1,n-1], CVE-2022-21449) + raw r||s
20
21
  // validator.attcert.packedCert / .aikCert / .aaguidExt / .requireV3 / .assertNotCa
21
22
  // -- the WebAuthn attestation-certificate profile
22
23
  // (sec. 8.2.1 packed, sec. 8.3.1 TPM AIK), one home
@@ -25,42 +25,13 @@
25
25
 
26
26
  var asn1 = require("./asn1-der");
27
27
 
28
- // ecdsaSigToRaw(der, coordLen, E, code) -> the validated signature as raw r||s, each
29
- // coordinate left-padded to coordLen bytes, or throws new E(code, ...). The complete DER
30
- // ECDSA-Sig-Value conformance gate; a verifier MUST route an ECDSA signature through here,
31
- // never hand-decode the SEQUENCE and read r/s content raw (which skips minimality).
32
- // @enforced-by behavioral -- a DER ECDSA-Sig-Value decode has no rename-proof code shape
33
- // distinct from generic 2-child-SEQUENCE content access; the RED conformance vectors
34
- // (non-minimal / negative / zero / over-size r or s rejected) and the webauthn ECDSA KATs
35
- // are the guard.
36
- function ecdsaSigToRaw(der, coordLen, E, code) {
37
- var node;
38
- try { node = asn1.decode(der); } catch (e) { throw new E(code, "ECDSA signature is not a DER SEQUENCE", e); }
39
- if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2) {
40
- throw new E(code, "ECDSA signature must be a DER SEQUENCE { r, s }");
41
- }
42
- function coord(c, label) {
43
- // The strict DER integer reader enforces PRIMITIVE + MINIMAL encoding (a constructed
44
- // child, an empty INTEGER, or a redundant 0x00/0xFF sign octet all throw). The value
45
- // is then range-checked: r and s MUST be positive (>= 1) and fit the curve field size.
46
- var v;
47
- try { v = asn1.read.integer(c); } catch (e) { throw new E(code, "ECDSA signature " + label + " is not a minimally-encoded DER INTEGER", e); }
48
- if (v <= 0n) throw new E(code, "ECDSA signature " + label + " must be a positive integer");
49
- var hex = v.toString(16); if (hex.length % 2) hex = "0" + hex;
50
- var b = Buffer.from(hex, "hex");
51
- if (b.length > coordLen) throw new E(code, "ECDSA signature " + label + " exceeds the curve field size");
52
- var out = Buffer.alloc(coordLen); b.copy(out, coordLen - b.length); return out;
53
- }
54
- return Buffer.concat([coord(node.children[0], "r"), coord(node.children[1], "s")]);
55
- }
56
-
57
28
  // rawToEcdsaDer(raw, coordLen) -> the canonical DER ECDSA-Sig-Value SEQUENCE { r, s } for a raw
58
- // r||s signature (IEEE P1363, the WebCrypto sign() output). The inverse of ecdsaSigToRaw: a
29
+ // r||s signature (IEEE P1363, the WebCrypto sign() output). The inverse of ecdsaDerToP1363: a
59
30
  // signer composes this so the emitted ECDSA signature is canonical DER (minimally-encoded
60
31
  // INTEGERs via the build layer), never a hand-built SEQUENCE that could re-introduce a
61
32
  // non-minimal encoding. A config-time TypeError guards a mis-sized raw signature at entry.
62
33
  // @enforced-by behavioral -- a DER ECDSA-Sig-Value BUILD has no rename-proof code shape distinct
63
- // from generic sequence([integer,integer]); the round-trip vectors (build -> ecdsaSigToRaw
34
+ // from generic sequence([integer,integer]); the round-trip vectors (build -> ecdsaDerToP1363
64
35
  // identity) and the cms.sign ECDSA KATs are the guard.
65
36
  function rawToEcdsaDer(raw, coordLen) {
66
37
  if (!Buffer.isBuffer(raw) || typeof coordLen !== "number" || coordLen <= 0 || raw.length !== coordLen * 2) {
@@ -82,12 +53,15 @@ var CURVE_ORDER = {
82
53
  // ecdsaDerToP1363(der, curve, E, code) -> the DER ECDSA-Sig-Value converted to raw r||s (P1363),
83
54
  // each coordinate left-padded to the curve field width, rejecting r or s outside [1, n-1] against
84
55
  // the curve ORDER (CVE-2022-21449 "Psychic Signatures" -- the r/s = 0 case AND the >= n upper
85
- // bound). `curve` is a WebCrypto namedCurve (P-256/384/521). This is the ORDER-AWARE gate a
86
- // verifier that knows the curve order MUST use; it is STRICTER than ecdsaSigToRaw above, which
87
- // bounds r/s only by the field SIZE (>= 1, <= coordLen bytes) and does not know the order. A
88
- // signature whose r or s is >= n is rejected here but passes ecdsaSigToRaw -- do not conflate them.
89
- // @enforced-by behavioral -- the RED conformance vectors (r/s = 0, r/s >= n, non-minimal, over-size)
90
- // and the composite-signature + path-validation KATs are the guard.
56
+ // bound). `curve` is a WebCrypto namedCurve (P-256/384/521). This is the SINGLE ECDSA-Sig-Value
57
+ // conformance gate every curve-aware verifier routes through: it enforces the complete strict-DER
58
+ // rule set (2-INTEGER SEQUENCE, minimal encoding) AND the order bound, so a verifier never
59
+ // hand-decodes the SEQUENCE (skipping minimality) nor bounds r/s only by the field size (missing
60
+ // the order). A non-minimal, negative, zero, over-size, or >= n coordinate fails closed.
61
+ // @enforced-by behavioral -- a DER ECDSA-Sig-Value decode has no rename-proof code shape distinct
62
+ // from generic 2-child-SEQUENCE content access; the RED conformance vectors (r/s = 0, r/s >= n,
63
+ // non-minimal, negative, over-size) and the cms / ct / composite / path / webauthn ECDSA KATs are
64
+ // the guard.
91
65
  function ecdsaDerToP1363(der, curve, E, code) {
92
66
  var width = CURVE_FIELD_BYTES[curve];
93
67
  var order = CURVE_ORDER[curve];
@@ -99,8 +73,8 @@ function ecdsaDerToP1363(der, curve, E, code) {
99
73
  throw new E(code, "ECDSA signature must be a SEQUENCE of exactly two INTEGERs");
100
74
  }
101
75
  // Wrap the strict DER integer reads (they enforce PRIMITIVE + MINIMAL encoding) so a non-minimal /
102
- // constructed / empty INTEGER surfaces the CALLER's typed code, not a raw asn1/* -- the same behavior
103
- // as ecdsaSigToRaw, so this gate is a strict superset (DER conformance + the order bound below).
76
+ // constructed / empty INTEGER surfaces the CALLER's typed code, not a raw asn1/* -- the complete DER
77
+ // conformance rule set, gated together with the order bound below.
104
78
  var r, s;
105
79
  try { r = asn1.read.integer(n.children[0]); } catch (e) { throw new E(code, "ECDSA signature r is not a minimally-encoded DER INTEGER", e); }
106
80
  try { s = asn1.read.integer(n.children[1]); } catch (e) { throw new E(code, "ECDSA signature s is not a minimally-encoded DER INTEGER", e); }
@@ -122,4 +96,4 @@ function ecdsaDerToP1363(der, curve, E, code) {
122
96
  return Buffer.concat([pad(r), pad(s)]);
123
97
  }
124
98
 
125
- module.exports = { ecdsaSigToRaw: ecdsaSigToRaw, rawToEcdsaDer: rawToEcdsaDer, ecdsaDerToP1363: ecdsaDerToP1363 };
99
+ module.exports = { rawToEcdsaDer: rawToEcdsaDer, ecdsaDerToP1363: ecdsaDerToP1363 };
package/lib/webcrypto.js CHANGED
@@ -1000,10 +1000,27 @@ Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
1000
1000
 
1001
1001
  Crypto.prototype.randomUUID = function randomUUID() { return nodeCrypto.randomUUID(); };
1002
1002
 
1003
+ // decompressEcPoint(sec1Compressed, nodeCurve) -> the uncompressed SEC1 point 0x04||X||Y for a
1004
+ // compressed 0x02/0x03||X input, computing Y on-curve via node's ECDH.convertKey. The crypto-engine
1005
+ // home for EC point de-compression (Hard rule #8: EC key material is webcrypto's domain). Fail-closed:
1006
+ // convertKey throws on an off-curve X or an unsupported curve, surfaced as the caller's typed error.
1007
+ // `nodeCurve` is the OpenSSL curve name (prime256v1 / secp384r1 / secp521r1). The C509-specific
1008
+ // 0xFE/0xFD marker -> 0x02/0x03 parity translation stays in the C509 layer (this takes a real SEC1 point).
1009
+ function decompressEcPoint(sec1Compressed, nodeCurve, E, code) {
1010
+ var head = sec1Compressed[0];
1011
+ if (head !== 0x02 && head !== 0x03) throw E(code, "EC point de-compression expects a compressed SEC1 point (0x02/0x03)");
1012
+ try {
1013
+ return nodeCrypto.ECDH.convertKey(sec1Compressed, nodeCurve, undefined, undefined, "uncompressed");
1014
+ } catch (e) {
1015
+ throw E(code, "EC point is not on curve " + nodeCurve + " (de-compression failed)", e);
1016
+ }
1017
+ }
1018
+
1003
1019
  module.exports = {
1004
1020
  webcrypto: new Crypto(),
1005
1021
  Crypto: Crypto,
1006
1022
  SubtleCrypto: SubtleCrypto,
1007
1023
  CryptoKey: CryptoKey,
1008
1024
  WebCryptoError: WebCryptoError,
1025
+ decompressEcPoint: decompressEcPoint,
1009
1026
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:2699d42b-0d6d-4b83-a3b9-1e8816b4e49a",
5
+ "serialNumber": "urn:uuid:c89087a8-9dba-4496-9704-cd639f3c4038",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-16T15:08:05.156Z",
8
+ "timestamp": "2026-07-16T21:10:43.618Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.2.29",
22
+ "bom-ref": "@blamejs/pki@0.2.31",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.2.29",
25
+ "version": "0.2.31",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.2.29",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.2.31",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.2.29",
57
+ "ref": "@blamejs/pki@0.2.31",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]