From 4b02e16a6d7cb7534c585c3e015b5a5f0d2aa9ac Mon Sep 17 00:00:00 2001 From: Thomas Moiron Date: Tue, 10 Apr 2018 17:58:59 +0200 Subject: [PATCH] Adds signing key in the metadata service provider generation. --- README.md | 5 +- lib/passport-saml/saml.js | 67 ++++++++++++++------ lib/passport-saml/strategy.js | 4 +- test/static/expected metadata.xml | 2 +- test/static/expectedMetadataWithBothKeys.xml | 65 +++++++++++++++++++ test/tests.js | 24 ++++++- 6 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 test/static/expectedMetadataWithBothKeys.xml diff --git a/README.md b/README.md index 6e1bf4b1..527f3b8a 100644 --- a/README.md +++ b/README.md @@ -173,12 +173,15 @@ app.get('/login', ); ``` -### generateServiceProviderMetadata( decryptionCert ) +### generateServiceProviderMetadata( decryptionCert, signingCert ) + As a convenience, the strategy object exposes a `generateServiceProviderMetadata` method which will generate a service provider metadata document suitable for supplying to an identity provider. This method will only work on strategies which are configured with a `callbackUrl` (since the relative path for the callback is not sufficient information to generate a complete metadata document). The `decryptionCert` argument should be a public certificate matching the `decryptionPvk` and is required if the strategy is configured with a `decryptionPvk`. +The `signingCert` argument should be a public certificate matching the `privateCert` and is required if the strategy is configured with a `privateCert`. + ## Security and signatures diff --git a/lib/passport-saml/saml.js b/lib/passport-saml/saml.js index 4f016bda..d26703d4 100644 --- a/lib/passport-saml/saml.js +++ b/lib/passport-saml/saml.js @@ -994,7 +994,7 @@ function processValidlySignedPostRequest(self, doc, callback) { } } -SAML.prototype.generateServiceProviderMetadata = function( decryptionCert ) { +SAML.prototype.generateServiceProviderMetadata = function( decryptionCert, signingCert ) { var metadata = { 'EntityDescriptor' : { '@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata', @@ -1012,26 +1012,58 @@ SAML.prototype.generateServiceProviderMetadata = function( decryptionCert ) { throw new Error( "Missing decryptionCert while generating metadata for decrypting service provider"); } + } + + if(this.options.privateCert){ + if(!signingCert){ + throw new Error( + "Missing signingCert while generating metadata for signing service provider messages"); + } + } - decryptionCert = decryptionCert.replace( /-+BEGIN CERTIFICATE-+\r?\n?/, '' ); - decryptionCert = decryptionCert.replace( /-+END CERTIFICATE-+\r?\n?/, '' ); - decryptionCert = decryptionCert.replace( /\r\n/g, '\n' ); + if(this.options.decryptionPvk || this.options.privateCert){ + metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor=[]; + if (this.options.privateCert) { - metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor = { - 'ds:KeyInfo' : { - 'ds:X509Data' : { - 'ds:X509Certificate': { - '#text': decryptionCert + signingCert = signingCert.replace( /-+BEGIN CERTIFICATE-+\r?\n?/, '' ); + signingCert = signingCert.replace( /-+END CERTIFICATE-+\r?\n?/, '' ); + signingCert = signingCert.replace( /\r\n/g, '\n' ); + + metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor.push({ + '@use': 'signing', + 'ds:KeyInfo' : { + 'ds:X509Data' : { + 'ds:X509Certificate': { + '#text': signingCert + } } } - }, - 'EncryptionMethod' : [ - // this should be the set that the xmlenc library supports - { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' }, - { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' }, - { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' } - ] - }; + }); + } + + if (this.options.decryptionPvk) { + + decryptionCert = decryptionCert.replace( /-+BEGIN CERTIFICATE-+\r?\n?/, '' ); + decryptionCert = decryptionCert.replace( /-+END CERTIFICATE-+\r?\n?/, '' ); + decryptionCert = decryptionCert.replace( /\r\n/g, '\n' ); + + metadata.EntityDescriptor.SPSSODescriptor.KeyDescriptor.push({ + '@use': 'encryption', + 'ds:KeyInfo' : { + 'ds:X509Data' : { + 'ds:X509Certificate': { + '#text': decryptionCert + } + } + }, + 'EncryptionMethod' : [ + // this should be the set that the xmlenc library supports + { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' }, + { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' }, + { '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' } + ] + }); + } } if (this.options.logoutCallbackUrl) { @@ -1048,7 +1080,6 @@ SAML.prototype.generateServiceProviderMetadata = function( decryptionCert ) { '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', '@Location': this.getCallbackUrl({}) }; - return xmlbuilder.create(metadata).end({ pretty: true, indent: ' ', newline: '\n' }); }; diff --git a/lib/passport-saml/strategy.js b/lib/passport-saml/strategy.js index 335f6972..81fb93ae 100644 --- a/lib/passport-saml/strategy.js +++ b/lib/passport-saml/strategy.js @@ -115,8 +115,8 @@ Strategy.prototype.logout = function(req, callback) { this._saml.getLogoutUrl(req, {}, callback); }; -Strategy.prototype.generateServiceProviderMetadata = function( decryptionCert ) { - return this._saml.generateServiceProviderMetadata( decryptionCert ); +Strategy.prototype.generateServiceProviderMetadata = function( decryptionCert, signingCert ) { + return this._saml.generateServiceProviderMetadata( decryptionCert, signingCert ); }; module.exports = Strategy; diff --git a/test/static/expected metadata.xml b/test/static/expected metadata.xml index b915723f..c0edf3cb 100644 --- a/test/static/expected metadata.xml +++ b/test/static/expected metadata.xml @@ -1,7 +1,7 @@ - + MIIEsDCCApigAwIBAgIBADANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwgxMC4w diff --git a/test/static/expectedMetadataWithBothKeys.xml b/test/static/expectedMetadataWithBothKeys.xml new file mode 100644 index 00000000..db1f129d --- /dev/null +++ b/test/static/expectedMetadataWithBothKeys.xml @@ -0,0 +1,65 @@ + + + + + + + MIICrjCCAZYCCQDWybyUsLVkXzANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDFA5h +Y21lX3Rvb2xzLmNvbTAeFw0xNTA4MTgwODQ3MzZaFw0yNTA4MTcwODQ3MzZaMBkx +FzAVBgNVBAMUDmFjbWVfdG9vbHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAlyT+OzEymhaZFNfx4+HFxZbBP3egvcUgPvGa7wWCV7vyuCauLBqw +O1FQqzaRDxkEihkHqmUz63D25v2QixLxXyqaFQ8TxDFKwYATtSL7x5G2Gww56H0L +1XGgYdNW1akPx90P+USmVn1Wb//7AwU+TV+u4jIgKZyTaIFWdFlwBhlp4OBEHCyY +wngFgMyVoCBsSmwb4if7Mi5T746J9ZMQpC+ts+kfzley59Nz55pa5fRLwu4qxFUv +2oRdXAf2ZLuxB7DPQbRH82/ewZZ8N4BUGiQyAwOsHgp0sb9JJ8uEM/qhyS1dXXxj +o+kxsI5HXhxp4P5R9VADuOquaLIo8ptIrQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQBW/Y7leJnV76+6bzeqqi+buTLyWc1mASi5LVH68mdailg2WmGfKlSMLGzFkNtg +8fJnfaRZ/GtxmSxhpQRHn63ZlyzqVrFcJa0qzPG21PXPHG/ny8pN+BV8fk74CIb/ ++YN7NvDUrV7jlsPxNT2rQk8G2fM7jsTMYvtz0MBkrZZsUzTv4rZkF/v44J/ACDir +KJiE+TYArm70yQPweX6RvYHNZLSzgg4o+hoyBXo5BGQetAjmcIhC6ZOwN3iVhGjp +0YpWM0pkqStPy3sIR0//LZbskWWlSRb0fX1c4632Xb+zikfec4DniYV6CxkB2U+p +lHpOX1rt1R+UiTEIhTSXPNt/ + + + + + + + + MIIEsDCCApigAwIBAgIBADANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwgxMC4w +LjEuNDAeFw0xNDA1MDgwMDU0MTlaFw0xNDA1MDgwMDU0MTlaMBMxETAPBgNVBAMT +CDEwLjAuMS40MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5hqjaroJ +pB+aR8FME7hQ9nMV0h7MpKtmgFLcK3vwP67feAK+xdt17i8RyUhxil9FCFR5K08W +jwo3NiHZqHqEKitw+IJSndjLSsoNgKEIaiFSug2eV1oYElz06DBXTxc8iq/Laznd +qTUom51Ode9yI9AGa88cDM5iOqq9mhuGuvwuLtoyU78Ld+s1Ea6Mgf7L8M7fZVO7 +Ncu+FgIzI6Gt035ohYCLBmOoM7o0uj7DcMEvKOMFziwF40wYmyp3hCLlq3qwkM9p +TVJltuz0Bt1vqDdrq3kTheA9JHMayRz3I/BZxAV3iRd4hzLKTkegD8ToTGU10Gme ++ZAr1w/erc5hVrM0/XBmHQlnI5d31GU/mfIkm0XPTGRSpPy7E+dUvj9djvm/VqDd +ojf3uuwirGeLMRlO9P/lCerTktW3g27SV8gn3ETm2Mm7rkNqf24KJpDv0tKDosgb +daHr2IEYD4RpqySp8kd25BhzushqKRkS8Xu5t7HAlVSHwiFhuLqrr4dUfkB8kZeM +/ycfZLCn7oNUDFdgjGYSVMpakL97sC9slAW4/8UtXXZxLqcyq/YxdpCysPYP1hsA +p+VgPC7GI6CyiNojKPOptMqLZRYnViKxlOiWBJBzUBRUVuac8LXrMiDw8btWGa1G +h5vThuFUKsvmRoeuk7eyXEN9J7j6+fTYjnsCAwEAAaMPMA0wCwYDVR0PBAQDAgTw +MA0GCSqGSIb3DQEBBQUAA4ICAQDAPFUo0Pga7vB4Ijy9u3qpWLQSCd4xfw8l92iq +3JqLVXBx8Gf9XVy6GzbXWIe6pmQIzmom/CWQvh6o7kc0F4y4ftsWfqnq3k0+HcX4 +Heu1PN2HSnUUPNsk5Adggd3129MHIdfaKb8bSuLJvSMeMaycrmsb+gEF6JFP8kGW +OP4xQYjAVODcFbOyAfEuVAhujw0bqlLHT+El4vlfBrtdU+MZztIqQkGDW7cmm86Z +GiBr/ga97BCnOCrgZXlrgmia8SmF7Rfa48/Xzh7bDmPfgmHglL5JxhpRX2IoPY5X +ItmR8IaYKhOBOrO/qercJ6Z3rQ6fIrs8HrYgeTO/uE9ww6WRWCWDaCR4oWDVwjKz +GfP1oJtAIQ+UNjOddH5otDawXJRlQWpr72qT7+WOK/yzJhN0xmXDkmt5psMY/CDn +4kXMM0xaRpfYOn5Sc1oelQ+hIm8/rHVK/0krhCDOniqj+L/ne2SoyoXYgS6o0Nlk +NhtTHmjjosE7HV6JfAUgq2D9dq00JO967I3P0VF6F8lcdaq0tovJhKWRjrrigYd7 +56/3aT2B83JWA1RA9niXmrXfnAPZrNCfWvj+X2alUN/izdIqRts0UpWToVUr0ozW +gmbmp5ZOGYOQh+lNcAp3V9lOrdbZVSITM47RYGBZaZNDA2PbDzC13MS/EjTEvSko +nwtlCg== + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + \ No newline at end of file diff --git a/test/tests.js b/test/tests.js index 0fc028a3..1ea74bb7 100644 --- a/test/tests.js +++ b/test/tests.js @@ -673,16 +673,16 @@ describe( 'passport-saml /', function() { }); describe( 'generateServiceProviderMetadata tests /', function() { - function testMetadata( samlConfig, expectedMetadata ) { + function testMetadata( samlConfig, expectedMetadata, signingCert ) { var samlObj = new SAML( samlConfig ); var decryptionCert = fs.readFileSync(__dirname + '/static/testshib encryption cert.pem', 'utf-8'); - var metadata = samlObj.generateServiceProviderMetadata( decryptionCert ); + var metadata = samlObj.generateServiceProviderMetadata( decryptionCert, signingCert ); // splits are to get a nice diff if they don't match for some reason metadata.split( '\n' ).should.eql( expectedMetadata.split( '\n' ) ); // verify that we are exposed through Strategy as well var strategy = new SamlStrategy( samlConfig, function() {} ); - metadata = strategy.generateServiceProviderMetadata( decryptionCert ); + metadata = strategy.generateServiceProviderMetadata( decryptionCert, signingCert ); metadata.split( '\n' ).should.eql( expectedMetadata.split( '\n' ) ); } @@ -739,6 +739,24 @@ describe( 'passport-saml /', function() { testMetadata( samlConfig, expectedMetadata ); done(); }); + + it( 'config with protocol, path, host, decryptionPvk and privateCert should pass', function( done ) { + var samlConfig = { + issuer: 'http://example.serviceprovider.com', + protocol: 'http://', + host: 'example.serviceprovider.com', + path: '/saml/callback', + identifierFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + decryptionPvk: fs.readFileSync(__dirname + '/static/testshib encryption pvk.pem'), + privateCert: fs.readFileSync(__dirname + '/static/acme_tools_com.key') + }; + var expectedMetadata = fs.readFileSync(__dirname + '/static/expectedMetadataWithBothKeys.xml', 'utf-8'); + var signingCert = fs.readFileSync(__dirname + '/static/acme_tools_com.cert').toString(); + + testMetadata( samlConfig, expectedMetadata, signingCert ); + done(); + }); + }); it('generateServiceProviderMetadata contains logout callback url', function (done) {