Skip to content

Commit 8f01659

Browse files
committed
Add method for checking if element is signed
1 parent 2e9cbf1 commit 8f01659

File tree

4 files changed

+114
-47
lines changed

4 files changed

+114
-47
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,19 @@ If the verification process fails `sig.validationErrors` will contain the errors
152152
In order to protect from some attacks we must check the content we want to use is the one that has been signed:
153153

154154
```javascript
155-
var elem = select(doc, "/xpath_to_interesting_element");
155+
// Roll your own
156+
var elem = xpath.select("/xpath_to_interesting_element", doc);
156157
var uri = sig.references[0].uri; // might not be 0 - depending on the document you verify
157158
var id = uri[0] === "#" ? uri.substring(1) : uri;
158159
if (elem.getAttribute("ID") != id && elem.getAttribute("Id") != id && elem.getAttribute("id") != id)
159160
throw new Error("the interesting element was not the one verified by the signature");
161+
162+
// Use the built-in method
163+
let elem = xpath.select("/xpath_to_interesting_element", doc);
164+
const matchingReference = sig.validateElementAgainstReferences(elem, doc);
165+
if (!matchingReference) {
166+
throw new Error("the interesting element was not the one verified by the signature");
167+
}
160168
```
161169

162170
Note:

src/signed-xml.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,23 @@ export class SignedXml {
5959
/**
6060
* Specifies the data to be signed within an XML document. See {@link Reference}
6161
*/
62-
private references: Reference[] = [];
6362
private id = 0;
6463
private signedXml = "";
6564
private signatureXml = "";
6665
private signatureNode: Node | null = null;
6766
private signatureValue = "";
6867
private originalXmlWithIds = "";
68+
private keyInfo: Node | null = null;
69+
70+
/**
71+
* Contains the references that were signed. See {@link Reference}
72+
*/
73+
references: Reference[] = [];
74+
6975
/**
7076
* Contains validation errors (if any) after {@link checkSignature} method is called
7177
*/
7278
validationErrors: string[] = [];
73-
private keyInfo: Node | null = null;
7479

7580
/**
7681
* To add a new transformation algorithm create a new class that implements the {@link TransformationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
@@ -389,6 +394,37 @@ export class SignedXml {
389394
}
390395
}
391396

397+
validateElementAgainstReferences(elem: Element, doc: Document): Reference | false {
398+
for (const ref of this.references) {
399+
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
400+
let targetElem: xpath.SelectSingleReturnType;
401+
402+
for (const attr of this.idAttributes) {
403+
const elemId = elem.getAttribute(attr);
404+
if (uri === elemId) {
405+
targetElem = elem;
406+
ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
407+
break; // found the correct element, no need to check further
408+
}
409+
}
410+
411+
// @ts-expect-error This is a problem with the types on `xpath`
412+
if (!xpath.isNodeLike(targetElem)) {
413+
continue;
414+
}
415+
416+
const canonXml = this.getCanonReferenceXml(doc, ref, targetElem);
417+
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
418+
const digest = hash.getHash(canonXml);
419+
420+
if (utils.validateDigestValue(digest, ref.digestValue)) {
421+
return ref;
422+
}
423+
}
424+
425+
return false; // No references passed validation
426+
}
427+
392428
validateReferences(doc) {
393429
for (const ref of this.references) {
394430
let elemXpath;

test/signature-unit-tests.spec.ts

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -32,60 +32,85 @@ describe("Signature unit tests", function () {
3232
expect(res, "expected signature to be valid, but it was reported invalid").to.equal(true);
3333
}
3434

35-
function passLoadSignature(file, toString) {
35+
function passLoadSignature(file: string, toString?: boolean) {
3636
const xml = fs.readFileSync(file, "utf8");
3737
const doc = new xmldom.DOMParser().parseFromString(xml);
38-
const node = xpath.select1(
38+
const signature = xpath.select1(
3939
"/*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
4040
doc,
4141
);
42-
const sig = new SignedXml();
43-
// @ts-expect-error FIXME
44-
sig.loadSignature(toString ? node.toString() : node);
42+
if (xpath.isElement(signature)) {
43+
const sig = new SignedXml();
44+
sig.loadSignature(toString ? signature.toString() : signature);
4545

46-
expect(sig.canonicalizationAlgorithm, "wrong canonicalization method").to.equal(
47-
"http://www.w3.org/2001/10/xml-exc-c14n#",
48-
);
46+
expect(sig.canonicalizationAlgorithm, "wrong canonicalization method").to.equal(
47+
"http://www.w3.org/2001/10/xml-exc-c14n#",
48+
);
4949

50-
expect(sig.signatureAlgorithm, "wrong signature method").to.equal(
51-
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
52-
);
50+
expect(sig.signatureAlgorithm, "wrong signature method").to.equal(
51+
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
52+
);
5353

54-
// @ts-expect-error FIXME
55-
expect(sig.signatureValue, "wrong signature value").to.equal(
56-
"PI2xGt3XrVcxYZ34Kw7nFdq75c7Mmo7J0q7yeDhBprHuJal/KV9KyKG+Zy3bmQIxNwkPh0KMP5r1YMTKlyifwbWK0JitRCSa0Fa6z6+TgJi193yiR5S1MQ+esoQT0RzyIOBl9/GuJmXx/1rXnqrTxmL7UxtqKuM29/eHwF0QDUI=",
57-
);
54+
sig.getCertFromKeyInfo = (keyInfo) => {
55+
// @ts-expect-error FIXME
56+
if (xpath.isNodeLike(keyInfo)) {
57+
const keyInfoContents = xpath.select1(
58+
"//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']",
59+
keyInfo,
60+
);
61+
if (xpath.isNodeLike(keyInfoContents)) {
62+
const firstChild = keyInfoContents.firstChild;
63+
if (xpath.isTextNode(firstChild)) {
64+
expect(firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234");
65+
} else {
66+
expect(xpath.isTextNode(firstChild), "keyInfo has improper format").to.be.true;
67+
}
68+
} else {
69+
expect(xpath.isNodeLike(keyInfoContents), "KeyInfo contents not found").to.be.true;
70+
}
71+
} else {
72+
// @ts-expect-error FIXME
73+
expect(xpath.isNodeLike(keyInfo), "KeyInfo not found").to.be.true;
74+
}
75+
76+
return fs.readFileSync("./test/static/client.pem", "latin1");
77+
};
5878

59-
const keyInfo = xpath.select1(
60-
"//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']",
61-
// @ts-expect-error FIXME
62-
sig.keyInfo,
63-
);
64-
// @ts-expect-error FIXME
65-
expect(keyInfo.firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234");
79+
const checkedSignature = sig.checkSignature(xml);
80+
expect(checkedSignature).to.be.true;
6681

67-
// @ts-expect-error FIXME
68-
expect(sig.references.length).to.equal(3);
82+
expect(sig.references.length).to.equal(3);
6983

70-
const digests = [
71-
"b5GCZ2xpP5T7tbLWBTkOl4CYupQ=",
72-
"K4dI497ZCxzweDIrbndUSmtoezY=",
73-
"sH1gxKve8wlU8LlFVa2l6w3HMJ0=",
74-
];
84+
const digests = [
85+
"b5GCZ2xpP5T7tbLWBTkOl4CYupQ=",
86+
"K4dI497ZCxzweDIrbndUSmtoezY=",
87+
"sH1gxKve8wlU8LlFVa2l6w3HMJ0=",
88+
];
7589

76-
// @ts-expect-error FIXME
77-
for (let i = 0; i < sig.references.length; i++) {
90+
const firstGrandchild = doc.firstChild?.firstChild;
7891
// @ts-expect-error FIXME
79-
const ref = sig.references[i];
80-
const expectedUri = `#_${i}`;
81-
expect(
82-
ref.uri,
83-
`wrong uri for index ${i}. expected: ${expectedUri} actual: ${ref.uri}`,
84-
).to.equal(expectedUri);
85-
expect(ref.transforms.length).to.equal(1);
86-
expect(ref.transforms[0]).to.equal("http://www.w3.org/2001/10/xml-exc-c14n#");
87-
expect(ref.digestValue).to.equal(digests[i]);
88-
expect(ref.digestAlgorithm).to.equal("http://www.w3.org/2000/09/xmldsig#sha1");
92+
if (xpath.isElement(firstGrandchild)) {
93+
const matchedReference = sig.validateElementAgainstReferences(firstGrandchild, doc);
94+
expect(matchedReference).to.not.be.false;
95+
} else {
96+
// @ts-expect-error FIXME
97+
expect(xpath.isElement(firstGrandchild)).to.be.true;
98+
}
99+
100+
for (let i = 0; i < sig.references.length; i++) {
101+
const ref = sig.references[i];
102+
const expectedUri = `#_${i}`;
103+
expect(
104+
ref.uri,
105+
`wrong uri for index ${i}. expected: ${expectedUri} actual: ${ref.uri}`,
106+
).to.equal(expectedUri);
107+
expect(ref.transforms.length).to.equal(1);
108+
expect(ref.transforms[0]).to.equal("http://www.w3.org/2001/10/xml-exc-c14n#");
109+
expect(ref.digestValue).to.equal(digests[i]);
110+
expect(ref.digestAlgorithm).to.equal("http://www.w3.org/2000/09/xmldsig#sha1");
111+
}
112+
} else {
113+
expect(xpath.isNodeLike(signature)).to.be.true;
89114
}
90115
}
91116

@@ -759,7 +784,6 @@ describe("Signature unit tests", function () {
759784
});
760785

761786
it("correctly loads signature", function () {
762-
// @ts-expect-error FIXME
763787
passLoadSignature("./test/static/valid_signature.xml");
764788
});
765789

@@ -768,7 +792,6 @@ describe("Signature unit tests", function () {
768792
});
769793

770794
it("correctly loads signature with root level sig namespace", function () {
771-
// @ts-expect-error FIXME
772795
passLoadSignature("./test/static/valid_signature_with_root_level_sig_namespace.xml");
773796
});
774797

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<root xmlns:ns1="http://www.w3.org/2000/09/xmldsig#"><x xmlns="ns" Id="_0"/><y z_attr="value" a_attr1="foo" Id="_1"/><z><ns:w ns:attr="value" xmlns:ns="myns" Id="_2"/></z><ns1:Signature><ns1:SignedInfo><ns1:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ns1:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ns1:Reference URI="#_0"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>b5GCZ2xpP5T7tbLWBTkOl4CYupQ=</ns1:DigestValue></ns1:Reference><ns1:Reference URI="#_1"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>K4dI497ZCxzweDIrbndUSmtoezY=</ns1:DigestValue></ns1:Reference><ns1:Reference URI="#_2"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>sH1gxKve8wlU8LlFVa2l6w3HMJ0=</ns1:DigestValue></ns1:Reference></ns1:SignedInfo><ns1:SignatureValue>PI2xGt3XrVcxYZ34Kw7nFdq75c7Mmo7J0q7yeDhBprHuJal/KV9KyKG+Zy3bmQIxNwkPh0KMP5r1YMTKlyifwbWK0JitRCSa0Fa6z6+TgJi193yiR5S1MQ+esoQT0RzyIOBl9/GuJmXx/1rXnqrTxmL7UxtqKuM29/eHwF0QDUI=</ns1:SignatureValue><ns1:KeyInfo><ns1:dummyKey>1234</ns1:dummyKey></ns1:KeyInfo></ns1:Signature></root>
1+
<root xmlns:ns1="http://www.w3.org/2000/09/xmldsig#"><x xmlns="ns" Id="_0"/><y z_attr="value" a_attr1="foo" Id="_1"/><z><ns:w ns:attr="value" xmlns:ns="myns" Id="_2"/></z><ns1:Signature><ns1:SignedInfo><ns1:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ns1:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ns1:Reference URI="#_0"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>b5GCZ2xpP5T7tbLWBTkOl4CYupQ=</ns1:DigestValue></ns1:Reference><ns1:Reference URI="#_1"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>K4dI497ZCxzweDIrbndUSmtoezY=</ns1:DigestValue></ns1:Reference><ns1:Reference URI="#_2"><ns1:Transforms><ns1:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ns1:Transforms><ns1:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ns1:DigestValue>sH1gxKve8wlU8LlFVa2l6w3HMJ0=</ns1:DigestValue></ns1:Reference></ns1:SignedInfo><ns1:SignatureValue>rR8+4xHiI8GQJ9Wty2TUbNI7Dd4uc89/BsAygYfeobEjmt4awzg6bQNA0nuQ+VggiPCYdKuKL8cPI7FUhk8osbVKdLPdy+rdJnibsyNpV87R7W5GZlFBEu/NqG6EYOMTHjpD4hq+H8ZeHC5YZDHPknPzJV8+A1UKN/BL2oWMQcg=</ns1:SignatureValue><ns1:KeyInfo><ns1:dummyKey>1234</ns1:dummyKey></ns1:KeyInfo></ns1:Signature></root>

0 commit comments

Comments
 (0)