From 7d3b0e0f7e398c80aa82998abe2ab15197b7f62b Mon Sep 17 00:00:00 2001
From: mhassan1 <marc.j.hassan@gmail.com>
Date: Thu, 25 Mar 2021 13:56:33 -0400
Subject: [PATCH] resolve XML-encoded carriage returns during signature
 validation

---
 src/passport-saml/saml.ts |  8 ++++++++
 test/test-signatures.js   | 23 +++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/src/passport-saml/saml.ts b/src/passport-saml/saml.ts
index 0cb93486..1cbbc521 100644
--- a/src/passport-saml/saml.ts
+++ b/src/passport-saml/saml.ts
@@ -730,6 +730,8 @@ class SAML {
     if (totalReferencedNodes.length > 1) {
       return false;
     }
+    // normalize XML to replace XML-encoded carriage returns with actual carriage returns
+    fullXml = this.normalizeXml(fullXml);
     fullXml = this.normalizeNewlines(fullXml);
     return sig.checkSignature(fullXml);
   }
@@ -1418,6 +1420,12 @@ class SAML {
     // https://github.com/node-saml/passport-saml/issues/431#issuecomment-718132752
     return xml.replace(/\r\n?/g, "\n");
   }
+
+  normalizeXml(xml: string): string {
+    // we can use this utility to parse and re-stringify XML
+    // `DOMParser` will take care of normalization tasks, like replacing XML-encoded carriage returns with actual carriage returns
+    return new xmldom.DOMParser({}).parseFromString(xml).toString();
+  }
 }
 
 export { SAML };
diff --git a/test/test-signatures.js b/test/test-signatures.js
index 2972a542..98a795c9 100644
--- a/test/test-signatures.js
+++ b/test/test-signatures.js
@@ -100,4 +100,27 @@ describe('Signatures', function() {
 
   });
 
+  describe("Signature on saml:Response with XML-encoded carriage returns", () => {
+    const samlResponseXml = fs
+      .readFileSync(
+        __dirname + "/static/signatures/valid/response.root-unsigned.assertion-signed.xml"
+      )
+      .toString();
+    const makeBody = (str) => ({ SAMLResponse: Buffer.from(str).toString("base64") });
+
+    const insertChars = (str, where, chars) =>
+      str.replace(new RegExp(`(<ds:${where}>)(.{10})(.{10})`), `$1$2${chars}$3`);
+
+    it("SignatureValue with &#13;", async () => {
+      const body = makeBody(insertChars(samlResponseXml, "SignatureValue", "&#13;"));
+      await testOneResponseBody(body, false, 2);
+    });
+
+    it("SignatureValue with &#xd;", async () => {
+      const body = makeBody(insertChars(samlResponseXml, "SignatureValue", "&#xd;"));
+      await testOneResponseBody(body, false, 2);
+    });
+
+  });
+
 });