Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to back up the issue instant to account for clock skew #43

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
Create SAML assertions.

NOTE: currently supports SAML 1.1 tokens
NOTE: currently supports SAML 1.1 and SAML 2.0 tokens

[![Build Status](https://travis-ci.org/auth0/node-saml.png)](https://travis-ci.org/auth0/node-saml)

### Usage

```js
var saml11 = require('saml').Saml11;

var options = {
// Required
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
key: fs.readFileSync(__dirname + '/test-auth0.key'),
// Optional
issuer: 'urn:issuer',
issueInstantSkewInSeconds: 60,
lifetimeInSeconds: 600,
audiences: 'urn:myapp',
attributes: {
Expand All @@ -23,10 +25,15 @@ var options = {
sessionIndex: '_faed468a-15a0-4668-aed6-3d9c478cc8fa'
};

// SAML 1.1
var saml11 = require('saml').Saml11;
var signedAssertion = saml11.create(options);
```

Everything except the cert and key is optional.
// SAML 2.0
var saml20 = require('saml').Saml20;
var signedAssertion = saml20.create(options);

```

## Issue Reporting

Expand Down
15 changes: 11 additions & 4 deletions lib/saml11.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var path = require('path');
var saml11 = fs.readFileSync(path.join(__dirname, 'saml11.template')).toString();

var NAMESPACE = 'urn:oasis:names:tc:SAML:1.0:assertion';
var TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSS[Z]';

var algorithms = {
signature: {
Expand Down Expand Up @@ -61,12 +62,18 @@ exports.create = function(options, callback) {
doc.documentElement.setAttribute('Issuer', options.issuer);

var now = moment.utc();
doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

// Optionally, back up the issue instant to accommodate for clock skew in the assertion consumer
if (!isNaN(options.issueInstantSkewInSeconds)) {
now.subtract(options.issueInstantSkewInSeconds, 'seconds');
}

doc.documentElement.setAttribute('IssueInstant', now.format(TIME_FORMAT));
var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');

if (options.lifetimeInSeconds) {
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]'));
conditions[0].setAttribute('NotBefore', now.format(TIME_FORMAT));
conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format(TIME_FORMAT));
}

if (options.audiences) {
Expand Down Expand Up @@ -107,7 +114,7 @@ exports.create = function(options, callback) {
}

doc.getElementsByTagName('saml:AuthenticationStatement')[0]
.setAttribute('AuthenticationInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
.setAttribute('AuthenticationInstant', now.format(TIME_FORMAT));

var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameIdentifier')[0];

Expand Down
17 changes: 12 additions & 5 deletions lib/saml20.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var path = require('path');
var saml20 = fs.readFileSync(path.join(__dirname, 'saml20.template')).toString();

var NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion';
var TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSS[Z]';

var algorithms = {
signature: {
Expand Down Expand Up @@ -102,15 +103,21 @@ exports.create = function(options, callback) {
}

var now = moment.utc();
doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

// Optionally, back up the issue instant to accommodate for clock skew in the assertion consumer
if (!isNaN(options.issueInstantSkewInSeconds)) {
now.subtract(options.issueInstantSkewInSeconds, 'seconds');
}

doc.documentElement.setAttribute('IssueInstant', now.format(TIME_FORMAT));
var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');
var confirmationData = doc.documentElement.getElementsByTagName('saml:SubjectConfirmationData');

if (options.lifetimeInSeconds) {
conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
conditions[0].setAttribute('NotBefore', now.format(TIME_FORMAT));
conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format(TIME_FORMAT));

confirmationData[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
confirmationData[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format(TIME_FORMAT));
}

if (options.audiences) {
Expand Down Expand Up @@ -168,7 +175,7 @@ exports.create = function(options, callback) {
}

doc.getElementsByTagName('saml:AuthnStatement')[0]
.setAttribute('AuthnInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
.setAttribute('AuthnInstant', now.format(TIME_FORMAT));

if (options.sessionIndex) {
doc.getElementsByTagName('saml:AuthnStatement')[0]
Expand Down
28 changes: 28 additions & 0 deletions test/saml11.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,34 @@ describe('saml 1.1', function () {
assert.equal(600, lifetime);
});

it('should skew the issue instant if requested', function () {

var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
key: fs.readFileSync(__dirname + '/test-auth0.key'),
lifetimeInSeconds: 600,
issueInstantSkewInSeconds: 60,
};

var signedAssertion = saml11.create(options);
var isValid = utils.isValidSignature(signedAssertion, options.cert);
assert.equal(true, isValid);

var conditions = utils.getConditions(signedAssertion);
assert.equal(1, conditions.length);
var notBefore = conditions[0].getAttribute('NotBefore');
var notOnOrAfter = conditions[0].getAttribute('NotOnOrAfter');

should.ok(notBefore);
should.ok(notOnOrAfter);

var skew = Math.round((moment.utc() - moment(notBefore).utc()) / 1000);
assert.equal(60, skew);

var lifetime = Math.round((moment(notOnOrAfter).utc() - moment(notBefore).utc()) / 1000);
assert.equal(600, lifetime);
});

it('should set audience restriction', function () {
var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
Expand Down
28 changes: 28 additions & 0 deletions test/saml20.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,34 @@ describe('saml 2.0', function () {
assert.equal('specific', authnContextClassRef.textContent);
});

it('should skew the issue instant if requested', function () {

var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
key: fs.readFileSync(__dirname + '/test-auth0.key'),
lifetimeInSeconds: 600,
issueInstantSkewInSeconds: 60,
};

var signedAssertion = saml.create(options);
var isValid = utils.isValidSignature(signedAssertion, options.cert);
assert.equal(true, isValid);

var conditions = utils.getConditions(signedAssertion);
assert.equal(1, conditions.length);
var notBefore = conditions[0].getAttribute('NotBefore');
var notOnOrAfter = conditions[0].getAttribute('NotOnOrAfter');

should.ok(notBefore);
should.ok(notOnOrAfter);

var skew = Math.round((moment.utc() - moment(notBefore).utc()) / 1000);
assert.equal(60, skew);

var lifetime = Math.round((moment(notOnOrAfter).utc() - moment(notBefore).utc()) / 1000);
assert.equal(600, lifetime);
});

it('should place signature where specified', function () {
var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
Expand Down