diff --git a/lib/saml11.js b/lib/saml11.js index f359aa54..bf9d8a04 100644 --- a/lib/saml11.js +++ b/lib/saml11.js @@ -42,7 +42,7 @@ exports.create = function(options, callback) { algorithms.digest[options.digestAlgorithm]); sig.signingKey = options.key; - + sig.keyInfoProvider = { getKeyInfo: function () { return "" + cert + ""; @@ -68,7 +68,7 @@ exports.create = function(options, callback) { conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')); conditions[0].setAttribute('NotOnOrAfter', now.add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')); } - + if (options.audiences) { var audiences = options.audiences instanceof Array ? options.audiences : [options.audiences]; audiences.forEach(function (audience) { @@ -83,7 +83,7 @@ exports.create = function(options, callback) { var statement = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'AttributeStatement')[0]; Object.keys(options.attributes).forEach(function(prop) { if(typeof options.attributes[prop] === 'undefined') return; - + // // Foo Bar // @@ -109,16 +109,21 @@ exports.create = function(options, callback) { doc.getElementsByTagName('saml:AuthenticationStatement')[0] .setAttribute('AuthenticationInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]')); + if (options.unspecifiedAuthenticationMethod) { + doc.getElementsByTagName('saml:AuthenticationStatement')[0] + .setAttribute('AuthenticationMethod', 'urn:oasis:names:tc:SAML:1.0:am:unspecified'); + } + var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameIdentifier')[0]; - + if (options.nameIdentifier) { nameID.textContent = options.nameIdentifier; - + doc.getElementsByTagName('saml:AuthenticationStatement')[0] .getElementsByTagName('saml:NameIdentifier')[0] .textContent = options.nameIdentifier; } - + if (options.nameIdentifierFormat) { var nameIDs = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameIdentifier'); nameIDs[0].setAttribute('Format', options.nameIdentifierFormat); @@ -127,18 +132,18 @@ exports.create = function(options, callback) { if (!options.encryptionCert) return sign(options, sig, doc, callback); - // encryption is turned on, + // encryption is turned on, var proofSecret; async.waterfall([ function(cb) { - if (!options.subjectConfirmationMethod && options.subjectConfirmationMethod !== 'holder-of-key') + if (!options.subjectConfirmationMethod && options.subjectConfirmationMethod !== 'holder-of-key') return cb(); - + crypto.randomBytes(32, function(err, randomBytes) { proofSecret = randomBytes; addSubjectConfirmation(options, doc, options.holderOfKeyProofSecret || randomBytes, cb); }); - + }, function(cb) { sign(options, sig, doc, function(err, signed) { @@ -150,7 +155,7 @@ exports.create = function(options, callback) { if (err) return callback(err); callback(null, result, proofSecret); }); -}; +}; function addSubjectConfirmation(options, doc, randomBytes, callback) { var encryptOptions = { @@ -159,7 +164,7 @@ function addSubjectConfirmation(options, doc, randomBytes, callback) { keyEncryptionAlgorighm: options.keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' }; - xmlenc.encryptKeyInfo(randomBytes, encryptOptions, function(err, keyinfo) { + xmlenc.encryptKeyInfo(randomBytes, encryptOptions, function(err, keyinfo) { if (err) return cb(err); var subjectConfirmationNodes = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'SubjectConfirmation'); @@ -185,9 +190,9 @@ function sign(options, sig, doc, callback) { var signed; try { - var opts = options.xpathToNodeBeforeSignature ? { - location: { - reference: options.xpathToNodeBeforeSignature, + var opts = options.xpathToNodeBeforeSignature ? { + location: { + reference: options.xpathToNodeBeforeSignature, action: 'after' } } : {}; diff --git a/test/saml11.tests.js b/test/saml11.tests.js index d924c278..0f4157fe 100644 --- a/test/saml11.tests.js +++ b/test/saml11.tests.js @@ -125,7 +125,7 @@ describe('saml 1.1', function () { var isValid = utils.isValidSignature(signedAssertion, options.cert); assert.equal(true, isValid); - + var attributes = utils.getAttributes(signedAssertion); assert.equal(3, attributes.length); assert.equal('emailaddress', attributes[0].getAttribute('AttributeName')); @@ -192,6 +192,18 @@ describe('saml 1.1', function () { assert.ok(!!authenticationStatement.getAttribute('AuthenticationInstant')); }); + it('should set unspecified AuthenticationMethod attribute', function () { + var options = { + cert: fs.readFileSync(__dirname + '/test-auth0.pem'), + key: fs.readFileSync(__dirname + '/test-auth0.key'), + unspecifiedAuthenticationMethod: true + }; + + var signedAssertion = saml11.create(options); + var authenticationStatement = utils.getAuthenticationStatement(signedAssertion); + assert.equal('urn:oasis:names:tc:SAML:1.0:am:unspecified', authenticationStatement.getAttribute('AuthenticationMethod')); + }); + it('should set AuthenticationStatement NameIdentifier', function () { var options = { cert: fs.readFileSync(__dirname + '/test-auth0.pem'), @@ -253,7 +265,7 @@ describe('saml 1.1', function () { }; var signedAssertion = saml11.create(options); var doc = new xmldom.DOMParser().parseFromString(signedAssertion); - + var signature = doc.documentElement.getElementsByTagName('Signature'); assert.equal('saml:Conditions', signature[0].previousSibling.nodeName); @@ -317,7 +329,7 @@ describe('saml 1.1', function () { saml11.create(options, function(err, encrypted) { if (err) return done(err); - + xmlenc.decrypt(encrypted, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) { if (err) return done(err); var isValid = utils.isValidSignature(decrypted, options.cert); @@ -338,10 +350,10 @@ describe('saml 1.1', function () { saml11.create(options, function(err, encrypted, proofSecret) { if (err) return done(err); - + xmlenc.decrypt(encrypted, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) { if (err) return done(err); - + var doc = new xmldom.DOMParser().parseFromString(decrypted); var subjectConfirmationNodes = doc.documentElement.getElementsByTagName('saml:SubjectConfirmation'); assert.equal(2, subjectConfirmationNodes.length); @@ -374,13 +386,13 @@ describe('saml 1.1', function () { saml11.create(options, function(err, encrypted) { if (err) return done(err); - + xmlenc.decrypt(encrypted, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) { if (err) return done(err); var isValid = utils.isValidSignature(decrypted, options.cert); assert.equal(true, isValid); - + var attributes = utils.getAttributes(decrypted); assert.equal(3, attributes.length); assert.equal('emailaddress', attributes[0].getAttribute('AttributeName')); @@ -392,7 +404,7 @@ describe('saml 1.1', function () { assert.equal('testaccent', attributes[2].getAttribute('AttributeName')); assert.equal('http://example.org/claims', attributes[2].getAttribute('AttributeNamespace')); assert.equal('fóo', attributes[2].firstChild.textContent); - + done(); }); });