From cdd468b6235a00ce2a90a8d3454ef9b187019e7e Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Thu, 28 Aug 2025 10:17:42 -0500 Subject: [PATCH 1/3] samlcontent should be decrypted SAML response (sign then encrypt) --- src/flow.ts | 2 +- test/flow.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/flow.ts b/src/flow.ts index d154996a..4b85570c 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -16,7 +16,6 @@ import { BindingNamespace, ParserType, wording, - MessageSignatureOrder, StatusCode } from './urn'; @@ -225,6 +224,7 @@ async function postFlow(options): Promise { // Encrypted Assertion, the assertion is signed const result = await libsaml.decryptAssertion(self, samlContent); const decryptedDoc = result[0]; + samlContent = decryptedDoc; const [decryptedDocVerified, verifiedDecryptedAssertion] = libsaml.verifySignature(decryptedDoc, verificationOptions); if (decryptedDocVerified) { // extractor depends on signed content diff --git a/test/flow.ts b/test/flow.ts index 75481710..d2eca4dd 100644 --- a/test/flow.ts +++ b/test/flow.ts @@ -404,6 +404,7 @@ test('send response with signed assertion and parse it', async t => { t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); + t.is(samlContent.includes('>user@esaml2.com'), true); t.is(extract.nameID, 'user@esaml2.com'); t.is(extract.response.inResponseTo, 'request_id'); }); @@ -929,6 +930,7 @@ test('send login response with encrypted non-signed assertion and parse it', asy t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); + t.is(samlContent.includes('>user@esaml2.com'), true); t.is(extract.nameID, 'user@esaml2.com'); t.is(extract.response.inResponseTo, 'request_id'); }); @@ -1101,6 +1103,7 @@ test('send login response with encrypted non-signed assertion with EncryptThenSi t.is(typeof id, 'string'); t.is(samlContent.startsWith(''), true); + t.is(samlContent.includes('>user@esaml2.com'), true); t.is(extract.nameID, 'user@esaml2.com'); }); From 2c34a57a7b6cba9ecb08820e5925e24a72a557ff Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Tue, 16 Dec 2025 16:29:17 -0700 Subject: [PATCH 2/3] Call checkSignature is passed in arg Passing a value that was parsed and re-stringified can mess up whitespace encodings. --- src/libsaml.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libsaml.ts b/src/libsaml.ts index bdb48356..932dfd01 100644 --- a/src/libsaml.ts +++ b/src/libsaml.ts @@ -370,7 +370,6 @@ const libSaml = () => { const { dom } = getContext(); const doc = dom.parseFromString(xml); - const docParser = new DOMParser(); // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element // message signature (logout response / saml response) const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']"; @@ -455,8 +454,7 @@ const libSaml = () => { } sig.loadSignature(signatureNode); - - verified = sig.checkSignature(doc.toString()); + verified = sig.checkSignature(xml); // immediately throw error when any one of the signature is failed to get verified if (!verified) { @@ -468,7 +466,7 @@ const libSaml = () => { throw new Error('NO_SIGNATURE_REFERENCES') } const signedVerifiedXML = sig.getSignedReferences()[0]; - const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement; + const rootNode = dom.parseFromString(signedVerifiedXML, 'text/xml').documentElement; // process the verified signature: // case 1, rootSignedDoc is a response: if (rootNode.localName === 'Response') { From d880728f71399f23c545719611283aa6bc9ae5e4 Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Wed, 10 Jun 2026 10:37:16 -0600 Subject: [PATCH 3/3] align variable pattern --- src/flow.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/flow.ts b/src/flow.ts index a3d1dfb9..ac962237 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -241,9 +241,8 @@ async function postFlow(options: FlowOptions): Promise { } else if (decryptRequired && !verified) { // Encrypted assertion, signature is on the assertion itself. const result = await libsaml.decryptAssertion(self, samlContent); - const decryptedDoc = result[0]; - samlContent = decryptedDoc; - const [decryptedDocVerified, verifiedDecryptedAssertion] = libsaml.verifySignature(decryptedDoc, verificationOptions); + samlContent = result[0]; + const [decryptedDocVerified, verifiedDecryptedAssertion] = libsaml.verifySignature(samlContent, verificationOptions); if (decryptedDocVerified) { extractorFields = getDefaultExtractorFields(parserType, verifiedDecryptedAssertion); } else {