Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions test/c14n-non-exclusive-unit-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,70 @@ describe("C14N non-exclusive canonicalization tests", function () {
});

for (const Canonicalization of [C14nCanonicalization, C14nCanonicalizationWithComments]) {
describe(`${Canonicalization.name}: hoisted ancestor namespaces`, function () {
// A hoisted ancestor default namespace becomes the default the subset's descendants are
// canonicalized against. The rationale is spelled out in §4.7: the empty default is kept on
// output "so that e3 does not take on the default namespace qualification of e1".
// https://www.w3.org/TR/xml-c14n/#ProcessingModel
// https://www.w3.org/TR/xml-c14n/#PropagateDefaultNSDecl
it('renders xmlns="" on a descendant when the apex hoists an ancestor default namespace', function () {
test_C14nCanonicalization(
'<root xmlns="urn:A"><p:x xmlns:p="urn:p"><y xmlns=""></y></p:x></root>',
"//*[local-name()='x']",
'<p:x xmlns="urn:A" xmlns:p="urn:p"><y xmlns=""></y></p:x>',
new Canonicalization(),
);
});

it("omits a descendant declaration the hoisted ancestor default namespace makes redundant", function () {
test_C14nCanonicalization(
'<root xmlns="urn:A"><p:x xmlns:p="urn:p"><y xmlns="urn:A"></y></p:x></root>',
"//*[local-name()='x']",
'<p:x xmlns="urn:A" xmlns:p="urn:p"><y></y></p:x>',
new Canonicalization(),
);
});

it("renders the hoisted ancestor default namespace once when the apex inherits it", function () {
test_C14nCanonicalization(
'<root xmlns="urn:A"><x xmlns:p="urn:p"><y xmlns=""></y></x></root>',
"//*[local-name()='x']",
'<x xmlns="urn:A" xmlns:p="urn:p"><y xmlns=""></y></x>',
new Canonicalization(),
);
});

it("prefers the apex's own default namespace over the hoisted ancestor one", function () {
test_C14nCanonicalization(
'<root xmlns="urn:A"><x xmlns:p="urn:p" xmlns="urn:B"><y></y></x></root>',
"//*[local-name()='x']",
'<x xmlns="urn:B" xmlns:p="urn:p"><y></y></x>',
new Canonicalization(),
);
});

// A declaration on the apex shadows the ancestor one binding the same prefix, so the
// element must not resolve against the outer binding — in its descendants or its own name.
// https://www.w3.org/TR/REC-xml-names/#scoping
it("renders the apex's redeclaration of an ancestor prefix, not the ancestor binding", function () {
test_C14nCanonicalization(
'<root xmlns:q="urn:old"><x xmlns:p="urn:p" xmlns:q="urn:new"><q:y></q:y></x></root>',
"//*[local-name()='x']",
'<x xmlns:p="urn:p" xmlns:q="urn:new"><q:y></q:y></x>',
new Canonicalization(),
);
});

it("renders the apex's redeclaration of the prefix in its own name", function () {
test_C14nCanonicalization(
'<root xmlns:q="urn:old"><q:x xmlns:p="urn:p" xmlns:q="urn:new"><y></y></q:x></root>',
"//*[local-name()='x']",
'<q:x xmlns:p="urn:p" xmlns:q="urn:new"><y></y></q:x>',
new Canonicalization(),
);
});
});

describe(`${Canonicalization.name}: subset namespace declarations`, function () {
it("does not duplicate the default namespace when the subset declares a prefixed namespace", function () {
// Render the inherited default namespace exactly once on the subset root.
Expand Down
38 changes: 38 additions & 0 deletions test/signature-integration-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,42 @@ describe("Signature integration tests", function () {
});
});
}

// A signed reference must hand back the element in the namespace it was signed in. If a
// hoisted ancestor default namespace leaks into a descendant, the signature still verifies
// while `getSignedReferences()` reports an identity the sender never signed — and a caller
// that dispatches on element namespace acts on it.
// https://www.w3.org/TR/xml-c14n/#ProcessingModel
it("does not move an element into a namespace it was not signed in", function () {
const c14n = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
const xml = '<root xmlns="urn:A"><p:x xmlns:p="urn:p" Id="_1"><y xmlns=""></y></p:x></root>';

const sig = new SignedXml();
sig.privateKey = fs.readFileSync("./test/static/client.pem");
sig.canonicalizationAlgorithm = c14n;
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
sig.addReference({
xpath: "//*[local-name(.)='x']",
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
transforms: [c14n],
});
sig.computeSignature(xml);
const signed = sig.getSignedXml();

const signature = xpath.select1(
"//*[local-name(.)='Signature']",
new xmldom.DOMParser().parseFromString(signed),
);
isDomNode.assertIsNodeLike(signature);

const verify = new SignedXml();
verify.publicCert = fs.readFileSync("./test/static/client_public.pem");
verify.loadSignature(signature);
expect(verify.checkSignature(signed)).to.be.true;

const trusted = new xmldom.DOMParser().parseFromString(verify.getSignedReferences()[0]);
const y = xpath.select1("//*[local-name(.)='y']", trusted);
isDomNode.assertIsElementNode(y);
expect(y.namespaceURI ?? "", "<y> must stay in no namespace").to.equal("");
});
});