@blamejs/pki 0.5.33 → 0.5.34

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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to `@blamejs/pki` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.5.34 — 2026-08-29
8
+
9
+ A subjectAltName URI shorthand whose authority is a bracketed IPv6 literal is now accepted rather than refused.
10
+
11
+ ### Fixed
12
+
13
+ - A bare-string `subjectAltName` entry whose value is a URI with a bracketed IPv6 authority, such as `https://[2001:db8::1]:443/path`, now classifies as a `uniformResourceIdentifier`. The bracketed host is taken as one unit before an optional port, so a colon inside the IPv6 literal is no longer read as the port separator. An unterminated bracket, a bracket body that is not a valid IPv6 address, or bytes other than a port after the closing bracket stay refused, and the explicit `{ uniformResourceIdentifier: ... }` object form is unchanged.
14
+
7
15
  ## v0.5.33 — 2026-08-29
8
16
 
9
17
  pki.cms.sign and pki.cms.verify accept the content as an async iterable of byte chunks, so a detached signature over a large payload is produced and verified without holding the payload in memory.
package/lib/pki-build.js CHANGED
@@ -70,7 +70,8 @@ function _isSubDelim(c) {
70
70
  function _isUserinfoByte(c) { return _isUnreserved(c) || _isSubDelim(c) || c === 58; }
71
71
  // A path/query/fragment byte: unreserved / sub-delim / a non-bracket gen-delim (":" "/" "?" "#" "@").
72
72
  // "%" is handled separately (it must open a "%HEXHEX" triplet); the IP-literal brackets "[" "]" are
73
- // excluded, so an IPv6-literal host is refused by the shorthand and uses the object form.
73
+ // excluded here because they are authority syntax -- a bracketed IPv6 host is validated in
74
+ // _validUriAuthority (RFC 3986 sec. 3.2.2), never in the path.
74
75
  function _isUriByte(c) {
75
76
  return _isUnreserved(c) || _isSubDelim(c) || c === 58 || c === 47 || c === 63 || c === 35 || c === 64;
76
77
  }
@@ -122,8 +123,8 @@ function _componentBytesOk(s, from, to, byteOk) {
122
123
  // or an FQDN of strict-LDH labels (the email-domain grammar: no underscore, no hyphen at a label edge) with
123
124
  // at least one dot -- a single-label host such as "localhost" is not fully qualified and uses the object
124
125
  // form, matching the email domain. An IPv4-SHAPED host that is not a valid address is refused as ambiguous
125
- // (the same rule the top-level classifier uses), and an IPv6-literal host -- needing the excluded "[" "]"
126
- // -- uses the object form.
126
+ // (the same rule the top-level classifier uses). An IPv6-literal host is written in "[" "]" and validated
127
+ // by _validUriIp6Host from _validUriAuthority, never reaching this unbracketed-host check.
127
128
  function _validUriHost(s, from, to) {
128
129
  if (to <= from) return false; // empty host
129
130
  var host = _sliceStr(s, from, to), len = host.length;
@@ -135,10 +136,21 @@ function _validUriHost(s, from, to) {
135
136
  return _labelsValid(host, 0, len, _isHostLabelRange, true); // strict-LDH FQDN, root "." allowed
136
137
  }
137
138
 
139
+ // A bracketed IPv6-literal URI host (RFC 3986 sec. 3.2.2): the bytes between "[" and "]" must be a valid
140
+ // IPv6 address (16 packed octets), never an IPv4 literal (which uses the unbracketed IPv4address form). The
141
+ // bracket is the ONLY way to write an IPv6 host in a URI, and an IPv6 host is a valid iPAddress per RFC 5280
142
+ // sec. 4.2.1.6, so the shorthand accepts it rather than forcing the object form.
143
+ function _validUriIp6Host(s, from, to) {
144
+ if (to <= from) return false; // empty "[ ]"
145
+ var packed = _packIpLiteral(_sliceStr(s, from, to));
146
+ return packed !== null && intrinsic.sizeOf(packed) === 16; // IPv6 literal only (16 octets)
147
+ }
148
+
138
149
  // The authority in [from,to) is "[ userinfo "@" ] host [ ":" port ]" (RFC 3986 sec. 3.2): at most one "@"
139
- // (userinfo and reg-name each exclude it), a host validated by _validUriHost (RFC 5280 FQDN or IPv4), and
140
- // an all-digit -- possibly empty -- port after the host's single ":" (a reg-name has no ":"). The userinfo
141
- // bytes are validated against the RFC 3986 userinfo class.
150
+ // (userinfo and reg-name each exclude it), a host that is an RFC 5280 FQDN or IPv4 (_validUriHost) or a
151
+ // bracketed IPv6 literal (_validUriIp6Host), and an all-digit -- possibly empty -- port after the host's
152
+ // ":". A bracketed host is taken as ONE unit before the port, so a ":" inside the IPv6 literal is not read
153
+ // as the port separator. The userinfo bytes are validated against the RFC 3986 userinfo class.
142
154
  function _validUriAuthority(s, from, to) {
143
155
  var i, c, at = -1;
144
156
  for (i = from; i < to; i += 1) {
@@ -147,6 +159,17 @@ function _validUriAuthority(s, from, to) {
147
159
  }
148
160
  var hostStart = at === -1 ? from : at + 1;
149
161
  if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false; // userinfo
162
+ if (hostStart < to && _charCodeAt(s, hostStart) === 91) { // "[" -> bracketed IPv6 host
163
+ var close = -1;
164
+ for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } } // "]"
165
+ if (close === -1) return false; // unterminated "["
166
+ if (!_validUriIp6Host(s, hostStart + 1, close)) return false; // IPv6 literal in "[ ]"
167
+ var after = close + 1;
168
+ if (after === to) return true; // "[ ipv6 ]" with no port
169
+ if (_charCodeAt(s, after) !== 58) return false; // only a ":" port may follow "]"
170
+ for (i = after + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; } // port = *DIGIT
171
+ return true;
172
+ }
150
173
  var colon = -1;
151
174
  for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } } // ":" -> port
152
175
  var hostEnd = colon === -1 ? to : colon;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.33",
3
+ "version": "0.5.34",
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:50c541ec-c574-4bac-aa78-36123ed457dc",
5
+ "serialNumber": "urn:uuid:849173ca-4a17-451d-91be-1f2d75d83f00",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-29T08:57:14.236Z",
8
+ "timestamp": "2026-08-29T10:04:44.768Z",
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.5.33",
22
+ "bom-ref": "@blamejs/pki@0.5.34",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.33",
25
+ "version": "0.5.34",
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.5.33",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.34",
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.5.33",
57
+ "ref": "@blamejs/pki@0.5.34",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]