/* * @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('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'); this._log.verbose('Creating self signed JWT header. x5t: ' + x5t, true); 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('.'); if (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; try { jwt = jws.sign({ header : header, payload : payload, secret : certificate }); } catch (err) { this._log.error(err, true); throw this._log.createError('Failed to sign JWT.This is most likely due to an invalid 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;