xmldsigjs
Version:
XML Digital Signature implementation in TypeScript/JavaScript using Web Crypto API
389 lines (388 loc) • 14.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.XmlCanonicalizer = exports.XmlCanonicalizerState = void 0;
const tslib_1 = require("tslib");
const XmlCore = tslib_1.__importStar(require("xml-core"));
var XmlCanonicalizerState;
(function (XmlCanonicalizerState) {
XmlCanonicalizerState[XmlCanonicalizerState["BeforeDocElement"] = 0] = "BeforeDocElement";
XmlCanonicalizerState[XmlCanonicalizerState["InsideDocElement"] = 1] = "InsideDocElement";
XmlCanonicalizerState[XmlCanonicalizerState["AfterDocElement"] = 2] = "AfterDocElement";
})(XmlCanonicalizerState || (exports.XmlCanonicalizerState = XmlCanonicalizerState = {}));
class XmlCanonicalizer {
constructor(withComments, excC14N, propagatedNamespaces = new XmlCore.NamespaceManager()) {
this.propagatedNamespaces = new XmlCore.NamespaceManager();
this.result = [];
this.visibleNamespaces = new XmlCore.NamespaceManager();
this.inclusiveNamespacesPrefixList = [];
this.state = XmlCanonicalizerState.BeforeDocElement;
this.withComments = withComments;
this.exclusive = excC14N;
this.propagatedNamespaces = propagatedNamespaces;
}
get InclusiveNamespacesPrefixList() {
return this.inclusiveNamespacesPrefixList.join(' ');
}
set InclusiveNamespacesPrefixList(value) {
this.inclusiveNamespacesPrefixList = value.split(' ');
}
Canonicalize(node) {
if (!node) {
throw new XmlCore.XmlError(XmlCore.XE.CRYPTOGRAPHIC, 'Parameter 1 is not Node');
}
this.WriteNode(node);
const res = this.result.join('');
return res;
}
WriteNode(node) {
switch (node.nodeType) {
case XmlCore.XmlNodeType.Document:
case XmlCore.XmlNodeType.DocumentFragment:
this.WriteDocumentNode(node);
break;
case XmlCore.XmlNodeType.Element:
this.WriteElementNode(node);
break;
case XmlCore.XmlNodeType.CDATA:
case XmlCore.XmlNodeType.SignificantWhitespace:
case XmlCore.XmlNodeType.Text:
if (!XmlCore.isDocument(node.parentNode)) {
this.WriteTextNode(node);
}
break;
case XmlCore.XmlNodeType.Whitespace:
if (this.state === XmlCanonicalizerState.InsideDocElement) {
this.WriteTextNode(node);
}
break;
case XmlCore.XmlNodeType.Comment:
this.WriteCommentNode(node);
break;
case XmlCore.XmlNodeType.ProcessingInstruction:
this.WriteProcessingInstructionNode(node);
break;
case XmlCore.XmlNodeType.EntityReference:
for (let i = 0; i < node.childNodes.length; i++) {
this.WriteNode(node.childNodes[i]);
}
break;
case XmlCore.XmlNodeType.Attribute:
throw new XmlCore.XmlError(XmlCore.XE.CRYPTOGRAPHIC, 'Attribute node is impossible here');
case XmlCore.XmlNodeType.EndElement:
throw new XmlCore.XmlError(XmlCore.XE.CRYPTOGRAPHIC, 'Attribute node is impossible here');
case XmlCore.XmlNodeType.EndEntity:
throw new XmlCore.XmlError(XmlCore.XE.CRYPTOGRAPHIC, 'Attribute node is impossible here');
case XmlCore.XmlNodeType.DocumentType:
case XmlCore.XmlNodeType.Entity:
case XmlCore.XmlNodeType.Notation:
case XmlCore.XmlNodeType.XmlDeclaration:
break;
}
}
WriteDocumentNode(node) {
this.state = XmlCanonicalizerState.BeforeDocElement;
for (let child = node.firstChild; child != null; child = child.nextSibling) {
this.WriteNode(child);
}
}
WriteCommentNode(node) {
if (this.withComments) {
if (this.state === XmlCanonicalizerState.AfterDocElement) {
this.result.push(String.fromCharCode(10) + '<!--');
}
else {
this.result.push('<!--');
}
this.result.push(this.NormalizeString(node.nodeValue, XmlCore.XmlNodeType.Comment));
if (this.state === XmlCanonicalizerState.BeforeDocElement) {
this.result.push('-->' + String.fromCharCode(10));
}
else {
this.result.push('-->');
}
}
}
WriteTextNode(node) {
this.result.push(this.NormalizeString(node.nodeValue, node.nodeType));
}
WriteProcessingInstructionNode(node) {
const nodeName = node.nodeName || node.tagName;
if (nodeName === 'xml') {
return;
}
if (this.state === XmlCanonicalizerState.AfterDocElement) {
this.result.push('\u000A<?');
}
else {
this.result.push('<?');
}
this.result.push(nodeName);
if (node.nodeValue) {
this.result.push(' ');
this.result.push(this.NormalizeString(node.nodeValue, XmlCore.XmlNodeType.ProcessingInstruction));
}
if (this.state === XmlCanonicalizerState.BeforeDocElement) {
this.result.push('?>\u000A');
}
else {
this.result.push('?>');
}
}
WriteElementNode(node) {
const state = this.state;
if (this.state === XmlCanonicalizerState.BeforeDocElement) {
this.state = XmlCanonicalizerState.InsideDocElement;
}
this.result.push('<');
this.result.push(node.nodeName);
let visibleNamespacesCount = this.WriteNamespacesAxis(node);
this.WriteAttributesAxis(node);
this.result.push('>');
for (let n = node.firstChild; n != null; n = n.nextSibling) {
this.WriteNode(n);
}
this.result.push('</');
this.result.push(node.nodeName);
this.result.push('>');
if (state === XmlCanonicalizerState.BeforeDocElement) {
this.state = XmlCanonicalizerState.AfterDocElement;
}
while (visibleNamespacesCount--) {
this.visibleNamespaces.Pop();
}
}
WriteNamespacesAxis(node) {
const list = [];
let visibleNamespacesCount = 0;
for (let i = 0; i < node.attributes.length; i++) {
const attribute = node.attributes[i];
if (!IsNamespaceNode(attribute)) {
if (attribute.prefix &&
!this.IsNamespaceRendered(attribute.prefix, attribute.namespaceURI)) {
const ns = { prefix: attribute.prefix, namespace: attribute.namespaceURI };
list.push(ns);
this.visibleNamespaces.Add(ns);
visibleNamespacesCount++;
}
continue;
}
if (attribute.localName === 'xmlns' && !attribute.prefix && !attribute.nodeValue) {
const ns = { prefix: attribute.prefix, namespace: attribute.nodeValue };
list.push(ns);
this.visibleNamespaces.Add(ns);
visibleNamespacesCount++;
}
let prefix = null;
let matches;
if ((matches = /xmlns:([\w.-]+)/.exec(attribute.nodeName))) {
prefix = matches[1];
}
let printable = true;
if (this.exclusive && !this.IsNamespaceInclusive(node, prefix)) {
const used = IsNamespaceUsed(node, prefix);
if (used > 1) {
printable = false;
}
else if (used === 0) {
continue;
}
}
if (this.IsNamespaceRendered(prefix, attribute.nodeValue)) {
continue;
}
if (printable) {
const ns = { prefix, namespace: attribute.nodeValue };
list.push(ns);
this.visibleNamespaces.Add(ns);
visibleNamespacesCount++;
}
}
if (!this.IsNamespaceRendered(node.prefix, node.namespaceURI) &&
node.namespaceURI !== 'http://www.w3.org/2000/xmlns/') {
const ns = { prefix: node.prefix, namespace: node.namespaceURI };
list.push(ns);
this.visibleNamespaces.Add(ns);
visibleNamespacesCount++;
}
list.sort(XmlDsigC14NTransformNamespacesComparer);
let prevPrefix = null;
list.forEach((n) => {
if (n.prefix === prevPrefix) {
return;
}
prevPrefix = n.prefix;
this.result.push(' xmlns');
if (n.prefix) {
this.result.push(':' + n.prefix);
}
this.result.push('="');
this.result.push(n.namespace || '');
this.result.push('"');
});
return visibleNamespacesCount;
}
WriteAttributesAxis(node) {
const list = [];
for (let i = 0; i < node.attributes.length; i++) {
const attribute = node.attributes[i];
if (!IsNamespaceNode(attribute)) {
list.push(attribute);
}
}
list.sort(XmlDsigC14NTransformAttributesComparer);
list.forEach((attribute) => {
if (attribute != null) {
this.result.push(' ');
this.result.push(attribute.nodeName);
this.result.push('="');
this.result.push(this.NormalizeString(attribute.nodeValue, XmlCore.XmlNodeType.Attribute));
this.result.push('"');
}
});
}
NormalizeString(input, type) {
const sb = [];
if (input) {
for (let i = 0; i < input.length; i++) {
const ch = input[i];
if (ch === '<' && (type === XmlCore.XmlNodeType.Attribute || this.IsTextNode(type))) {
sb.push('<');
}
else if (ch === '>' && this.IsTextNode(type)) {
sb.push('>');
}
else if (ch === '&' &&
(type === XmlCore.XmlNodeType.Attribute || this.IsTextNode(type))) {
sb.push('&');
}
else if (ch === '"' && type === XmlCore.XmlNodeType.Attribute) {
sb.push('"');
}
else if (ch === '\u0009' && type === XmlCore.XmlNodeType.Attribute) {
sb.push('	');
}
else if (ch === '\u000A' && type === XmlCore.XmlNodeType.Attribute) {
sb.push('
');
}
else if (ch === '\u000D') {
sb.push('
');
}
else {
sb.push(ch);
}
}
}
return sb.join('');
}
IsTextNode(type) {
switch (type) {
case XmlCore.XmlNodeType.Text:
case XmlCore.XmlNodeType.CDATA:
case XmlCore.XmlNodeType.SignificantWhitespace:
case XmlCore.XmlNodeType.Whitespace:
return true;
}
return false;
}
IsNamespaceInclusive(node, prefix) {
const prefix2 = prefix || null;
if (node.prefix === prefix2) {
return false;
}
return this.inclusiveNamespacesPrefixList.indexOf(prefix2 || '') !== -1;
}
IsNamespaceRendered(prefix, uri) {
prefix = prefix || '';
uri = uri || '';
if (!prefix && !uri) {
return true;
}
if (prefix === 'xml' && uri === 'http://www.w3.org/XML/1998/namespace') {
return true;
}
const ns = this.visibleNamespaces.GetPrefix(prefix);
if (ns) {
return ns.namespace === uri;
}
return false;
}
}
exports.XmlCanonicalizer = XmlCanonicalizer;
function XmlDsigC14NTransformNamespacesComparer(x, y) {
if (x == y) {
return 0;
}
else if (!x) {
return -1;
}
else if (!y) {
return 1;
}
else if (!x.prefix) {
return -1;
}
else if (!y.prefix) {
return 1;
}
else if (x.prefix < y.prefix) {
return -1;
}
else if (x.prefix > y.prefix) {
return 1;
}
else {
return 0;
}
}
function XmlDsigC14NTransformAttributesComparer(x, y) {
if (!x.namespaceURI && y.namespaceURI) {
return -1;
}
if (!y.namespaceURI && x.namespaceURI) {
return 1;
}
const left = (x.namespaceURI || '') + x.localName;
const right = (y.namespaceURI || '') + y.localName;
if (left === right) {
return 0;
}
else if (left < right) {
return -1;
}
else {
return 1;
}
}
function IsNamespaceUsed(node, prefix, result = 0) {
const prefix2 = prefix || null;
if (node.prefix === prefix2) {
return ++result;
}
if (node.attributes) {
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
if (!IsNamespaceNode(attr) && prefix && node.attributes[i].prefix === prefix) {
return ++result;
}
}
}
for (let n = node.firstChild; n; n = n.nextSibling) {
if (n.nodeType === XmlCore.XmlNodeType.Element) {
const el = n;
const res = IsNamespaceUsed(el, prefix, result);
if (n.nodeType === XmlCore.XmlNodeType.Element && res) {
return ++result + res;
}
}
}
return result;
}
function IsNamespaceNode(node) {
const reg = /xmlns:/;
if (node !== null &&
node.nodeType === XmlCore.XmlNodeType.Attribute &&
(node.nodeName === 'xmlns' || reg.test(node.nodeName))) {
return true;
}
return false;
}