From f5e9ec2113c86c97e3a20a40092364c4b302ba1e Mon Sep 17 00:00:00 2001 From: Eero Siren Date: Tue, 10 Dec 2019 08:05:40 +0200 Subject: [PATCH] add support for encrypted nameIDs in SLO request handling --- lib/passport-saml/saml.js | 87 ++++++++++++++----- .../logout_request_with_encrypted_name_id.xml | 25 ++++++ test/tests.js | 24 +++++ 3 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 test/static/logout_request_with_encrypted_name_id.xml diff --git a/lib/passport-saml/saml.js b/lib/passport-saml/saml.js index 4adcbc7e4..27537cc5c 100644 --- a/lib/passport-saml/saml.js +++ b/lib/passport-saml/saml.js @@ -764,20 +764,20 @@ SAML.prototype.validateRedirect = function(container, originalQuery, callback) { this.verifyLogoutResponse(doc) : this.verifyLogoutRequest(doc); }) .then(() => this.hasValidSignatureForRedirect(container, originalQuery)) - .then(() => processValidlySignedSamlLogout(this, doc, callback)) + .then(() => processValidlySignedSamlLogout(this, doc, dom, callback)) .fail(err => callback(err)); }); }); }; -function processValidlySignedSamlLogout(self, doc, callback) { +function processValidlySignedSamlLogout(self, doc, dom, callback) { var response = doc.LogoutResponse; var request = doc.LogoutRequest; if (response){ return callback(null, null, true); } else if (request) { - processValidlySignedPostRequest(self, doc, callback); + processValidlySignedPostRequest(self, doc, dom, callback); } else { throw new Error('Unknown SAML response message'); } @@ -1114,13 +1114,58 @@ SAML.prototype.validatePostRequest = function (container, callback) { return callback(new Error('Invalid signature on documentElement')); } - processValidlySignedPostRequest(this, doc, callback); + processValidlySignedPostRequest(this, doc, dom, callback); }) .fail(err => callback(err)); }); }; -function processValidlySignedPostRequest(self, doc, callback) { +function callBackWithNameID(nameid, callback) { + var format = xpath(nameid, "@Format"); + return callback(null, { + value: nameid.textContent, + format: format && format[0] && format[0].nodeValue + }); +} + +SAML.prototype.getNameID = function(self, doc, callback) { + var nameIds = xpath(doc, "/*[local-name()='LogoutRequest']/*[local-name()='NameID']"); + var encryptedIds = xpath(doc, + "/*[local-name()='LogoutRequest']/*[local-name()='EncryptedID']"); + + if (nameIds.length + encryptedIds.length > 1) { + return callback(new Error('Invalid LogoutRequest')); + } + if (nameIds.length === 1) { + return callBackWithNameID(nameIds[0], callback); + } + if (encryptedIds.length === 1) { + if (!self.options.decryptionPvk) { + return callback(new Error('No decryption key for encrypted SAML response')); + } + + var encryptedDatas = xpath(encryptedIds[0], "./*[local-name()='EncryptedData']"); + + if (encryptedDatas.length !== 1) { + return callback(new Error('Invalid LogoutRequest')); + } + var encryptedDataXml = encryptedDatas[0].toString(); + + var xmlencOptions = { key: self.options.decryptionPvk }; + return Q.ninvoke(xmlenc, 'decrypt', encryptedDataXml, xmlencOptions) + .then(function (decryptedXml) { + var decryptedDoc = new xmldom.DOMParser().parseFromString(decryptedXml); + var decryptedIds = xpath(decryptedDoc, "/*[local-name()='NameID']"); + if (decryptedIds.length !== 1) { + return callback(new Error('Invalid EncryptedAssertion content')); + } + return callBackWithNameID(decryptedIds[0], callback); + }); + } + callback(new Error('Missing SAML NameID')); +}; + +function processValidlySignedPostRequest(self, doc, dom, callback) { var request = doc.LogoutRequest; if (request) { var profile = {}; @@ -1135,23 +1180,25 @@ function processValidlySignedPostRequest(self, doc, callback) { } else { return callback(new Error('Missing SAML issuer')); } - - var nameID = request.NameID; - if (nameID) { - profile.nameID = nameID[0]._; - - if (nameID[0].$ && nameID[0].$.Format) { - profile.nameIDFormat = nameID[0].$.Format; + self.getNameID(self, dom, function (err, nameID) { + if(err) { + return callback(err); } - } else { - return callback(new Error('Missing SAML NameID')); - } - var sessionIndex = request.SessionIndex; - if (sessionIndex) { - profile.sessionIndex = sessionIndex[0]._; - } - callback(null, profile, true); + if (nameID) { + profile.nameID = nameID.value; + if (nameID.format) { + profile.nameIDFormat = nameID.format; + } + } else { + return callback(new Error('Missing SAML NameID')); + } + var sessionIndex = request.SessionIndex; + if (sessionIndex) { + profile.sessionIndex = sessionIndex[0]._; + } + callback(null, profile, true); + }); } else { return callback(new Error('Unknown SAML request message')); } diff --git a/test/static/logout_request_with_encrypted_name_id.xml b/test/static/logout_request_with_encrypted_name_id.xml new file mode 100644 index 000000000..fbfb2a23f --- /dev/null +++ b/test/static/logout_request_with_encrypted_name_id.xml @@ -0,0 +1,25 @@ + + + http://sp.example.com/demo1/metadata.php + + + myTkSwwgK+Lcx5JZukoHggbFBVA=ayMUV7pPijoh7ocMnIz2GPYK7Y4Olib+U1mIUX0o7uU22m+ZGUP2HkmvC7bIZ4N3MFyeUyPuEBTDdFtLaENTWzovGMNRXDSypNI5UwobGqAFu16BY9lL5uJ/6HAAEgayxaY5kVMEY4VmNUpNCIE0WMpzA91bnElbEFsxi6G8sUj49oDFYncbOHilThLoe0dPQxB7N1wfX73k5nZ/hkEPlbwvENJRRojvwlbcn7crviYRzbzJPa31iMKmaSTaoS7cIV0Q8V1jYuQV29Y4eNwbVa4ZGu06by5CFXHYZoev0zyEoOTCNgQuF72zxzOmzDF5yEH5fWPE0QwUhf8MMGOtGg== +MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynXKsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJyvO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEslqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMSaebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvoW4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzHF6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEXmBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesSiTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpRv5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9BfXNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8= + + + + + + + + Rtg5L09Un3DdhYBFJvYp4w1WGKHIi0Umf9PaFdiSL2r+1NK+Z76NwhR4vsIu1lq2BJEX1ZMTEJ/kitF/PgEqeAOGyLu80dmXyNbhKwbEZrbv+dYx5vJ21nHIbLeeknNB70XsGFtcrWbCqt2r6/e5wFxD7HglPmxWEzNgz5SGEki35MWqtCbfX8lTCsnKFEKU9GfaHYIfPZzf/szwJZVGJps5HI/k7wuKbfS/U8odxsyWX73//+rkduhF9j5Iq95+xd1KRrxcuyvfYsXH5SWcnXt2nIlHWuCDHVNILoWDvskvOyGTP1e+8K+W2sgptoA98D6NJb+k+x/TCD1eFbce5w== + + + + + hbSadp3tEX/QyaTsIpChQQat89Yc3shCc6728DoS4qzdrsswHIUoBjp5hKDjlECJBQvXSNFV3vYn93pm44/fl0Z3yTqKkt6eUI3lZQ3ZsVfzVwkNT2jAnZom+OThhVfb1vcpN62tDmGI2dLxzZAvOuvmuG52qambAnZ6hR4FevVyCww5AkD86x8Q8OWYUTwPsggDOuQEbDyMXG4YoRpUag9boTMoUcidmUQaeO6omLzr/Mg1P0xY8fkVetVh63L1T6Kp+6c17bSIW1q4e8SazujVoQZ5eCJP4DQHAvmmcEs= + + + + 1 + diff --git a/test/tests.js b/test/tests.js index 078b43d7c..c7d8888bb 100644 --- a/test/tests.js +++ b/test/tests.js @@ -2112,6 +2112,30 @@ describe( 'passport-saml /', function() { done(err2); } }); + }) + it('returns profile for valid signature with encrypted nameID', function(done) { + var samlObj = new SAML({ + cert: fs.readFileSync(__dirname + '/static/cert.pem', 'ascii'), + decryptionPvk: fs.readFileSync(__dirname + '/static/key.pem', 'ascii') + }); + var body = { + SAMLRequest: fs.readFileSync(__dirname + '/static/logout_request_with_encrypted_name_id.xml', 'base64') + }; + samlObj.validatePostRequest(body, function(err, profile) { + try { + should.not.exist(err); + profile.should.eql({ + ID: 'pfx00cb5227-d9d0-1d4b-bdb2-c7ad6c3c6906', + issuer: 'http://sp.example.com/demo1/metadata.php', + nameID: 'ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7', + nameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + sessionIndex: '1' + }); + done(); + } catch (err2) { + done(err2); + } + }); }); it('errors if bad privateCert to requestToURL', function(done){ var samlObj = new SAML({