diff --git a/src/signed-xml.ts b/src/signed-xml.ts
index e5d80af7..4f6d11ec 100644
--- a/src/signed-xml.ts
+++ b/src/signed-xml.ts
@@ -252,26 +252,72 @@ export class SignedXml {
this.signedXml = xml;
const doc = new xmldom.DOMParser().parseFromString(xml);
+ // Reset the references as only references from our re-parsed signedInfo node can be trusted
+ this.references = [];
+
+ const unverifiedSignedInfoCanon = this.getCanonSignedInfoXml(doc);
+ if (!unverifiedSignedInfoCanon) {
+ if (callback) {
+ callback(new Error("Canonical signed info cannot be empty"), false);
+ return;
+ }
+
+ throw new Error("Canonical signed info cannot be empty");
+ }
+
+ // unsigned, verify later to keep with consistent callback behavior
+ const parsedUnverifiedSignedInfo = new xmldom.DOMParser().parseFromString(
+ unverifiedSignedInfoCanon,
+ "text/xml",
+ );
+
+ const unverifiedSignedInfoDoc = parsedUnverifiedSignedInfo.documentElement;
+ if (!unverifiedSignedInfoDoc) {
+ if (callback) {
+ callback(new Error("Could not parse unverifiedSignedInfoCanon into a document"), false);
+ return;
+ }
+
+ throw new Error("Could not parse unverifiedSignedInfoCanon into a document");
+ }
+
+ const references = utils.findChildren(unverifiedSignedInfoDoc, "Reference");
+ if (!utils.isArrayHasLength(references)) {
+ if (callback) {
+ callback(new Error("could not find any Reference elements"), false);
+ return;
+ }
+
+ throw new Error("could not find any Reference elements");
+ }
+
+ // TODO: In a future release we'd like to load the Signature and its References at the same time,
+ // however, in the `.loadSignature()` method we don't have the entire document,
+ // which we need to to keep the inclusive namespaces
+ for (const reference of references) {
+ this.loadReference(reference);
+ }
if (!this.getReferences().every((ref) => this.validateReference(ref, doc))) {
if (callback) {
- callback(new Error("Could not validate all references"));
+ callback(new Error("Could not validate all references"), false);
return;
}
return false;
}
- const signedInfoCanon = this.getCanonSignedInfoXml(doc);
+ // Stage B: Take the signature algorithm and key and verify the SignatureValue against the canonicalized SignedInfo
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
const key = this.getCertFromKeyInfo(this.keyInfo) || this.publicCert || this.privateKey;
if (key == null) {
throw new Error("KeyInfo or publicCert or privateKey is required to validate signature");
}
+
if (callback) {
- signer.verifySignature(signedInfoCanon, key, this.signatureValue, callback);
+ signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue, callback);
} else {
- const verified = signer.verifySignature(signedInfoCanon, key, this.signatureValue);
+ const verified = signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue);
if (verified === false) {
throw new Error(
@@ -295,6 +341,11 @@ export class SignedXml {
if (signedInfo.length === 0) {
throw new Error("could not find SignedInfo element in the message");
}
+ if (signedInfo.length > 1) {
+ throw new Error(
+ "could not get canonicalized signed info for a signature that contains multiple SignedInfo nodes",
+ );
+ }
if (
this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
@@ -522,11 +573,43 @@ export class SignedXml {
this.signatureAlgorithm = signatureAlgorithm.value as SignatureAlgorithmType;
}
- this.references = [];
- const references = xpath.select(
- ".//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']",
- signatureNode,
+ const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
+ if (!utils.isArrayHasLength(signedInfoNodes)) {
+ throw new Error("no signed info node found");
+ }
+ if (signedInfoNodes.length > 1) {
+ throw new Error("could not load signature that contains multiple SignedInfo nodes");
+ }
+
+ // Try to operate on the c14n version of `signedInfo`. This forces the initial `getReferences()`
+ // API call to always return references that are loaded under the canonical `SignedInfo`
+ // in the case that the client access the `.references` **before** signature verification.
+
+ // Ensure canonicalization algorithm is exclusive, otherwise we'd need the entire document
+ let canonicalizationAlgorithmForSignedInfo = this.canonicalizationAlgorithm;
+ if (
+ !canonicalizationAlgorithmForSignedInfo ||
+ canonicalizationAlgorithmForSignedInfo ===
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
+ canonicalizationAlgorithmForSignedInfo ===
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
+ ) {
+ canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#";
+ }
+
+ const temporaryCanonSignedInfo = this.getCanonXml(
+ [canonicalizationAlgorithmForSignedInfo],
+ signedInfoNodes[0],
+ );
+ const temporaryCanonSignedInfoXml = new xmldom.DOMParser().parseFromString(
+ temporaryCanonSignedInfo,
+ "text/xml",
);
+ const signedInfoDoc = temporaryCanonSignedInfoXml.documentElement;
+
+ this.references = [];
+ const references = utils.findChildren(signedInfoDoc, "Reference");
+
if (!utils.isArrayHasLength(references)) {
throw new Error("could not find any Reference elements");
}
@@ -572,11 +655,15 @@ export class SignedXml {
if (nodes.length === 0) {
throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`);
}
- const firstChild = nodes[0].firstChild;
- if (!firstChild || !("data" in firstChild)) {
- throw new Error(`could not find the value of DigestValue in ${nodes[0].toString()}`);
+ if (nodes.length > 1) {
+ throw new Error(
+ `could not load reference for a node that contains multiple DigestValue nodes: ${refNode.toString()}`,
+ );
+ }
+ const digestValue = nodes[0].textContent;
+ if (!digestValue) {
+ throw new Error(`could not find the value of DigestValue in ${refNode.toString()}`);
}
- const digestValue = firstChild.data;
const transforms: string[] = [];
let inclusiveNamespacesPrefixList: string[] = [];
@@ -626,11 +713,14 @@ export class SignedXml {
) {
transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
}
+ const refUri = isDomNode.isElementNode(refNode)
+ ? refNode.getAttribute("URI") || undefined
+ : undefined;
this.addReference({
transforms,
digestAlgorithm: digestAlgo,
- uri: isDomNode.isElementNode(refNode) ? utils.findAttr(refNode, "URI")?.value : undefined,
+ uri: refUri,
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri: false,
diff --git a/test/saml-response-tests.spec.ts b/test/saml-response-tests.spec.ts
index b6ee8263..7ae94bc8 100644
--- a/test/saml-response-tests.spec.ts
+++ b/test/saml-response-tests.spec.ts
@@ -92,4 +92,78 @@ describe("SAML response tests", function () {
// This doesn't matter, just want to make sure that we don't fail due to unknown algorithm
expect(() => sig.checkSignature(xml)).to.throw(/^invalid signature/);
});
+
+ it("throws an error for a document with no `SignedInfo` node", function () {
+ const xml = fs.readFileSync("./test/static/invalid_saml_no_signed_info.xml", "utf-8");
+ const doc = new xmldom.DOMParser().parseFromString(xml);
+ const node = xpath.select1(
+ "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+ doc,
+ );
+
+ isDomNode.assertIsNodeLike(node);
+ const sig = new SignedXml();
+ const feidePublicCert = fs.readFileSync("./test/static/feide_public.pem");
+ sig.publicCert = feidePublicCert;
+
+ expect(() => sig.loadSignature(node)).to.throw("no signed info node found");
+ });
+
+ it("test validation ignores an additional wrapped `SignedInfo` node", function () {
+ const xml = fs.readFileSync("./test/static/saml_wrapped_signed_info_node.xml", "utf-8");
+ const doc = new xmldom.DOMParser().parseFromString(xml);
+ const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc);
+ isDomNode.assertIsNodeLike(assertion);
+ const signature = xpath.select1(
+ "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+ assertion,
+ );
+ isDomNode.assertIsNodeLike(signature);
+
+ const sig = new SignedXml();
+ sig.publicCert = fs.readFileSync("./test/static/saml_external_ns.pem");
+ sig.loadSignature(signature);
+ expect(sig.getReferences().length).to.equal(1);
+ const checkSignatureResult = sig.checkSignature(xml);
+ expect(checkSignatureResult).to.be.true;
+ });
+
+ it("test signature throws if multiple `SignedInfo` nodes are found", function () {
+ const xml = fs.readFileSync("./test/static/saml_multiple_signed_info_nodes.xml", "utf-8");
+ const doc = new xmldom.DOMParser().parseFromString(xml);
+ const assertion = xpath.select1("//*[local-name(.)='Assertion'][1]", doc);
+ isDomNode.assertIsNodeLike(assertion);
+ const signature = xpath.select1(
+ "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+ assertion,
+ );
+ isDomNode.assertIsNodeLike(signature);
+
+ const sig = new SignedXml();
+ sig.publicCert = fs.readFileSync("./test/static/saml_external_ns.pem");
+ expect(() => sig.loadSignature(signature)).to.throw(
+ "could not load signature that contains multiple SignedInfo nodes",
+ );
+ });
+
+ describe("for a SAML response with a digest value comment", () => {
+ it("loads digest value from text content instead of comment", function () {
+ const xml = fs.readFileSync("./test/static/valid_saml_with_digest_comment.xml", "utf-8");
+ const doc = new xmldom.DOMParser().parseFromString(xml);
+ const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc);
+ isDomNode.assertIsNodeLike(assertion);
+ const signature = xpath.select1(
+ "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+ assertion,
+ );
+ isDomNode.assertIsNodeLike(signature);
+ const sig = new SignedXml();
+ sig.publicCert = fs.readFileSync("./test/static/feide_public.pem");
+
+ sig.loadSignature(signature);
+
+ expect(sig.getReferences()[0].digestValue).to.equal("RnNjoyUguwze5w2R+cboyTHlkQk=");
+ expect(sig.checkSignature(xml)).to.be.false;
+ });
+ });
});
diff --git a/test/static/invalid_saml_no_signed_info.xml b/test/static/invalid_saml_no_signed_info.xml
new file mode 100644
index 00000000..cfc34ca1
--- /dev/null
+++ b/test/static/invalid_saml_no_signed_info.xml
@@ -0,0 +1,9 @@
+https://openidp.feide.no
+
+
+ dkONrkxW+LSuDvnNMG/mWYFa47d2WGyapLhXSTYqrlT9Td+tT7ciojNJ55WTaPaCMt7IrGtIxxskPAZIjdIn5pRyDxHr0joWxzZ7oZHCOI1CnQV5HjOq+rzzmEN2LctCZ6S4hbL7SQ1qJ3vp2BCXAygy4tmJOURQdnk0KLwwRS8=
+MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==https://openidp.feide.no
+
+
+ RnNjoyUguwze5w2R+cboyTHlkQk=aw5711jKP7xragunjRRCAD4mT4xKHc37iohBpQDbdSomD3ksOSB96UZQp0MtaC3xlVSkMtYw85Om96T2q2xrxLLYVA50eFJEMMF7SCVPStWTVjBlaCuOPEQxIaHyJs9Sy3MCEfbBh4Pqn9IJBd1kzwdlCrWWjAmksbFFg5wHQJA=
+MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==_6c5dcaa3053321ff4d63785fbc3f67c59a129cde82passport-samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordbergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3ebergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3e
\ No newline at end of file
diff --git a/test/static/saml_multiple_signed_info_nodes.xml b/test/static/saml_multiple_signed_info_nodes.xml
new file mode 100644
index 00000000..aed22887
--- /dev/null
+++ b/test/static/saml_multiple_signed_info_nodes.xml
@@ -0,0 +1 @@
+https://app.onelogin.com/saml/metadata/164679https://app.onelogin.com/saml/metadata/164679Gx0mTydMn1k6804jZBrdUrZmbV4=jGst6BnAC9xOeqa6hKNPsoMm2TY=oHEPKtwoCbfq1QRm2pjx35zVMqAsti4nQU+3ws8EUJUXHmPG2EoX3HBkb7D2wN4m+ZFrdwARUpNJlhhOIz/eG4jES6ar0tvlNN3qE5cqcQhwZHyRARLnTlERqyZU9Qm729DnAGBeXCdMb736zi16onOIVPNA63LRTzUIxhyZqypDCf1wd6me/ur6UUgH11nYOu4JDYx0iWNkXc1Nad7vkF9oMPeO1QsMxuZSIVH4tvdJkue+qAnu2l+dFJb0LPfm+xmIC0FBo+VX1ECCWRoUZIxjotQfAM6yZpHIi5fNqPXkVyN9fYoUEa9CafqHlc4tAAdgAgGeOqA3jWeC8ZnOVA==MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA1MjcwODU1MTNaFw0xODA1MjcwODU1MTNaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXoc7IFZQRv+SwJ15zjIl9touwY5e6b7H4vn3OtOUByjOKHUX8VX0TpbAV2ctZE2GSALx1AGuQAv6O4MVUH+qn/2IAiBY3a7zKN07UBsya7xFMQVHuGE6EiBAs9jpA9wjvYMPRkS5wYZcwjpTQSZK7zFPPtobG8K/1vDbm/tWZjNLmZmQePmXpwrQAuC0+NlzlmnjoQYB2xp2NaTUK9JnnmuB5qev3dpUwlYGSJpf+HUIoxuo8IpxAXOymq1d6tEEJgU1kR2sa7o1sSRFo31YeW/qYCP/gcLJZo3MRUDFe0g5MHeliFue9DsKYUsC6qwAD3gc+MI47buiD6Msu11cwIDAQABo4HUMIHRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAAJFJRIlpQQSFsuNdeq7FkTJIH4MIGRBgNVHSMEgYkwgYaAFAAJFJRIlpQQSFsuNdeq7FkTJIH4oWukaTBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAOBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB9zN+g6N4sUBE61RaMUH2LSHWwOtfhL64i7pjHjvZa47/qcV/S0Yyd4IE44ho9i2N+AM79d34mThc30oK5aVxOKphKf+xM/cOyVaWIeqr+dCbkY/0OpLEwWOh9VSgOizRO3evLMurbtR892LbSK/Td3hG5jfwoHD23nHH87Dv/3KyZox9MkJdY2DXOHGGIcsqoIifaTyNZyhW6RgwEujQ6LjsaolP1YoeV85TZFKTLa1Ta7ZLUVUC2UJWqz+kRlsyGxf+E/ZmJ7hSq0ZBVHrVOyXjCcFn6X0/W5SrpOmN3fZYcj8Bp6vhB0cJk9qpjgWOP2RCuBdHZVawjCjIaE+bc=anyone@gmail.comForNodeJSurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportkartik.cds@gmail.comKartikCDShttps://app.onelogin.com/saml/metadata/164679kartik.cds@gmail.comForNodeJSurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportkartik.cds@gmail.comKartikCDS
\ No newline at end of file
diff --git a/test/static/saml_wrapped_signed_info_node.xml b/test/static/saml_wrapped_signed_info_node.xml
new file mode 100644
index 00000000..b7eb19d1
--- /dev/null
+++ b/test/static/saml_wrapped_signed_info_node.xml
@@ -0,0 +1 @@
+https://app.onelogin.com/saml/metadata/164679https://app.onelogin.com/saml/metadata/164679jGst6BnAC9xOeqa6hKNPsoMm2TY=Gx0mTydMn1k6804jZBrdUrZmbV4=oHEPKtwoCbfq1QRm2pjx35zVMqAsti4nQU+3ws8EUJUXHmPG2EoX3HBkb7D2wN4m+ZFrdwARUpNJlhhOIz/eG4jES6ar0tvlNN3qE5cqcQhwZHyRARLnTlERqyZU9Qm729DnAGBeXCdMb736zi16onOIVPNA63LRTzUIxhyZqypDCf1wd6me/ur6UUgH11nYOu4JDYx0iWNkXc1Nad7vkF9oMPeO1QsMxuZSIVH4tvdJkue+qAnu2l+dFJb0LPfm+xmIC0FBo+VX1ECCWRoUZIxjotQfAM6yZpHIi5fNqPXkVyN9fYoUEa9CafqHlc4tAAdgAgGeOqA3jWeC8ZnOVA==MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA1MjcwODU1MTNaFw0xODA1MjcwODU1MTNaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXoc7IFZQRv+SwJ15zjIl9touwY5e6b7H4vn3OtOUByjOKHUX8VX0TpbAV2ctZE2GSALx1AGuQAv6O4MVUH+qn/2IAiBY3a7zKN07UBsya7xFMQVHuGE6EiBAs9jpA9wjvYMPRkS5wYZcwjpTQSZK7zFPPtobG8K/1vDbm/tWZjNLmZmQePmXpwrQAuC0+NlzlmnjoQYB2xp2NaTUK9JnnmuB5qev3dpUwlYGSJpf+HUIoxuo8IpxAXOymq1d6tEEJgU1kR2sa7o1sSRFo31YeW/qYCP/gcLJZo3MRUDFe0g5MHeliFue9DsKYUsC6qwAD3gc+MI47buiD6Msu11cwIDAQABo4HUMIHRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAAJFJRIlpQQSFsuNdeq7FkTJIH4MIGRBgNVHSMEgYkwgYaAFAAJFJRIlpQQSFsuNdeq7FkTJIH4oWukaTBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAOBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB9zN+g6N4sUBE61RaMUH2LSHWwOtfhL64i7pjHjvZa47/qcV/S0Yyd4IE44ho9i2N+AM79d34mThc30oK5aVxOKphKf+xM/cOyVaWIeqr+dCbkY/0OpLEwWOh9VSgOizRO3evLMurbtR892LbSK/Td3hG5jfwoHD23nHH87Dv/3KyZox9MkJdY2DXOHGGIcsqoIifaTyNZyhW6RgwEujQ6LjsaolP1YoeV85TZFKTLa1Ta7ZLUVUC2UJWqz+kRlsyGxf+E/ZmJ7hSq0ZBVHrVOyXjCcFn6X0/W5SrpOmN3fZYcj8Bp6vhB0cJk9qpjgWOP2RCuBdHZVawjCjIaE+bc=anyone@gmail.comForNodeJSurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportkartik.cds@gmail.comKartikCDShttps://app.onelogin.com/saml/metadata/164679kartik.cds@gmail.comForNodeJSurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportkartik.cds@gmail.comKartikCDS
\ No newline at end of file
diff --git a/test/static/valid_saml_with_digest_comment.xml b/test/static/valid_saml_with_digest_comment.xml
new file mode 100644
index 00000000..d0996872
--- /dev/null
+++ b/test/static/valid_saml_with_digest_comment.xml
@@ -0,0 +1,7 @@
+https://openidp.feide.no
+https://openidp.feide.no
+
+
+ RnNjoyUguwze5w2R+cboyTHlkQk=aw5711jKP7xragunjRRCAD4mT4xKHc37iohBpQDbdSomD3ksOSB96UZQp0MtaC3xlVSkMtYw85Om96T2q2xrxLLYVA50eFJEMMF7SCVPStWTVjBlaCuOPEQxIaHyJs9Sy3MCEfbBh4Pqn9IJBd1kzwdlCrWWjAmksbFFg5wHQJA=
+MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==test@example.compassport-samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordbergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3ebergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3e
+
\ No newline at end of file