index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. var Buffer = require('safe-buffer').Buffer;
  2. var crypto = require('crypto');
  3. var formatEcdsa = require('ecdsa-sig-formatter');
  4. var util = require('util');
  5. var MSG_INVALID_ALGORITHM = '"%s" is not a valid algorithm.\n Supported algorithms are:\n "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512" and "none".'
  6. var MSG_INVALID_SECRET = 'secret must be a string or buffer';
  7. var MSG_INVALID_VERIFIER_KEY = 'key must be a string or a buffer';
  8. var MSG_INVALID_SIGNER_KEY = 'key must be a string, a buffer or an object';
  9. var supportsKeyObjects = typeof crypto.createPublicKey === 'function';
  10. if (supportsKeyObjects) {
  11. MSG_INVALID_VERIFIER_KEY += ' or a KeyObject';
  12. MSG_INVALID_SECRET += 'or a KeyObject';
  13. }
  14. function checkIsPublicKey(key) {
  15. if (Buffer.isBuffer(key)) {
  16. return;
  17. }
  18. if (typeof key === 'string') {
  19. return;
  20. }
  21. if (!supportsKeyObjects) {
  22. throw typeError(MSG_INVALID_VERIFIER_KEY);
  23. }
  24. if (typeof key !== 'object') {
  25. throw typeError(MSG_INVALID_VERIFIER_KEY);
  26. }
  27. if (typeof key.type !== 'string') {
  28. throw typeError(MSG_INVALID_VERIFIER_KEY);
  29. }
  30. if (typeof key.asymmetricKeyType !== 'string') {
  31. throw typeError(MSG_INVALID_VERIFIER_KEY);
  32. }
  33. if (typeof key.export !== 'function') {
  34. throw typeError(MSG_INVALID_VERIFIER_KEY);
  35. }
  36. };
  37. function checkIsPrivateKey(key) {
  38. if (Buffer.isBuffer(key)) {
  39. return;
  40. }
  41. if (typeof key === 'string') {
  42. return;
  43. }
  44. if (typeof key === 'object') {
  45. return;
  46. }
  47. throw typeError(MSG_INVALID_SIGNER_KEY);
  48. };
  49. function checkIsSecretKey(key) {
  50. if (Buffer.isBuffer(key)) {
  51. return;
  52. }
  53. if (typeof key === 'string') {
  54. return key;
  55. }
  56. if (!supportsKeyObjects) {
  57. throw typeError(MSG_INVALID_SECRET);
  58. }
  59. if (typeof key !== 'object') {
  60. throw typeError(MSG_INVALID_SECRET);
  61. }
  62. if (key.type !== 'secret') {
  63. throw typeError(MSG_INVALID_SECRET);
  64. }
  65. if (typeof key.export !== 'function') {
  66. throw typeError(MSG_INVALID_SECRET);
  67. }
  68. }
  69. function fromBase64(base64) {
  70. return base64
  71. .replace(/=/g, '')
  72. .replace(/\+/g, '-')
  73. .replace(/\//g, '_');
  74. }
  75. function toBase64(base64url) {
  76. base64url = base64url.toString();
  77. var padding = 4 - base64url.length % 4;
  78. if (padding !== 4) {
  79. for (var i = 0; i < padding; ++i) {
  80. base64url += '=';
  81. }
  82. }
  83. return base64url
  84. .replace(/\-/g, '+')
  85. .replace(/_/g, '/');
  86. }
  87. function typeError(template) {
  88. var args = [].slice.call(arguments, 1);
  89. var errMsg = util.format.bind(util, template).apply(null, args);
  90. return new TypeError(errMsg);
  91. }
  92. function bufferOrString(obj) {
  93. return Buffer.isBuffer(obj) || typeof obj === 'string';
  94. }
  95. function normalizeInput(thing) {
  96. if (!bufferOrString(thing))
  97. thing = JSON.stringify(thing);
  98. return thing;
  99. }
  100. function createHmacSigner(bits) {
  101. return function sign(thing, secret) {
  102. checkIsSecretKey(secret);
  103. thing = normalizeInput(thing);
  104. var hmac = crypto.createHmac('sha' + bits, secret);
  105. var sig = (hmac.update(thing), hmac.digest('base64'))
  106. return fromBase64(sig);
  107. }
  108. }
  109. var bufferEqual;
  110. var timingSafeEqual = 'timingSafeEqual' in crypto ? function timingSafeEqual(a, b) {
  111. if (a.byteLength !== b.byteLength) {
  112. return false;
  113. }
  114. return crypto.timingSafeEqual(a, b)
  115. } : function timingSafeEqual(a, b) {
  116. if (!bufferEqual) {
  117. bufferEqual = require('buffer-equal-constant-time');
  118. }
  119. return bufferEqual(a, b)
  120. }
  121. function createHmacVerifier(bits) {
  122. return function verify(thing, signature, secret) {
  123. var computedSig = createHmacSigner(bits)(thing, secret);
  124. return timingSafeEqual(Buffer.from(signature), Buffer.from(computedSig));
  125. }
  126. }
  127. function createKeySigner(bits) {
  128. return function sign(thing, privateKey) {
  129. checkIsPrivateKey(privateKey);
  130. thing = normalizeInput(thing);
  131. // Even though we are specifying "RSA" here, this works with ECDSA
  132. // keys as well.
  133. var signer = crypto.createSign('RSA-SHA' + bits);
  134. var sig = (signer.update(thing), signer.sign(privateKey, 'base64'));
  135. return fromBase64(sig);
  136. }
  137. }
  138. function createKeyVerifier(bits) {
  139. return function verify(thing, signature, publicKey) {
  140. checkIsPublicKey(publicKey);
  141. thing = normalizeInput(thing);
  142. signature = toBase64(signature);
  143. var verifier = crypto.createVerify('RSA-SHA' + bits);
  144. verifier.update(thing);
  145. return verifier.verify(publicKey, signature, 'base64');
  146. }
  147. }
  148. function createPSSKeySigner(bits) {
  149. return function sign(thing, privateKey) {
  150. checkIsPrivateKey(privateKey);
  151. thing = normalizeInput(thing);
  152. var signer = crypto.createSign('RSA-SHA' + bits);
  153. var sig = (signer.update(thing), signer.sign({
  154. key: privateKey,
  155. padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  156. saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
  157. }, 'base64'));
  158. return fromBase64(sig);
  159. }
  160. }
  161. function createPSSKeyVerifier(bits) {
  162. return function verify(thing, signature, publicKey) {
  163. checkIsPublicKey(publicKey);
  164. thing = normalizeInput(thing);
  165. signature = toBase64(signature);
  166. var verifier = crypto.createVerify('RSA-SHA' + bits);
  167. verifier.update(thing);
  168. return verifier.verify({
  169. key: publicKey,
  170. padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
  171. saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
  172. }, signature, 'base64');
  173. }
  174. }
  175. function createECDSASigner(bits) {
  176. var inner = createKeySigner(bits);
  177. return function sign() {
  178. var signature = inner.apply(null, arguments);
  179. signature = formatEcdsa.derToJose(signature, 'ES' + bits);
  180. return signature;
  181. };
  182. }
  183. function createECDSAVerifer(bits) {
  184. var inner = createKeyVerifier(bits);
  185. return function verify(thing, signature, publicKey) {
  186. signature = formatEcdsa.joseToDer(signature, 'ES' + bits).toString('base64');
  187. var result = inner(thing, signature, publicKey);
  188. return result;
  189. };
  190. }
  191. function createNoneSigner() {
  192. return function sign() {
  193. return '';
  194. }
  195. }
  196. function createNoneVerifier() {
  197. return function verify(thing, signature) {
  198. return signature === '';
  199. }
  200. }
  201. module.exports = function jwa(algorithm) {
  202. var signerFactories = {
  203. hs: createHmacSigner,
  204. rs: createKeySigner,
  205. ps: createPSSKeySigner,
  206. es: createECDSASigner,
  207. none: createNoneSigner,
  208. }
  209. var verifierFactories = {
  210. hs: createHmacVerifier,
  211. rs: createKeyVerifier,
  212. ps: createPSSKeyVerifier,
  213. es: createECDSAVerifer,
  214. none: createNoneVerifier,
  215. }
  216. var match = algorithm.match(/^(RS|PS|ES|HS)(256|384|512)$|^(none)$/i);
  217. if (!match)
  218. throw typeError(MSG_INVALID_ALGORITHM, algorithm);
  219. var algo = (match[1] || match[3]).toLowerCase();
  220. var bits = match[2];
  221. return {
  222. sign: signerFactories[algo](bits),
  223. verify: verifierFactories[algo](bits),
  224. }
  225. };