From 7bcd937970757e519fe788168c417dc2b5acd093 Mon Sep 17 00:00:00 2001 From: Aswin V Date: Mon, 13 Nov 2023 19:17:36 +0800 Subject: [PATCH] Enhance multi cert handling in parseMetadata (#438) * Simple concat instead of comma * Assert public key in metadata * validate response signed using one of multiple certs * Fix assertion and update test asset * Use unformatted SAML response without signing cert * Refactor test/asset names * Add more test cases --- lib/validateSignature.ts | 2 +- .../saml20.validResponseSigned-noX509.xml | 1 + test/lib/metadata.spec.ts | 28 ++++++ test/lib/validateSignature.spec.ts | 98 +++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 test/assets/saml20.validResponseSigned-noX509.xml diff --git a/lib/validateSignature.ts b/lib/validateSignature.ts index b97fa584..91d71baf 100644 --- a/lib/validateSignature.ts +++ b/lib/validateSignature.ts @@ -17,7 +17,7 @@ const _certToPEM = (cert) => { const certToPEM = (cert) => { if (cert.indexOf(',') !== -1) { const _certs = cert.split(','); - return _certs.map((_cert) => _certToPEM(_cert)).join(','); + return _certs.map((_cert) => _certToPEM(_cert)).join(''); } return _certToPEM(cert); diff --git a/test/assets/saml20.validResponseSigned-noX509.xml b/test/assets/saml20.validResponseSigned-noX509.xml new file mode 100644 index 00000000..fa3ac428 --- /dev/null +++ b/test/assets/saml20.validResponseSigned-noX509.xml @@ -0,0 +1 @@ +https://saml.example.com/entityid-localhostpil/VfYGUm6+SmrAmGwzT0b5dx/iPTsJHcVZ5X4WHjs=spN9JpiL6lhktxxWNEnAy6tbO2SJvdZFpu1cPurHrn59BsYTHZvcPGmgV6J8oi0A95dPrjVWxBTdyFHDBJw+bJHuq4tdDVaYSr1f6NbxW/6osJm1BZrVwy1eSdOFP7Q5aojapJL4qw+5I+fiEx9rDxeeb9+ibHtI8gQsiuv/6m77rJNikdKbaiuYzuhO1or+EZ/z8O/PPQ6B1FQ22/lOzKLWw/jU3+OHTIKuiNP0pOzvYYQ5U5goD6U8e4pFCv1C5kpeuajWlgRGCvK1Eep89bBNWxGO/rSMb7Z5PqTCE2Z8rr/xMNYY2knC5mMDB3WF0zTHmAPVJWBwNNt2vKIb0Q==https://saml.example.com/entityid-localhostjackson@example.comhttps://saml.boxyhq.comurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified1dda9fb491dc01bd24d2423ba2f22ae561f56ddf2376b29a11c80281d21201f9jackson@example.comjacksonjackson \ No newline at end of file diff --git a/test/lib/metadata.spec.ts b/test/lib/metadata.spec.ts index 20306574..5b7f9681 100644 --- a/test/lib/metadata.spec.ts +++ b/test/lib/metadata.spec.ts @@ -55,6 +55,34 @@ describe('metadata.ts', function () { expect(value.thumbprint).to.equal( '8996bcc1afff3ff8e41f8025ff034b516050a434,f9e424fe5fb3422db37859fe29b7f92f11af60a7' ); + expect(value.publicKey).to.equal(`MIICmDCCAYACCQC6LM978TM/gjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJJ +\t\t\t\t\t\tbjAgFw0yMjA0MTExMDI3MjBaGA8zMDIxMDgxMjEwMjcyMFowDTELMAkGA1UEBhMC +\t\t\t\t\t\tSW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgPMN71V4y5VzLw6Ev +\t\t\t\t\t\taQA+oMLzmIpoV/p4Y3AM00FUYbVhVtngvRPCmsKOvIxkTM9kZ6VjVfPmzQet+dDS +\t\t\t\t\t\t+rOmJDH5Y+42du6dJnA0SM/wNWL7nAqfWN6e7q7/Jxa/dYMOhkgV6/7+0jBxHGnn +\t\t\t\t\t\tx/2CEVeDF5+nPsdDh2HlPy0MCXLjXGvRpHB/IHQsUHJFKuOQzTiz1OMQHLnV+FQX +\t\t\t\t\t\tT2kDsGmbM/wZo6xGeH5qcRqZJGgLvtLj8XNe6yVmb1naog7Fr7gjThMichkNDVg2 +\t\t\t\t\t\t0/lkxYqIL8zgS2NYXwQ6UOKplUv189kHSbXgQCco0h1oNR2LRTaHoYsRnzLMH2Pv +\t\t\t\t\t\tjVoTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMXKnzYEyLFwePXXWE76lq5S+2O2 +\t\t\t\t\t\tJIMtygzB3YxOJwvIFWmwDPxqpr4aOpw6T2pQLa3rM1YjW2roNw7B3HHXWoc9F4Av +\t\t\t\t\t\tGAe8T1u0Cu+Tyo8ZFf9VrPg5kZ7x2G+nojFfs8zeuEKdNrUZz4bkgkC7sTWHFsOA +\t\t\t\t\t\toZjUqLyT2tfLnXfYGiXd0qGg9X1bs1x+anAhViltjZ97Eeq8wPtRqhm1hiQyawKT +\t\t\t\t\t\t5qs4oKw0AaKsW4pBQux4h+ZmfvqD+1chBd5Ve/bq9FsEnWNkGyawzmsMSTB9UwDA +\t\t\t\t\t\t+bqiHmfaTXWlQnualNaY3g5v7EDVB4COz6rXXQY/y5Y90BFoho5MqIjGW0I=,-----BEGIN CERTIFICATE----- +\t\t\t\t\t\tMIICmDCCAYACCQC6LM978TM/gjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJJ +\t\t\t\t\t\tbjAgFw0yMjA0MTExMDI3MjBaGA8zMDIxMDgxMjEwMjcyMFowDTELMAkGA1UEBhMC +\t\t\t\t\t\tSW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgPMN71V4y5VzLw6Ev +\t\t\t\t\t\taQA+oMLzmIpoV/p4Y3AM00FUYbVhVtngvRPCmsKOvIxkTM9kZ6VjVfPmzQet+dDS +\t\t\t\t\t\t+rOmJDH5Y+42du6dJnA0SM/wNWL7nAqfWN6e7q7/Jxa/dYMOhkgV6/7+0jBxHGnn +\t\t\t\t\t\tx/2CEVeDF5+nPsdDh2HlPy0MCXLjXGvRpHB/IHQsUHJFKuOQzTiz1OMQHLnV+FQX +\t\t\t\t\t\tT2kDsGmbM/wZo6xGeH5qcRqZJGgLvtLj8XNe6yVmb1naog7Fr7gjThMichkNDVg2 +\t\t\t\t\t\t0/lkxYqIL8zgS2NYXwQ6UOKplUv189kHSbXgQCco0h1oNR2LRTaHoYsRnzLMH2Pv +\t\t\t\t\t\tjVoTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMXKnzYEyLFwePXXWE76lq5S+2O2 +\t\t\t\t\t\tJIMtygzB3YxOJwvIFWmwDPxqpr4aOpw6T2pQLa3rM1YjW2roNw7B3HHXWoc9F4Av +\t\t\t\t\t\tGAe8T1u0Cu+Tyo8ZFf9VrPg5kZ7x2G+nojFfs8zeuEKdNrUZz4bkgkC7sTWHFsOA +\t\t\t\t\t\toZjUqLyT2tfLnXfYGiXd0qGg9X1bs1x+anAhViltjZ97Eeq8wPtRqhm1hiQyawKT +\t\t\t\t\t\t5qs4oKw0AaKsW4pBQux4h+ZmfvqD+1chBd5Ve/bq9FsEnWNkGyawzmsMSTB9UwDA +\t\t\t\t\t\t+bqiHmfaTXWlQnualNaY3g5v7EDVB4COz6rXXQY/y5Y90BFoho5MqIjGW0I=`); expect(value.loginType).to.equal('idp'); expect(value.sso.postUrl).to.equal('http://localhost:4000/api/saml/sso'); expect(value.sso.redirectUrl).to.equal('http://localhost:4000/api/saml/sso'); diff --git a/test/lib/validateSignature.spec.ts b/test/lib/validateSignature.spec.ts index e87253ef..d02d6433 100644 --- a/test/lib/validateSignature.spec.ts +++ b/test/lib/validateSignature.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import xmlbuilder from 'xmlbuilder'; import crypto from 'crypto'; +import fs from 'fs'; import { sign } from '../../lib/sign'; const ssoUrl = @@ -20,6 +21,88 @@ const authnXPath = const identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'; const providerName = 'BoxyHQ'; +const validResponseSigned_noX509 = fs + .readFileSync('./test/assets/saml20.validResponseSigned-noX509.xml') + .toString(); + +const singlePublicKey = `MIIDczCCAlugAwIBAgIUE4RU7Pwiw58ZifnjQOXVg6ytNWowDQYJKoZIhvcNAQEL + BQAwSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAoM + BkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTAgFw0yMzExMTIxMDQ1MzdaGA8z + MDIzMDMxNTEwNDUzN1owSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3Rh + dGUxDzANBgNVBAoMBkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkwF6oPPd3Fn3AXC8K8h+q0uRgRoJim + HASKmwzXZZjqb2DN0isLNvbLlcB3mTmfQMhKH4yLPE5PHoDJ83olgILkB6Y3txgG + QJ48sIEeYiGCs+le4UnD44oL04fQCpkIImcFiHM/tr9kSnQsjF7tLn6GVZJKUU56 + 84mrOACHr3LDZkypLxjiYMoM9aojS3yw97AIJSyhmkpowuqdtmK/T5o4pnTNgXTB + XYPoGx/6aqoFVxAjh7ZuUzeHAMGHZlxT0e6K7nKSPoFKDbfDQoAwbq6B1BRNklSX + 4dz6MkmQAGqMnKBWNbiF2MAnt5dvIXInlafQ3Ypbw/bJ4uHw6L+RjGcCAwEAAaNT + MFEwHQYDVR0OBBYEFHyOsXZSwmNqljrM6LmWFWr0nUsvMB8GA1UdIwQYMBaAFHyO + sXZSwmNqljrM6LmWFWr0nUsvMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBALFfujo7fMqszjEg7Gla3FthO82/D+7mFKSGt04ZJfxlwuujTpI8u04g + LWNFV6uHLNNlxesdd1r9JtlXAHN4pDk06TEidz1oOO1rBWVDBajrO1wME99EqOAj + Q64SOFhkpw9Yd5L47SnxC3rQPsgeol+BJwosXcPG4OXjK5JisQGdakEJh8GLnE5u + 7QK5eFf84Qro6HthD+YsA0pPFDzh4TtSpm/yYDYRvKAfqh4a2uqwJDHJ8oxz5d37 + 4eXJ/Zy78JiYM4PUnPMKABsqcUZv5vsuV5HPO4ODtcGFRY1EoSXcMxz0jkUipe+Z + wmF8r5aO5sSGd+KOi2O/ja9VV4UzGD8=`; + +const singlePublicKeyNotUsedToSign = `MIIDczCCAlugAwIBAgIUOJZExQRTahl1DA9raMp0G6vCkHwwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAoM +BkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTAgFw0yMzExMTIxMTEwMDNaGA8z +MDIzMDMxNTExMTAwM1owSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3Rh +dGUxDzANBgNVBAoMBkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWZyyDK9/I3Pic2TCnckbdVG/PIknyk +YszbA+87q/MWlBA/vX2DogUw6UapZ07r6kxYRyMg/7VlJNP5rZXowv0LEpfpdAth +8O7TomyEbwhl4u/8CcCbRvihkQtr1DFlHBYVSC7znkpeS1iYwfsDKhZc5NHmplG5 ++dERS71rtWqxb9hySPcX2CUJOvLjeC6uhTux5ers33963qnQzEsOuBRvcUT6TU7Y +4WjzMycAjtsfT9r5y5Lhv9DpsIpVSRQ1MCLHCAeD1BerUZaebTonbsEA1EHk4vux +FmjvlrNp4hh2zrtGt7yZO2cAzcNmloq+JmZ/7Yeb5CAhCaXIXFBBsh0CAwEAAaNT +MFEwHQYDVR0OBBYEFLb5bLFbrOVXMAT5YnsQLSkPL3AyMB8GA1UdIwQYMBaAFLb5 +bLFbrOVXMAT5YnsQLSkPL3AyMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAKJFOBEouNp2AJicbA3Lmb4vVJfwP9h8LGqHV3TZHhlEblmBQNEoLyLO +z7XhIy1/5LyGb7b/o0LAoC1RxH/6GiHcIKt4/DS7dOfrpcNkHXAUHVFZ1LfFtBHc +zIZTXKWNiFLqz3nTaKS3dqmnZMsoWDuRpE4kwR5tT+zB492nnfH7XGICQDojQ1DN +NDvfSxFNmjcEuabxM9VGdsX6xOiClZBJwJBixj74EYPeeVOPbOEQfQZchX8xB3u5 +2knHSNiamr0NJ4GA44hIoCADW2G6W2+A4gFNnA6UYFlaijMWqb/XSNlbkYZD6OkG +9Xa5bTycscrxF6+S3n5z2yGft52wBe4=`; + +const multiPublicKey = `MIIDczCCAlugAwIBAgIUE4RU7Pwiw58ZifnjQOXVg6ytNWowDQYJKoZIhvcNAQEL + BQAwSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAoM + BkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTAgFw0yMzExMTIxMDQ1MzdaGA8z + MDIzMDMxNTEwNDUzN1owSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3Rh + dGUxDzANBgNVBAoMBkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkwF6oPPd3Fn3AXC8K8h+q0uRgRoJim + HASKmwzXZZjqb2DN0isLNvbLlcB3mTmfQMhKH4yLPE5PHoDJ83olgILkB6Y3txgG + QJ48sIEeYiGCs+le4UnD44oL04fQCpkIImcFiHM/tr9kSnQsjF7tLn6GVZJKUU56 + 84mrOACHr3LDZkypLxjiYMoM9aojS3yw97AIJSyhmkpowuqdtmK/T5o4pnTNgXTB + XYPoGx/6aqoFVxAjh7ZuUzeHAMGHZlxT0e6K7nKSPoFKDbfDQoAwbq6B1BRNklSX + 4dz6MkmQAGqMnKBWNbiF2MAnt5dvIXInlafQ3Ypbw/bJ4uHw6L+RjGcCAwEAAaNT + MFEwHQYDVR0OBBYEFHyOsXZSwmNqljrM6LmWFWr0nUsvMB8GA1UdIwQYMBaAFHyO + sXZSwmNqljrM6LmWFWr0nUsvMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBALFfujo7fMqszjEg7Gla3FthO82/D+7mFKSGt04ZJfxlwuujTpI8u04g + LWNFV6uHLNNlxesdd1r9JtlXAHN4pDk06TEidz1oOO1rBWVDBajrO1wME99EqOAj + Q64SOFhkpw9Yd5L47SnxC3rQPsgeol+BJwosXcPG4OXjK5JisQGdakEJh8GLnE5u + 7QK5eFf84Qro6HthD+YsA0pPFDzh4TtSpm/yYDYRvKAfqh4a2uqwJDHJ8oxz5d37 + 4eXJ/Zy78JiYM4PUnPMKABsqcUZv5vsuV5HPO4ODtcGFRY1EoSXcMxz0jkUipe+Z + wmF8r5aO5sSGd+KOi2O/ja9VV4UzGD8=,MIIDczCCAlugAwIBAgIUOJZExQRTahl1DA9raMp0G6vCkHwwDQYJKoZIhvcNAQEL + BQAwSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAoM + BkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTAgFw0yMzExMTIxMTEwMDNaGA8z + MDIzMDMxNTExMTAwM1owSDELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3Rh + dGUxDzANBgNVBAoMBkJveHlIUTETMBEGA1UEAwwKYm94eWhxLmNvbTCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWZyyDK9/I3Pic2TCnckbdVG/PIknyk + YszbA+87q/MWlBA/vX2DogUw6UapZ07r6kxYRyMg/7VlJNP5rZXowv0LEpfpdAth + 8O7TomyEbwhl4u/8CcCbRvihkQtr1DFlHBYVSC7znkpeS1iYwfsDKhZc5NHmplG5 + +dERS71rtWqxb9hySPcX2CUJOvLjeC6uhTux5ers33963qnQzEsOuBRvcUT6TU7Y + 4WjzMycAjtsfT9r5y5Lhv9DpsIpVSRQ1MCLHCAeD1BerUZaebTonbsEA1EHk4vux + FmjvlrNp4hh2zrtGt7yZO2cAzcNmloq+JmZ/7Yeb5CAhCaXIXFBBsh0CAwEAAaNT + MFEwHQYDVR0OBBYEFLb5bLFbrOVXMAT5YnsQLSkPL3AyMB8GA1UdIwQYMBaAFLb5 + bLFbrOVXMAT5YnsQLSkPL3AyMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAKJFOBEouNp2AJicbA3Lmb4vVJfwP9h8LGqHV3TZHhlEblmBQNEoLyLO + z7XhIy1/5LyGb7b/o0LAoC1RxH/6GiHcIKt4/DS7dOfrpcNkHXAUHVFZ1LfFtBHc + zIZTXKWNiFLqz3nTaKS3dqmnZMsoWDuRpE4kwR5tT+zB492nnfH7XGICQDojQ1DN + NDvfSxFNmjcEuabxM9VGdsX6xOiClZBJwJBixj74EYPeeVOPbOEQfQZchX8xB3u5 + 2knHSNiamr0NJ4GA44hIoCADW2G6W2+A4gFNnA6UYFlaijMWqb/XSNlbkYZD6OkG + 9Xa5bTycscrxF6+S3n5z2yGft52wBe4=`; + function generateXML() { const id = idPrefix + crypto.randomBytes(10).toString('hex'); const date = new Date().toISOString(); @@ -80,6 +163,21 @@ describe('validateSignature.ts', function () { expect(validateSignature(generateXML(), publicKey, null)).to.be.ok; }); + it('validate response signature - no embedded cert, use single cert to validate', function () { + const value = validateSignature(validResponseSigned_noX509, singlePublicKey, null); + expect(value).to.be.ok; + }); + + it('validate response signature - no embedded cert, use different cert, should fail validate', function () { + const value = validateSignature(validResponseSigned_noX509, singlePublicKeyNotUsedToSign, null); + expect(value).not.to.be.ok; + }); + + it('validate response signature - no embedded cert, use multikey cert to validate', function () { + const value = validateSignature(validResponseSigned_noX509, multiPublicKey, null); + expect(value).to.be.ok; + }); + it('validateSignature public key not ok ', function () { try { const value = validateSignature(generateXML(), undefined, 'null');