Skip to content

Commit

Permalink
Issue node-saml#206: Support signing AuthnRequests using the HTTP-POS…
Browse files Browse the repository at this point in the history
…T Binding

This commit adds support for signing AuthnRequests in the SAML HTTP-POST
binding. In the POST Binding the signature sits inside the SAML message
(as opposed to the Redirect binding, where the signture lives in the
URL's query string).

This will help suppport identity providers that require signed
AuthnRequests over the HTTP-POST binding.

Two new configuration options have been added:

* `digestAlgorithm`: allows you to specify the digest algorithm for the
  signature.
* `xmlSignatureTransforms`: allows you to configure which XML transforms
  to use.
  • Loading branch information
richardTowers committed May 3, 2017
1 parent 6d1215b commit 571214e
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
.vscode
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ Config parameter details:
* `privateCert`: see 'security and signatures'
* `decryptionPvk`: optional private key that will be used to attempt to decrypt any encrypted assertions that are received
* `signatureAlgorithm`: optionally set the signature algorithm for signing requests, valid values are 'sha1' (default), 'sha256', or 'sha512'
* `digestAlgorithm`: optionally set the digest algorithm for signing requests, valid values are 'sha1' (default), 'sha256', or 'sha512'
* `xmlSignatureTransforms`: optionally set an array of signature transforms to be used in HTTP-POST signatures. By default this is
[ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#' ]
* Additional SAML behaviors
* `additionalParams`: dictionary of additional query params to add to all requests
* `additionalAuthorizeParams`: dictionary of additional query params to add to 'authorize' requests
Expand Down
34 changes: 34 additions & 0 deletions lib/passport-saml/algorithms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var crypto = require('crypto');

exports.getSigningAlgorithm = function getSigningAlgorithm (shortName) {
switch(shortName) {
case 'sha256':
return 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
case 'sha512':
return 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
default:
return 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
}
};

exports.getDigestAlgorithm = function getDigestAlgorithm (shortName) {
switch(shortName) {
case 'sha256':
return 'http://www.w3.org/2001/04/xmlenc#sha256';
case 'sha512':
return 'http://www.w3.org/2001/04/xmlenc#sha512';
default:
return 'http://www.w3.org/2000/09/xmldsig#sha1';
}
};

exports.getSigner = function getSigner (shortName) {
switch(shortName) {
case 'sha256':
return crypto.createSign('RSA-SHA256');
case 'sha512':
return crypto.createSign('RSA-SHA512');
default:
return crypto.createSign('RSA-SHA1');
}
};
28 changes: 28 additions & 0 deletions lib/passport-saml/saml-post-signing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var SignedXml = require('xml-crypto').SignedXml;
var algorithms = require('./algorithms');

var authnRequestXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
var defaultTransforms = [ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#' ];

function signSamlPost(samlMessage, xpath, options) {
if (!samlMessage) throw new Error('samlMessage is required');
if (!xpath) throw new Error('xpath is required');
if (!options || !options.privateCert) throw new Error('options.privateCert is required');

var transforms = options.xmlSignatureTransforms || defaultTransforms;
var sig = new SignedXml();
if (options.signatureAlgorithm) {
sig.signatureAlgorithm = algorithms.getSigningAlgorithm(options.signatureAlgorithm);
}
sig.addReference(xpath, transforms, algorithms.getDigestAlgorithm(options.digestAlgorithm));
sig.signingKey = options.privateCert;
sig.computeSignature(samlMessage);
return sig.getSignedXml();
}

function signAuthnRequestPost(authnRequest, options) {
return signSamlPost(authnRequest, authnRequestXPath, options);
}

exports.signSamlPost = signSamlPost;
exports.signAuthnRequestPost = signAuthnRequestPost;
34 changes: 15 additions & 19 deletions lib/passport-saml/saml.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ var xmlbuilder = require('xmlbuilder');
var xmlenc = require('xml-encryption');
var xpath = xmlCrypto.xpath;
var InMemoryCacheProvider = require('./inmemory-cache-provider.js').CacheProvider;
var algorithms = require('./algorithms');
var signAuthnRequestPost = require('./saml-post-signing').signAuthnRequestPost;
var Q = require('q');

var SAML = function (options) {
Expand Down Expand Up @@ -109,20 +111,8 @@ SAML.prototype.generateInstant = function () {
SAML.prototype.signRequest = function (samlMessage) {
var signer;
var samlMessageToSign = {};
switch(this.options.signatureAlgorithm) {
case 'sha256':
samlMessage.SigAlg = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
signer = crypto.createSign('RSA-SHA256');
break;
case 'sha512':
samlMessage.SigAlg = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
signer = crypto.createSign('RSA-SHA512');
break;
default:
samlMessage.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
signer = crypto.createSign('RSA-SHA1');
break;
}
samlMessage.SigAlg = algorithms.getSigningAlgorithm(this.options.signatureAlgorithm);
signer = algorithms.getSigner(this.options.signatureAlgorithm);
if (samlMessage.SAMLRequest) {
samlMessageToSign.SAMLRequest = samlMessage.SAMLRequest;
}
Expand All @@ -139,11 +129,13 @@ SAML.prototype.signRequest = function (samlMessage) {
samlMessage.Signature = signer.sign(this.options.privateCert, 'base64');
};

SAML.prototype.generateAuthorizeRequest = function (req, isPassive, callback) {
SAML.prototype.generateAuthorizeRequest = function (req, options, callback) {
var self = this;
var id = "_" + self.generateUniqueID();
var instant = self.generateInstant();
var forceAuthn = self.options.forceAuthn || false;
var isPassive = options.isPassive;
var isHttpPostBinding = options.isHttpPostBinding;

Q.fcall(function() {
if(self.options.validateInResponseTo) {
Expand Down Expand Up @@ -199,7 +191,11 @@ SAML.prototype.generateAuthorizeRequest = function (req, isPassive, callback) {
request['samlp:AuthnRequest']['@AttributeConsumingServiceIndex'] = self.options.attributeConsumingServiceIndex;
}

callback(null, xmlbuilder.create(request).end());
var stringRequest = xmlbuilder.create(request).end();
if (isHttpPostBinding && self.options.privateCert) {
stringRequest = signAuthnRequestPost(stringRequest, self.options);
}
callback(null, stringRequest);
})
.fail(function(err){
callback(err);
Expand Down Expand Up @@ -353,7 +349,7 @@ SAML.prototype.getAdditionalParams = function (req, operation) {

SAML.prototype.getAuthorizeUrl = function (req, callback) {
var self = this;
self.generateAuthorizeRequest(req, self.options.passive, function(err, request){
self.generateAuthorizeRequest(req, { isPassive: self.options.passive, isHttpPostBinding: false }, function(err, request) {
if (err)
return callback(err);
var operation = 'authorize';
Expand Down Expand Up @@ -421,7 +417,7 @@ SAML.prototype.getAuthorizeForm = function (req, callback) {
].join('\r\n'));
};

self.generateAuthorizeRequest(req, self.options.passive, function(err, request) {
self.generateAuthorizeRequest(req, { isPassive: self.options.passive, isHttpPostBinding: true }, function(err, request) {
if (err) {
return callback(err);
}
Expand Down Expand Up @@ -923,7 +919,7 @@ SAML.prototype.generateServiceProviderMetadata = function( decryptionCert ) {
{ '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' },
{ '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' }
]
}
};
}

if (this.options.logoutCallbackUrl) {
Expand Down
37 changes: 37 additions & 0 deletions test/saml-post-signing-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require('fs');
const should = require('should');
const samlPostSigning = require('../lib/passport-saml/saml-post-signing');
const signSamlPost = samlPostSigning.signSamlPost;
const signAuthnRequestPost = samlPostSigning.signAuthnRequestPost;

const signingKey = fs.readFileSync(__dirname + '/static/key.pem');

describe('SAML POST Signing', function () {
it('should sign a simple saml request', function () {
var xml = '<SAMLRequest/>';
var result = signSamlPost(xml, '/SAMLRequest', { privateCert: signingKey });
result.should.match(/<DigestValue>[A-Za-z0-9\/\+\=]+<\/DigestValue>/);
result.should.match(/<SignatureValue>[A-Za-z0-9\/\+\=]+<\/SignatureValue>/);
});

it('should sign and digest with SHA256 when specified', function () {
var xml = '<SAMLRequest/>';
var options = {
signatureAlgorithm: 'sha256',
digestAlgorithm: 'sha256',
privateCert: signingKey
}
var result = signSamlPost(xml, '/SAMLRequest', options);
result.should.match(/<SignatureMethod Algorithm="http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256"/);
result.should.match(/<Transform Algorithm="http:\/\/www.w3.org\/2001\/10\/xml-exc-c14n#"\/>/);
result.should.match(/<Transform Algorithm="http:\/\/www.w3.org\/2000\/09\/xmldsig#enveloped-signature"\/>/);
result.should.match(/<DigestMethod Algorithm="http:\/\/www.w3.org\/2001\/04\/xmlenc#sha256"\/>/);
});

it('should sign an AuthnRequest', function () {
var xml = '<AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:protocol" />';
var result = signAuthnRequestPost(xml, { privateCert: signingKey });
result.should.match(/<DigestValue>[A-Za-z0-9\/\+\=]+<\/DigestValue>/);
result.should.match(/<SignatureValue>[A-Za-z0-9\/\+\=]+<\/SignatureValue>/);
});
});
2 changes: 2 additions & 0 deletions test/tests.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 571214e

Please sign in to comment.