self-signed-jwt.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. * @copyright
  3. * Copyright © Microsoft Open Technologies, Inc.
  4. *
  5. * All Rights Reserved
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http: *www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
  14. * OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
  15. * ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
  16. * PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
  17. *
  18. * See the Apache License, Version 2.0 for the specific language
  19. * governing permissions and limitations under the License.
  20. */
  21. 'use strict';
  22. var jwtConstants = require('./constants').Jwt;
  23. var Logger = require('./log').Logger;
  24. var util = require('./util');
  25. require('date-utils');
  26. var jws = require('jws');
  27. var uuid = require('uuid');
  28. /**
  29. * JavaScript dates are in milliseconds, but JWT dates are in seconds.
  30. * This function does the conversion.
  31. * @param {Date} date
  32. * @return {string}
  33. */
  34. function dateGetTimeInSeconds(date) {
  35. return Math.floor(date.getTime()/1000);
  36. }
  37. /**
  38. * Constructs a new SelfSignedJwt object.
  39. * @param {object} callContext Context specific to this token request.
  40. * @param {Authority} authority The authority to be used as the JWT audience.
  41. * @param {string} clientId The client id of the calling app.
  42. */
  43. function SelfSignedJwt(callContext, authority, clientId) {
  44. this._log = new Logger('SelfSignedJwt', callContext._logContext);
  45. this._callContext = callContext;
  46. this._authority = authority;
  47. this._tokenEndpoint = authority.tokenEndpoint;
  48. this._clientId = clientId;
  49. }
  50. /**
  51. * This wraps date creation in order to make unit testing easier.
  52. * @return {Date}
  53. */
  54. SelfSignedJwt.prototype._getDateNow = function() {
  55. return new Date();
  56. };
  57. SelfSignedJwt.prototype._getNewJwtId = function() {
  58. return uuid.v4();
  59. };
  60. /**
  61. * A regular certificate thumbprint is a hex encode string of the binary certificate
  62. * hash. For some reason teh x5t value in a JWT is a url save base64 encoded string
  63. * instead. This function does the conversion.
  64. * @param {string} thumbprint A hex encoded certificate thumbprint.
  65. * @return {string} A url safe base64 encoded certificate thumbprint.
  66. */
  67. SelfSignedJwt.prototype._createx5tValue = function(thumbprint) {
  68. var hexString = thumbprint.replace(/:/g, '').replace(/ /g, '');
  69. var base64 = (new Buffer(hexString, 'hex')).toString('base64');
  70. return util.convertRegularToUrlSafeBase64EncodedString(base64);
  71. };
  72. /**
  73. * Creates the JWT header.
  74. * @param {string} thumbprint A hex encoded certificate thumbprint.
  75. * @return {object}
  76. */
  77. SelfSignedJwt.prototype._createHeader = function(thumbprint) {
  78. var x5t = this._createx5tValue(thumbprint);
  79. var header = { typ: 'JWT', alg: 'RS256', x5t : x5t };
  80. this._log.verbose('Creating self signed JWT header');
  81. this._log.verbose('Creating self signed JWT header. x5t: ' + x5t, true);
  82. return header;
  83. };
  84. /**
  85. * Creates the JWT payload.
  86. * @return {object}
  87. */
  88. SelfSignedJwt.prototype._createPayload = function() {
  89. var now = this._getDateNow();
  90. var expires = (new Date(now.getTime())).addMinutes(jwtConstants.SELF_SIGNED_JWT_LIFETIME);
  91. this._log.verbose('Creating self signed JWT payload. Expires: ' + expires + ' NotBefore: ' + now);
  92. var jwtPayload = {};
  93. jwtPayload[jwtConstants.AUDIENCE] = this._tokenEndpoint;
  94. jwtPayload[jwtConstants.ISSUER] = this._clientId;
  95. jwtPayload[jwtConstants.SUBJECT] = this._clientId;
  96. jwtPayload[jwtConstants.NOT_BEFORE] = dateGetTimeInSeconds(now);
  97. jwtPayload[jwtConstants.EXPIRES_ON] = dateGetTimeInSeconds(expires);
  98. jwtPayload[jwtConstants.JWT_ID] = this._getNewJwtId();
  99. return jwtPayload;
  100. };
  101. SelfSignedJwt.prototype._throwOnInvalidJwtSignature = function(jwt) {
  102. var jwtSegments = jwt.split('.');
  103. if (3 > jwtSegments.length || !jwtSegments[2]) {
  104. throw this._log.createError('Failed to sign JWT. This is most likely due to an invalid certificate.');
  105. }
  106. return;
  107. };
  108. SelfSignedJwt.prototype._signJwt = function(header, payload, certificate) {
  109. var jwt;
  110. try {
  111. jwt = jws.sign({ header : header, payload : payload, secret : certificate });
  112. }
  113. catch (err) {
  114. this._log.error(err, true);
  115. throw this._log.createError('Failed to sign JWT.This is most likely due to an invalid certificate.');
  116. }
  117. this._throwOnInvalidJwtSignature(jwt);
  118. return jwt;
  119. };
  120. SelfSignedJwt.prototype._reduceThumbprint = function(thumbprint) {
  121. var canonical = thumbprint.toLowerCase().replace(/ /g, '').replace(/:/g, '');
  122. this._throwOnInvalidThumbprint(canonical);
  123. return canonical;
  124. };
  125. var numCharIn128BitHexString = 128/8*2;
  126. var numCharIn160BitHexString = 160/8*2;
  127. var thumbprintSizes = {};
  128. thumbprintSizes[numCharIn128BitHexString] = true;
  129. thumbprintSizes[numCharIn160BitHexString] = true;
  130. var thumbprintRegExp = /^[a-f\d]*$/;
  131. SelfSignedJwt.prototype._throwOnInvalidThumbprint = function(thumbprint) {
  132. if (!thumbprintSizes[thumbprint.length] || !thumbprintRegExp.test(thumbprint)) {
  133. throw this._log.createError('The thumbprint does not match a known format');
  134. }
  135. };
  136. /**
  137. * Creates a self signed JWT that can be used as a client_assertion.
  138. * @param {string} certificate A PEM encoded certificate private key.
  139. * @param {string} thumbprint A hex encoded thumbprint of the certificate.
  140. * @return {string} A self signed JWT token.
  141. */
  142. SelfSignedJwt.prototype.create = function(certificate, thumbprint) {
  143. thumbprint = this._reduceThumbprint(thumbprint);
  144. var header = this._createHeader(thumbprint);
  145. var payload = this._createPayload();
  146. var jwt = this._signJwt(header, payload, certificate);
  147. return jwt;
  148. };
  149. module.exports = SelfSignedJwt;