@blamejs/core 0.8.60 → 0.8.64
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 +4 -0
- package/README.md +2 -2
- package/index.js +11 -0
- package/lib/audit.js +1 -0
- package/lib/auth/ciba.js +530 -0
- package/lib/auth/oauth.js +199 -11
- package/lib/auth/oid4vci.js +588 -0
- package/lib/auth/oid4vp.js +514 -0
- package/lib/auth/openid-federation.js +523 -0
- package/lib/auth/saml.js +636 -0
- package/lib/auth/sd-jwt-vc-holder.js +30 -8
- package/lib/auth/sd-jwt-vc.js +61 -7
- package/lib/db-collection.js +402 -105
- package/lib/db-file-lifecycle.js +333 -0
- package/lib/session-stores.js +138 -0
- package/lib/session.js +307 -20
- package/lib/validate-opts.js +41 -0
- package/lib/xml-c14n.js +499 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/xml-c14n.js
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.xmlC14n
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title XML Exclusive Canonicalization
|
|
6
|
+
* @order 560
|
|
7
|
+
* @card RFC 3741 Exclusive XML Canonicalization 1.0 — the
|
|
8
|
+
* canonical form XMLDSig requires, and the missing piece
|
|
9
|
+
* that lets `b.guardXml` defend against XML signature-
|
|
10
|
+
* wrapping attacks.
|
|
11
|
+
*
|
|
12
|
+
* @intro
|
|
13
|
+
* XML signatures cover canonicalized bytes, not the source XML.
|
|
14
|
+
* Two structurally-equivalent XML documents (different attribute
|
|
15
|
+
* ordering, different namespace prefixes, different whitespace)
|
|
16
|
+
* must produce the same canonical bytes; otherwise an attacker
|
|
17
|
+
* could swap a benign signed assertion for a malicious one whose
|
|
18
|
+
* parsed tree is identical but whose serialized bytes differ.
|
|
19
|
+
*
|
|
20
|
+
* This module implements the SAML/XMLDSig-relevant subset of
|
|
21
|
+
* RFC 3741 Exclusive XML Canonicalization 1.0 plus
|
|
22
|
+
* `xml-exc-c14n#WithComments` (controlled via opts).
|
|
23
|
+
*
|
|
24
|
+
* What's covered (the v1-defensible SAML/SP subset):
|
|
25
|
+
*
|
|
26
|
+
* - UTF-8 output with no BOM
|
|
27
|
+
* - Element + attribute serialization with `&`, `<`, `>`, `"`,
|
|
28
|
+
* `\r`, `\t`, `\n` proper escaping per §1.3.2
|
|
29
|
+
* - Attribute ordering: namespace declarations first (alphabetical
|
|
30
|
+
* by namespace prefix, `xmlns` before `xmlns:foo`); regular
|
|
31
|
+
* attributes second (by namespace URI, then local name)
|
|
32
|
+
* - Exclusive namespace propagation: only the namespace prefixes
|
|
33
|
+
* *visibly used* by the canonicalized subtree are emitted
|
|
34
|
+
* - Empty elements expanded (`<a/>` → `<a></a>`)
|
|
35
|
+
* - Whitespace normalization in attribute values
|
|
36
|
+
* - Comments suppressed by default; `withComments: true` keeps
|
|
37
|
+
* them per `xml-exc-c14n#WithComments`
|
|
38
|
+
*
|
|
39
|
+
* What's NOT covered (deferred — open conditions on first
|
|
40
|
+
* operator demand or live SAML interop need):
|
|
41
|
+
*
|
|
42
|
+
* - `InclusiveNamespaces PrefixList` (the `<ec:InclusiveNamespaces
|
|
43
|
+
* PrefixList="..."/>` Transform parameter — we always operate
|
|
44
|
+
* in the strict exclusive mode without an inclusive list).
|
|
45
|
+
* - Inherited XML namespace propagation for `xml:lang`,
|
|
46
|
+
* `xml:space`, `xml:base` past the canonicalization boundary.
|
|
47
|
+
*
|
|
48
|
+
* Surface:
|
|
49
|
+
*
|
|
50
|
+
* b.xmlC14n.canonicalize(xmlString | parsedTree, opts?)
|
|
51
|
+
* → Buffer of canonicalized UTF-8 bytes
|
|
52
|
+
* b.xmlC14n.canonicalizeElementById(xmlString, id, opts?)
|
|
53
|
+
* → Buffer of c14n'd bytes for the element whose `ID="<id>"`
|
|
54
|
+
* attribute matches (used by XMLDSig Reference resolution)
|
|
55
|
+
* b.xmlC14n.parse(xmlString) → DOM tree (used by SAML)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
var validateOpts = require("./validate-opts");
|
|
59
|
+
var { defineClass } = require("./framework-error");
|
|
60
|
+
|
|
61
|
+
var XmlC14nError = defineClass("XmlC14nError", { alwaysPermanent: true });
|
|
62
|
+
function _xmlErr(code, message) { return new XmlC14nError(code, message); }
|
|
63
|
+
|
|
64
|
+
var MAX_INPUT_BYTES = 8 * 1024 * 1024; // allow:raw-byte-literal — XML doc cap (8 MiB)
|
|
65
|
+
var MAX_DEPTH = 200; // allow:raw-byte-literal — element nesting depth ceiling
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @primitive b.xmlC14n.parse
|
|
69
|
+
* @signature b.xmlC14n.parse(xml)
|
|
70
|
+
* @since 0.8.62
|
|
71
|
+
* @status stable
|
|
72
|
+
* @related b.xmlC14n.canonicalize, b.xmlC14n.canonicalizeElementById
|
|
73
|
+
*
|
|
74
|
+
* Lightweight DOM parser: produces a simple node tree with
|
|
75
|
+
* `{ type, name, attrs, children, parent }`. Node types: "element"
|
|
76
|
+
* / "text" / "comment". The parser is strict about what it refuses
|
|
77
|
+
* (DOCTYPE, ENTITY, malformed entity references); XML c14n is a
|
|
78
|
+
* security primitive and an over-permissive parser undermines the
|
|
79
|
+
* canonicalization guarantees downstream. Operators rarely call
|
|
80
|
+
* this directly — `canonicalize` and `canonicalizeElementById`
|
|
81
|
+
* accept either a string OR a parsed node, so the parsed-tree path
|
|
82
|
+
* is exposed mainly for the SAML primitive's signature-element
|
|
83
|
+
* lookup and operator-side custom traversal.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* var tree = b.xmlC14n.parse("<root><child id=\"x\"/></root>");
|
|
87
|
+
* tree.type; // → "element"
|
|
88
|
+
* tree.name; // → "root"
|
|
89
|
+
* tree.children[0].name;// → "child"
|
|
90
|
+
*/
|
|
91
|
+
function parse(xml) {
|
|
92
|
+
if (typeof xml !== "string") {
|
|
93
|
+
if (Buffer.isBuffer(xml)) xml = xml.toString("utf8");
|
|
94
|
+
else throw _xmlErr("xml-c14n/bad-input", "parse: input must be a string or Buffer");
|
|
95
|
+
}
|
|
96
|
+
if (xml.length === 0) throw _xmlErr("xml-c14n/empty", "parse: input is empty");
|
|
97
|
+
if (xml.length > MAX_INPUT_BYTES) {
|
|
98
|
+
throw _xmlErr("xml-c14n/too-large",
|
|
99
|
+
"parse: input exceeds " + MAX_INPUT_BYTES + " bytes");
|
|
100
|
+
}
|
|
101
|
+
// Strip BOM if present
|
|
102
|
+
if (xml.charCodeAt(0) === 0xFEFF) xml = xml.slice(1);
|
|
103
|
+
// Refuse DOCTYPE outright (XXE, billion-laughs class)
|
|
104
|
+
if (/<!DOCTYPE/i.test(xml)) {
|
|
105
|
+
throw _xmlErr("xml-c14n/doctype-refused",
|
|
106
|
+
"parse: <!DOCTYPE> declarations refused for canonicalization input");
|
|
107
|
+
}
|
|
108
|
+
if (/<!ENTITY/i.test(xml)) {
|
|
109
|
+
throw _xmlErr("xml-c14n/entity-refused",
|
|
110
|
+
"parse: <!ENTITY> declarations refused");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var pos = 0;
|
|
114
|
+
function err(msg) {
|
|
115
|
+
throw _xmlErr("xml-c14n/parse", "parse: " + msg + " at offset " + pos);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function skipWhitespace() {
|
|
119
|
+
while (pos < xml.length && /\s/.test(xml.charAt(pos))) pos += 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function skipProlog() {
|
|
123
|
+
skipWhitespace();
|
|
124
|
+
while (xml.substr(pos, 5) === "<?xml" ||
|
|
125
|
+
xml.substr(pos, 4) === "<!--" ||
|
|
126
|
+
xml.substr(pos, 2) === "<?") {
|
|
127
|
+
if (xml.substr(pos, 4) === "<!--") {
|
|
128
|
+
var end = xml.indexOf("-->", pos);
|
|
129
|
+
if (end === -1) err("unterminated comment in prolog");
|
|
130
|
+
pos = end + 3;
|
|
131
|
+
} else {
|
|
132
|
+
var endPi = xml.indexOf("?>", pos);
|
|
133
|
+
if (endPi === -1) err("unterminated processing instruction");
|
|
134
|
+
pos = endPi + 2;
|
|
135
|
+
}
|
|
136
|
+
skipWhitespace();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function readName() {
|
|
141
|
+
var start = pos;
|
|
142
|
+
if (!/[A-Za-z_:]/.test(xml.charAt(pos))) err("expected name");
|
|
143
|
+
pos += 1;
|
|
144
|
+
while (pos < xml.length && /[A-Za-z0-9._:-]/.test(xml.charAt(pos))) pos += 1;
|
|
145
|
+
return xml.slice(start, pos);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readAttrValue() {
|
|
149
|
+
var quote = xml.charAt(pos);
|
|
150
|
+
if (quote !== "\"" && quote !== "'") err("attribute value must be quoted");
|
|
151
|
+
pos += 1;
|
|
152
|
+
var start = pos;
|
|
153
|
+
while (pos < xml.length && xml.charAt(pos) !== quote) {
|
|
154
|
+
if (xml.charAt(pos) === "<") err("'<' not allowed in attribute value");
|
|
155
|
+
pos += 1;
|
|
156
|
+
}
|
|
157
|
+
if (pos >= xml.length) err("unterminated attribute value");
|
|
158
|
+
var raw = xml.slice(start, pos);
|
|
159
|
+
pos += 1; // closing quote
|
|
160
|
+
return _decodeEntities(raw);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _decodeEntities(s) {
|
|
164
|
+
return s.replace(/&([^;]+);/g, function (match, name) {
|
|
165
|
+
switch (name) {
|
|
166
|
+
case "amp": return "&";
|
|
167
|
+
case "lt": return "<";
|
|
168
|
+
case "gt": return ">";
|
|
169
|
+
case "quot": return "\"";
|
|
170
|
+
case "apos": return "'";
|
|
171
|
+
default:
|
|
172
|
+
if (name.charAt(0) === "#") {
|
|
173
|
+
var code;
|
|
174
|
+
if (name.charAt(1) === "x" || name.charAt(1) === "X") {
|
|
175
|
+
code = parseInt(name.slice(2), 16); // allow:raw-byte-literal — hex radix
|
|
176
|
+
} else {
|
|
177
|
+
code = parseInt(name.slice(1), 10);
|
|
178
|
+
}
|
|
179
|
+
if (Number.isFinite(code) && code >= 0 && code <= 0x10ffff) {
|
|
180
|
+
return String.fromCodePoint(code);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw _xmlErr("xml-c14n/unknown-entity",
|
|
184
|
+
"decodeEntities: unsupported entity reference \"&" + name + ";\"");
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseElement() {
|
|
190
|
+
if (xml.charAt(pos) !== "<") err("expected '<'");
|
|
191
|
+
pos += 1;
|
|
192
|
+
var name = readName();
|
|
193
|
+
var attrs = [];
|
|
194
|
+
var selfClosing = false;
|
|
195
|
+
skipWhitespace();
|
|
196
|
+
while (pos < xml.length && xml.charAt(pos) !== ">" && xml.charAt(pos) !== "/") {
|
|
197
|
+
var attrName = readName();
|
|
198
|
+
skipWhitespace();
|
|
199
|
+
if (xml.charAt(pos) !== "=") err("expected '=' after attribute name");
|
|
200
|
+
pos += 1;
|
|
201
|
+
skipWhitespace();
|
|
202
|
+
var value = readAttrValue();
|
|
203
|
+
attrs.push({ name: attrName, value: value });
|
|
204
|
+
skipWhitespace();
|
|
205
|
+
}
|
|
206
|
+
if (xml.charAt(pos) === "/") {
|
|
207
|
+
selfClosing = true;
|
|
208
|
+
pos += 1;
|
|
209
|
+
if (xml.charAt(pos) !== ">") err("expected '>' after '/'");
|
|
210
|
+
}
|
|
211
|
+
if (xml.charAt(pos) !== ">") err("expected '>'");
|
|
212
|
+
pos += 1;
|
|
213
|
+
var node = {
|
|
214
|
+
type: "element",
|
|
215
|
+
name: name,
|
|
216
|
+
attrs: attrs,
|
|
217
|
+
children: [],
|
|
218
|
+
parent: null,
|
|
219
|
+
};
|
|
220
|
+
if (selfClosing) return node;
|
|
221
|
+
|
|
222
|
+
var depth = 0;
|
|
223
|
+
var closeTag = "</" + name + ">";
|
|
224
|
+
while (pos < xml.length) {
|
|
225
|
+
if (xml.substr(pos, 4) === "<!--") {
|
|
226
|
+
var endC = xml.indexOf("-->", pos);
|
|
227
|
+
if (endC === -1) err("unterminated comment");
|
|
228
|
+
node.children.push({ type: "comment", text: xml.slice(pos + 4, endC), parent: node });
|
|
229
|
+
pos = endC + 3;
|
|
230
|
+
} else if (xml.charAt(pos) === "<" && xml.charAt(pos + 1) !== "/") {
|
|
231
|
+
if (xml.substr(pos, 9) === "<![CDATA[") {
|
|
232
|
+
var endCData = xml.indexOf("]]>", pos);
|
|
233
|
+
if (endCData === -1) err("unterminated CDATA");
|
|
234
|
+
node.children.push({ type: "text", text: xml.slice(pos + 9, endCData), parent: node, isCdata: true });
|
|
235
|
+
pos = endCData + 3;
|
|
236
|
+
} else if (xml.charAt(pos + 1) === "?") {
|
|
237
|
+
var endPi = xml.indexOf("?>", pos);
|
|
238
|
+
if (endPi === -1) err("unterminated PI");
|
|
239
|
+
// Skip — c14n drops PIs outside the canonicalized subtree
|
|
240
|
+
// boundary; we can include them but operators don't need
|
|
241
|
+
// them for SAML.
|
|
242
|
+
pos = endPi + 2;
|
|
243
|
+
} else {
|
|
244
|
+
depth += 1;
|
|
245
|
+
if (depth > MAX_DEPTH) err("max nesting depth (" + MAX_DEPTH + ") exceeded");
|
|
246
|
+
var child = parseElement();
|
|
247
|
+
child.parent = node;
|
|
248
|
+
node.children.push(child);
|
|
249
|
+
depth -= 1;
|
|
250
|
+
}
|
|
251
|
+
} else if (xml.charAt(pos) === "<" && xml.charAt(pos + 1) === "/") {
|
|
252
|
+
// Closing tag
|
|
253
|
+
if (xml.substr(pos, closeTag.length) !== closeTag) {
|
|
254
|
+
err("expected </" + name + ">");
|
|
255
|
+
}
|
|
256
|
+
pos += closeTag.length;
|
|
257
|
+
return node;
|
|
258
|
+
} else {
|
|
259
|
+
// Text content
|
|
260
|
+
var textStart = pos;
|
|
261
|
+
while (pos < xml.length && xml.charAt(pos) !== "<") pos += 1;
|
|
262
|
+
var text = _decodeEntities(xml.slice(textStart, pos));
|
|
263
|
+
node.children.push({ type: "text", text: text, parent: node });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
err("unterminated element </" + name + ">");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
skipProlog();
|
|
270
|
+
if (pos >= xml.length) err("no root element");
|
|
271
|
+
var root = parseElement();
|
|
272
|
+
return root;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Build a namespace map { prefix → uri } for an element by walking
|
|
276
|
+
// from root to it; the empty prefix is the default namespace.
|
|
277
|
+
function _namespacesInScope(node) {
|
|
278
|
+
var stack = [];
|
|
279
|
+
var cur = node;
|
|
280
|
+
while (cur) { stack.unshift(cur); cur = cur.parent; }
|
|
281
|
+
var nsMap = {};
|
|
282
|
+
stack.forEach(function (el) {
|
|
283
|
+
if (!el.attrs) return;
|
|
284
|
+
el.attrs.forEach(function (a) {
|
|
285
|
+
if (a.name === "xmlns") nsMap[""] = a.value;
|
|
286
|
+
else if (a.name.indexOf("xmlns:") === 0) nsMap[a.name.substring(6)] = a.value;
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
return nsMap;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Determine the prefixes a given element uses *visibly*: its own
|
|
293
|
+
// element prefix, any attribute prefixes, and the default namespace
|
|
294
|
+
// when the element has no explicit prefix.
|
|
295
|
+
function _prefixesUsedByElement(element) {
|
|
296
|
+
var used = new Set();
|
|
297
|
+
var elementPrefix = "";
|
|
298
|
+
var colon = element.name.indexOf(":");
|
|
299
|
+
if (colon !== -1) elementPrefix = element.name.substring(0, colon);
|
|
300
|
+
used.add(elementPrefix);
|
|
301
|
+
element.attrs.forEach(function (a) {
|
|
302
|
+
if (a.name === "xmlns" || a.name.indexOf("xmlns:") === 0) return;
|
|
303
|
+
var ac = a.name.indexOf(":");
|
|
304
|
+
if (ac !== -1) used.add(a.name.substring(0, ac));
|
|
305
|
+
});
|
|
306
|
+
return used;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function _escapeAttrValue(s) {
|
|
310
|
+
// Per RFC 3741 §1.3.2: "&" → "&", "<" → "<", `"` → """,
|
|
311
|
+
// \r → "
", \n → "
", \t → "	".
|
|
312
|
+
return String(s)
|
|
313
|
+
.replace(/&/g, "&")
|
|
314
|
+
.replace(/</g, "<")
|
|
315
|
+
.replace(/"/g, """)
|
|
316
|
+
.replace(/\r/g, "
")
|
|
317
|
+
.replace(/\n/g, "
")
|
|
318
|
+
.replace(/\t/g, "	");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function _escapeText(s) {
|
|
322
|
+
// Per RFC 3741 §1.3.1: "&" → "&", "<" → "<", ">" → ">",
|
|
323
|
+
// \r → "
".
|
|
324
|
+
return String(s)
|
|
325
|
+
.replace(/&/g, "&")
|
|
326
|
+
.replace(/</g, "<")
|
|
327
|
+
.replace(/>/g, ">")
|
|
328
|
+
.replace(/\r/g, "
");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function _serializeNode(node, ancestorRendered, withComments) {
|
|
332
|
+
if (node.type === "text") return _escapeText(node.text);
|
|
333
|
+
if (node.type === "comment") return withComments ? "<!--" + node.text + "-->" : "";
|
|
334
|
+
if (node.type !== "element") return "";
|
|
335
|
+
|
|
336
|
+
// Compute the namespace map visible from this element's scope.
|
|
337
|
+
var inScope = _namespacesInScope(node);
|
|
338
|
+
|
|
339
|
+
// Visibly-used prefixes by this element (per Exclusive c14n §2)
|
|
340
|
+
var used = _prefixesUsedByElement(node);
|
|
341
|
+
|
|
342
|
+
// Compute the namespace declarations to RENDER on this element.
|
|
343
|
+
// Exclusive c14n §2: render xmlns:p only if (a) p is in `used` and
|
|
344
|
+
// (b) p's binding wasn't already rendered by an ancestor in the
|
|
345
|
+
// canonicalized subtree.
|
|
346
|
+
var renderedHere = {};
|
|
347
|
+
var renderList = [];
|
|
348
|
+
var prefixes = Object.keys(inScope).sort(function (a, b) {
|
|
349
|
+
if (a === "" && b !== "") return -1;
|
|
350
|
+
if (b === "" && a !== "") return 1;
|
|
351
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
352
|
+
});
|
|
353
|
+
prefixes.forEach(function (p) {
|
|
354
|
+
if (!used.has(p)) return;
|
|
355
|
+
var uri = inScope[p];
|
|
356
|
+
if (ancestorRendered[p] === uri) return; // already rendered above
|
|
357
|
+
renderList.push({ prefix: p, uri: uri });
|
|
358
|
+
renderedHere[p] = uri;
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Sort attributes: regular attributes per c14n §2.4 order — by
|
|
362
|
+
// namespace URI (no NS first), then by local name.
|
|
363
|
+
var regularAttrs = node.attrs
|
|
364
|
+
.filter(function (a) { return a.name !== "xmlns" && a.name.indexOf("xmlns:") !== 0; })
|
|
365
|
+
.map(function (a) {
|
|
366
|
+
var aColon = a.name.indexOf(":");
|
|
367
|
+
var aPrefix = aColon !== -1 ? a.name.substring(0, aColon) : "";
|
|
368
|
+
var aLocal = aColon !== -1 ? a.name.substring(aColon + 1) : a.name;
|
|
369
|
+
var aUri = aPrefix && inScope[aPrefix] ? inScope[aPrefix] : "";
|
|
370
|
+
return { name: a.name, value: a.value, prefix: aPrefix, local: aLocal, uri: aUri };
|
|
371
|
+
})
|
|
372
|
+
.sort(function (a, b) {
|
|
373
|
+
if (a.uri !== b.uri) return a.uri < b.uri ? -1 : 1;
|
|
374
|
+
return a.local < b.local ? -1 : a.local > b.local ? 1 : 0;
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
var out = "<" + node.name;
|
|
378
|
+
|
|
379
|
+
// Render namespace declarations (sorted; xmlns first, then xmlns:foo
|
|
380
|
+
// alphabetical by prefix)
|
|
381
|
+
renderList.forEach(function (r) {
|
|
382
|
+
if (r.prefix === "") {
|
|
383
|
+
out += " xmlns=\"" + _escapeAttrValue(r.uri) + "\"";
|
|
384
|
+
} else {
|
|
385
|
+
out += " xmlns:" + r.prefix + "=\"" + _escapeAttrValue(r.uri) + "\"";
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
// Render regular attributes
|
|
389
|
+
regularAttrs.forEach(function (a) {
|
|
390
|
+
out += " " + a.name + "=\"" + _escapeAttrValue(a.value) + "\"";
|
|
391
|
+
});
|
|
392
|
+
out += ">";
|
|
393
|
+
|
|
394
|
+
// Merge ancestorRendered with renderedHere for child scope.
|
|
395
|
+
var childScope = Object.assign({}, ancestorRendered, renderedHere);
|
|
396
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
397
|
+
out += _serializeNode(node.children[i], childScope, withComments);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
out += "</" + node.name + ">";
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @primitive b.xmlC14n.canonicalize
|
|
406
|
+
* @signature b.xmlC14n.canonicalize(input, opts?)
|
|
407
|
+
* @since 0.8.62
|
|
408
|
+
* @status stable
|
|
409
|
+
* @related b.xmlC14n.canonicalizeElementById, b.guardXml
|
|
410
|
+
*
|
|
411
|
+
* Produce the RFC 3741 Exclusive XML Canonicalization 1.0 byte
|
|
412
|
+
* sequence for an XML document or a parsed DOM node. Returns a
|
|
413
|
+
* Buffer of UTF-8 bytes.
|
|
414
|
+
*
|
|
415
|
+
* @opts
|
|
416
|
+
* {
|
|
417
|
+
* withComments?: boolean, // default false (per xml-exc-c14n)
|
|
418
|
+
* }
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* var c = b.xmlC14n.canonicalize("<a:foo xmlns:a='urn:x'><a:bar/></a:foo>");
|
|
422
|
+
* // → Buffer<<a:foo xmlns:a="urn:x"><a:bar></a:bar></a:foo>>
|
|
423
|
+
*/
|
|
424
|
+
function canonicalize(input, opts) {
|
|
425
|
+
opts = opts || {};
|
|
426
|
+
var node = (typeof input === "string" || Buffer.isBuffer(input)) ? parse(input) : input;
|
|
427
|
+
if (!node || node.type !== "element") {
|
|
428
|
+
throw _xmlErr("xml-c14n/bad-input",
|
|
429
|
+
"canonicalize: input must be an XML string or a parsed element node");
|
|
430
|
+
}
|
|
431
|
+
var bytes = _serializeNode(node, {}, opts.withComments === true);
|
|
432
|
+
return Buffer.from(bytes, "utf8");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @primitive b.xmlC14n.canonicalizeElementById
|
|
437
|
+
* @signature b.xmlC14n.canonicalizeElementById(xml, id, opts?)
|
|
438
|
+
* @since 0.8.62
|
|
439
|
+
* @status stable
|
|
440
|
+
* @related b.xmlC14n.canonicalize, b.guardXml
|
|
441
|
+
*
|
|
442
|
+
* Find the element whose `ID` (or operator-specified attribute name)
|
|
443
|
+
* matches the supplied id, then return its canonical-form bytes.
|
|
444
|
+
* Throws if zero or more than one element matches — this single-
|
|
445
|
+
* match invariant is the core defense against XML signature-wrapping
|
|
446
|
+
* attacks where an attacker injects a sibling assertion with the
|
|
447
|
+
* same ID hoping the verifier picks the wrong one.
|
|
448
|
+
*
|
|
449
|
+
* @opts
|
|
450
|
+
* {
|
|
451
|
+
* attrName?: string, // default "ID"
|
|
452
|
+
* withComments?: boolean,
|
|
453
|
+
* }
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* var bytes = b.xmlC14n.canonicalizeElementById(
|
|
457
|
+
* "<root><a ID=\"sig\">payload</a></root>",
|
|
458
|
+
* "sig"
|
|
459
|
+
* );
|
|
460
|
+
* // → Buffer<<a ID="sig">payload</a>>
|
|
461
|
+
*/
|
|
462
|
+
function canonicalizeElementById(xml, id, opts) {
|
|
463
|
+
validateOpts.requireNonEmptyString(id, "canonicalizeElementById: id", XmlC14nError, "xml-c14n/no-id");
|
|
464
|
+
opts = opts || {};
|
|
465
|
+
var root = parse(xml);
|
|
466
|
+
var attrName = opts.attrName || "ID";
|
|
467
|
+
var matches = [];
|
|
468
|
+
function walk(node) {
|
|
469
|
+
if (node.type !== "element") return;
|
|
470
|
+
if (node.attrs) {
|
|
471
|
+
for (var i = 0; i < node.attrs.length; i++) {
|
|
472
|
+
if (node.attrs[i].name === attrName && node.attrs[i].value === id) {
|
|
473
|
+
matches.push(node);
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
for (var ci = 0; ci < node.children.length; ci++) walk(node.children[ci]);
|
|
479
|
+
}
|
|
480
|
+
walk(root);
|
|
481
|
+
if (matches.length === 0) {
|
|
482
|
+
throw _xmlErr("xml-c14n/no-match",
|
|
483
|
+
"canonicalizeElementById: no element with " + attrName + "=\"" + id + "\"");
|
|
484
|
+
}
|
|
485
|
+
if (matches.length > 1) {
|
|
486
|
+
throw _xmlErr("xml-c14n/duplicate-id",
|
|
487
|
+
"canonicalizeElementById: " + matches.length + " elements share " + attrName +
|
|
488
|
+
"=\"" + id + "\" — refusing (signature-wrapping defense)");
|
|
489
|
+
}
|
|
490
|
+
var bytes = _serializeNode(matches[0], {}, opts.withComments === true);
|
|
491
|
+
return Buffer.from(bytes, "utf8");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = {
|
|
495
|
+
parse: parse,
|
|
496
|
+
canonicalize: canonicalize,
|
|
497
|
+
canonicalizeElementById: canonicalizeElementById,
|
|
498
|
+
XmlC14nError: XmlC14nError,
|
|
499
|
+
};
|
package/package.json
CHANGED
package/sbom.cyclonedx.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:
|
|
5
|
+
"serialNumber": "urn:uuid:23c254f3-eb31-48e9-84e5-285c0cd30ffe",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-10T06:15:43.727Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.8.64",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.64",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.8.64",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.8.
|
|
57
|
+
"ref": "@blamejs/core@0.8.64",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|