/*
* @copyright
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http: *www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
*/
'use strict';
var jwtConstants = require('./constants').Jwt;
var Logger = require('./log').Logger;
var util = require('./util');
require('date-utils');
var jws = require('jws');
var uuid = require('node-uuid');
/**
* JavaScript dates are in milliseconds, but JWT dates are in seconds.
* This function does the conversion.
* @param {Date} date
* @return {string}
*/
function dateGetTimeInSeconds(date) {
return Math.floor(date.getTime()/1000);
}
/**
* Constructs a new SelfSignedJwt object.
* @param {object} callContext Context specific to this token request.
* @param {Authority} authority The authority to be used as the JWT audience.
* @param {string} clientId The client id of the calling app.
*/
function SelfSignedJwt(callContext, authority, clientId) {
this._log = new Logger('SelfSignedJwt', callContext._logContext);
this._callContext = callContext;
this._authority = authority;
this._tokenEndpoint = authority.tokenEndpoint;
this._clientId = clientId;
}
/**
* This wraps date creation in order to make unit testing easier.
* @return {Date}
*/
SelfSignedJwt.prototype._getDateNow = function() {
return new Date();
};
SelfSignedJwt.prototype._getNewJwtId = function() {
return uuid.v4();
};
/**
* A regular certificate thumbprint is a hex encode string of the binary certificate
* hash. For some reason teh x5t value in a JWT is a url save base64 encoded string
* instead. This function does the conversion.
* @param {string} thumbprint A hex encoded certificate thumbprint.
* @return {string} A url safe base64 encoded certificate thumbprint.
*/
SelfSignedJwt.prototype._createx5tValue = function(thumbprint) {
var hexString = thumbprint.replace(/:/g, '').replace(/ /g, '');
var base64 = (new Buffer(hexString, 'hex')).toString('base64');
return util.convertRegularToUrlSafeBase64EncodedString(base64);
};
/**
* Creates the JWT header.
* @param {string} thumbprint A hex encoded certificate thumbprint.
* @return {object}
*/
SelfSignedJwt.prototype._createHeader = function(thumbprint) {
var x5t = this._createx5tValue(thumbprint);
var header = { typ: 'JWT', alg: 'RS256', x5t : x5t };
this._log.verbose('Creating self signed JWT header. x5t: ' + x5t);
return header;
};
/**
* Creates the JWT payload.
* @return {object}
*/
SelfSignedJwt.prototype._createPayload = function() {
var now = this._getDateNow();
var expires = (new Date(now.getTime())).addMinutes(jwtConstants.SELF_SIGNED_JWT_LIFETIME);
this._log.verbose('Creating self signed JWT payload. Expires: ' + expires + ' NotBefore: ' + now);
var jwtPayload = {};
jwtPayload[jwtConstants.AUDIENCE] = this._tokenEndpoint;
jwtPayload[jwtConstants.ISSUER] = this._clientId;
jwtPayload[jwtConstants.SUBJECT] = this._clientId;
jwtPayload[jwtConstants.NOT_BEFORE] = dateGetTimeInSeconds(now);
jwtPayload[jwtConstants.EXPIRES_ON] = dateGetTimeInSeconds(expires);
jwtPayload[jwtConstants.JWT_ID] = this._getNewJwtId();
return jwtPayload;
};
SelfSignedJwt.prototype._throwOnInvalidJwtSignature = function(jwt) {
var jwtSegments = jwt.split('.');
Iif (3 > jwtSegments.length || !jwtSegments[2]) {
throw this._log.createError('Failed to sign JWT. This is most likely due to an invalid certificate.');
}
return;
};
SelfSignedJwt.prototype._signJwt = function(header, payload, certificate) {
var jwt = jws.sign({ header : header, payload : payload, secret : certificate});
this._throwOnInvalidJwtSignature(jwt);
return jwt;
};
SelfSignedJwt.prototype._reduceThumbprint = function(thumbprint) {
var canonical = thumbprint.toLowerCase().replace(/ /g, '').replace(/:/g, '');
this._throwOnInvalidThumbprint(canonical);
return canonical;
};
var numCharIn128BitHexString = 128/8*2;
var numCharIn160BitHexString = 160/8*2;
var thumbprintSizes = {};
thumbprintSizes[numCharIn128BitHexString] = true;
thumbprintSizes[numCharIn160BitHexString] = true;
var thumbprintRegExp = /^[a-f\d]*$/;
SelfSignedJwt.prototype._throwOnInvalidThumbprint = function(thumbprint) {
if (!thumbprintSizes[thumbprint.length] || !thumbprintRegExp.test(thumbprint)) {
throw this._log.createError('The thumbprint does not match a known format');
}
};
/**
* Creates a self signed JWT that can be used as a client_assertion.
* @param {string} certificate A PEM encoded certificate private key.
* @param {string} thumbprint A hex encoded thumbprint of the certificate.
* @return {string} A self signed JWT token.
*/
SelfSignedJwt.prototype.create = function(certificate, thumbprint) {
thumbprint = this._reduceThumbprint(thumbprint);
var header = this._createHeader(thumbprint);
var payload = this._createPayload();
var jwt = this._signJwt(header, payload, certificate);
return jwt;
};
module.exports = SelfSignedJwt; |