Skip to content

Commit

Permalink
Add support for directly querying a node to see if it has passed vali…
Browse files Browse the repository at this point in the history
…dation (#389)
  • Loading branch information
cjbarth authored Oct 7, 2023
1 parent f0237e9 commit 2aa2d13
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 26 deletions.
71 changes: 45 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,27 @@ A pre requisite it to have [openssl](http://www.openssl.org/) installed and its

### Canonicalization and Transformation Algorithms

- Canonicalization http://www.w3.org/TR/2001/REC-xml-c14n-20010315
- Canonicalization with comments http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments
- Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n#
- Exclusive Canonicalization with comments http://www.w3.org/2001/10/xml-exc-c14n#WithComments
- Enveloped Signature transform http://www.w3.org/2000/09/xmldsig#enveloped-signature
- Canonicalization <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>
- Canonicalization with comments <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments>
- Exclusive Canonicalization <http://www.w3.org/2001/10/xml-exc-c14n#>
- Exclusive Canonicalization with comments <http://www.w3.org/2001/10/xml-exc-c14n#WithComments>
- Enveloped Signature transform <http://www.w3.org/2000/09/xmldsig#enveloped-signature>

### Hashing Algorithms

- SHA1 digests http://www.w3.org/2000/09/xmldsig#sha1
- SHA256 digests http://www.w3.org/2001/04/xmlenc#sha256
- SHA512 digests http://www.w3.org/2001/04/xmlenc#sha512
- SHA1 digests <http://www.w3.org/2000/09/xmldsig#sha1>
- SHA256 digests <http://www.w3.org/2001/04/xmlenc#sha256>
- SHA512 digests <http://www.w3.org/2001/04/xmlenc#sha512>

### Signature Algorithms

- RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1
- RSA-SHA256 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- RSA-SHA512 http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
- RSA-SHA1 <http://www.w3.org/2000/09/xmldsig#rsa-sha1>
- RSA-SHA256 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha256>
- RSA-SHA512 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha512>

HMAC-SHA1 is also available but it is disabled by default

- HMAC-SHA1 http://www.w3.org/2000/09/xmldsig#hmac-sha1
- HMAC-SHA1 <http://www.w3.org/2000/09/xmldsig#hmac-sha1>

to enable HMAC-SHA1, call `enableHMAC()` on your instance of `SignedXml`.

Expand All @@ -51,11 +51,11 @@ signature algorithms enabled at same time.

by default the following algorithms are used:

_Canonicalization/Transformation Algorithm:_ Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n#
_Canonicalization/Transformation Algorithm:_ Exclusive Canonicalization <http://www.w3.org/2001/10/xml-exc-c14n#>

_Hashing Algorithm:_ SHA1 digest http://www.w3.org/2000/09/xmldsig#sha1
_Hashing Algorithm:_ SHA1 digest <http://www.w3.org/2000/09/xmldsig#sha1>

_Signature Algorithm:_ RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1
_Signature Algorithm:_ RSA-SHA1 <http://www.w3.org/2000/09/xmldsig#rsa-sha1>

[You are able to extend xml-crypto with custom algorithms.](#customizing-algorithms)

Expand Down Expand Up @@ -154,18 +154,37 @@ In order to protect from some attacks we must check the content we want to use i

```javascript
// Roll your own
var elem = xpath.select("/xpath_to_interesting_element", doc);
var uri = sig.getReferences()[0].uri; // might not be 0 - depending on the document you verify
var id = uri[0] === "#" ? uri.substring(1) : uri;
if (elem.getAttribute("ID") != id && elem.getAttribute("Id") != id && elem.getAttribute("id") != id)
throw new Error("the interesting element was not the one verified by the signature");
const elem = xpath.select("/xpath_to_interesting_element", doc);
const uri = sig.getReferences()[0].uri; // might not be 0; it depends on the document
const id = uri[0] === "#" ? uri.substring(1) : uri;
if (
elem.getAttribute("ID") != id &&
elem.getAttribute("Id") != id &&
elem.getAttribute("id") != id
) {
throw new Error("The interesting element was not the one verified by the signature");
}

// Get the validated element directly from a reference
const elem = sig.references[0].getValidatedElement(); // might not be 0; it depends on the document
const matchingReference = xpath.select1("/xpath_to_interesting_element", elem);
if (!isDomNode.isNodeLike(matchingReference)) {
throw new Error("The interesting element was not the one verified by the signature");
}

// Use the built-in method
let elem = xpath.select("/xpath_to_interesting_element", doc);
const elem = xpath.select1("/xpath_to_interesting_element", doc);
try {
const matchingReference = sig.validateElementAgainstReferences(elem, doc);
} catch {
throw new Error("the interesting element was not the one verified by the signature");
throw new Error("The interesting element was not the one verified by the signature");
}

// Use the built-in method with a an xpath expression
try {
const matchingReference = sig.validateReferenceWithXPath("/xpath_to_interesting_element", doc);
} catch {
throw new Error("The interesting element was not the one verified by the signature");
}
```

Expand Down Expand Up @@ -195,10 +214,10 @@ var res = sig.checkSignature(xml);

You might find it difficult to guess such transforms, but there are typical transforms you can try.

- http://www.w3.org/TR/2001/REC-xml-c14n-20010315
- http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments
- http://www.w3.org/2001/10/xml-exc-c14n#
- http://www.w3.org/2001/10/xml-exc-c14n#WithComments
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments>
- <http://www.w3.org/2001/10/xml-exc-c14n#>
- <http://www.w3.org/2001/10/xml-exc-c14n#WithComments>

## API

Expand Down
14 changes: 14 additions & 0 deletions src/signed-xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,15 @@ export class SignedXml {
}
}

ref.getValidatedNode = (xpathSelector?: string) => {
xpathSelector = xpathSelector || ref.xpath;
if (typeof xpathSelector !== "string" || ref.validationError != null) {
return null;
}
const selectedValue = xpath.select1(xpathSelector, doc);
return isDomNode.isNodeLike(selectedValue) ? selectedValue : null;
};

if (!isDomNode.isNodeLike(elem)) {
const validationError = new Error(
`invalid signature: the signature references an element with uri ${ref.uri} but could not find such element in the xml`,
Expand Down Expand Up @@ -641,6 +650,11 @@ export class SignedXml {
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri,
getValidatedNode: () => {
throw new Error(
"Reference has not been validated yet; Did you call `sig.checkSignature()`?",
);
},
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export interface Reference {
ancestorNamespaces?: NamespacePrefix[];

validationError?: Error;

getValidatedNode(xpathSelector?: string): Node | null;
}

/** Implement this to create a new CanonicalizationOrTransformationAlgorithm */
Expand Down
71 changes: 71 additions & 0 deletions test/document-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,74 @@ describe("Document tests", function () {
expect(result).to.be.true;
});
});

describe("Validated node references tests", function () {
it("should return references if the document is validly signed", function () {
const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
const sig = new SignedXml();
sig.loadSignature(sig.findSignatures(doc)[0]);
const validSignature = sig.checkSignature(xml);
expect(validSignature).to.be.true;

const ref = sig.getReferences()[0];
const result = ref.getValidatedNode();
expect(result?.toString()).to.equal(doc.toString());
});

it("should not return references if the document is not validly signed", function () {
const xml = fs.readFileSync("./test/static/invalid_signature - changed content.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
const sig = new SignedXml();
sig.loadSignature(sig.findSignatures(doc)[0]);
const validSignature = sig.checkSignature(xml);
expect(validSignature).to.be.false;

const ref = sig.getReferences()[1];
const result = ref.getValidatedNode();
expect(result).to.be.null;
});

it("should return `null` if the selected node isn't found", function () {
const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
const sig = new SignedXml();
sig.loadSignature(sig.findSignatures(doc)[0]);
const validSignature = sig.checkSignature(xml);
expect(validSignature).to.be.true;

const ref = sig.getReferences()[0];
const result = ref.getValidatedNode("/non-existent-node");
expect(result).to.be.null;
});

it("should return the selected node if it is validly signed", function () {
const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
const sig = new SignedXml();
sig.loadSignature(sig.findSignatures(doc)[0]);
const validSignature = sig.checkSignature(xml);
expect(validSignature).to.be.true;

const ref = sig.getReferences()[0];
const result = ref.getValidatedNode(
"//*[local-name()='Attribute' and @Name='mail']/*[local-name()='AttributeValue']/text()",
);
expect(result?.nodeValue).to.equal("henri.bergius@nemein.com");
});

it("should return `null` if the selected node isn't validly signed", function () {
const xml = fs.readFileSync("./test/static/invalid_signature - changed content.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
const sig = new SignedXml();
sig.loadSignature(sig.findSignatures(doc)[0]);
const validSignature = sig.checkSignature(xml);
expect(validSignature).to.be.false;

const ref = sig.getReferences()[0];
const result = ref.getValidatedNode(
"//*[local-name()='Attribute' and @Name='mail']/*[local-name()='AttributeValue']/text()",
);
expect(result).to.be.null;
});
});

0 comments on commit 2aa2d13

Please sign in to comment.