You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							253 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
	
	
							253 lines
						
					
					
						
							6.3 KiB
						
					
					
				| var bufferEqual = require('buffer-equal-constant-time');
 | |
| var Buffer = require('safe-buffer').Buffer;
 | |
| var crypto = require('crypto');
 | |
| var formatEcdsa = require('ecdsa-sig-formatter');
 | |
| var util = require('util');
 | |
| 
 | |
| 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".'
 | |
| var MSG_INVALID_SECRET = 'secret must be a string or buffer';
 | |
| var MSG_INVALID_VERIFIER_KEY = 'key must be a string or a buffer';
 | |
| var MSG_INVALID_SIGNER_KEY = 'key must be a string, a buffer or an object';
 | |
| 
 | |
| var supportsKeyObjects = typeof crypto.createPublicKey === 'function';
 | |
| if (supportsKeyObjects) {
 | |
|   MSG_INVALID_VERIFIER_KEY += ' or a KeyObject';
 | |
|   MSG_INVALID_SECRET += 'or a KeyObject';
 | |
| }
 | |
| 
 | |
| function checkIsPublicKey(key) {
 | |
|   if (Buffer.isBuffer(key)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (typeof key === 'string') {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!supportsKeyObjects) {
 | |
|     throw typeError(MSG_INVALID_VERIFIER_KEY);
 | |
|   }
 | |
| 
 | |
|   if (typeof key !== 'object') {
 | |
|     throw typeError(MSG_INVALID_VERIFIER_KEY);
 | |
|   }
 | |
| 
 | |
|   if (typeof key.type !== 'string') {
 | |
|     throw typeError(MSG_INVALID_VERIFIER_KEY);
 | |
|   }
 | |
| 
 | |
|   if (typeof key.asymmetricKeyType !== 'string') {
 | |
|     throw typeError(MSG_INVALID_VERIFIER_KEY);
 | |
|   }
 | |
| 
 | |
|   if (typeof key.export !== 'function') {
 | |
|     throw typeError(MSG_INVALID_VERIFIER_KEY);
 | |
|   }
 | |
| };
 | |
| 
 | |
| function checkIsPrivateKey(key) {
 | |
|   if (Buffer.isBuffer(key)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (typeof key === 'string') {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (typeof key === 'object') {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   throw typeError(MSG_INVALID_SIGNER_KEY);
 | |
| };
 | |
| 
 | |
| function checkIsSecretKey(key) {
 | |
|   if (Buffer.isBuffer(key)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (typeof key === 'string') {
 | |
|     return key;
 | |
|   }
 | |
| 
 | |
|   if (!supportsKeyObjects) {
 | |
|     throw typeError(MSG_INVALID_SECRET);
 | |
|   }
 | |
| 
 | |
|   if (typeof key !== 'object') {
 | |
|     throw typeError(MSG_INVALID_SECRET);
 | |
|   }
 | |
| 
 | |
|   if (key.type !== 'secret') {
 | |
|     throw typeError(MSG_INVALID_SECRET);
 | |
|   }
 | |
| 
 | |
|   if (typeof key.export !== 'function') {
 | |
|     throw typeError(MSG_INVALID_SECRET);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function fromBase64(base64) {
 | |
|   return base64
 | |
|     .replace(/=/g, '')
 | |
|     .replace(/\+/g, '-')
 | |
|     .replace(/\//g, '_');
 | |
| }
 | |
| 
 | |
| function toBase64(base64url) {
 | |
|   base64url = base64url.toString();
 | |
| 
 | |
|   var padding = 4 - base64url.length % 4;
 | |
|   if (padding !== 4) {
 | |
|     for (var i = 0; i < padding; ++i) {
 | |
|       base64url += '=';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return base64url
 | |
|     .replace(/\-/g, '+')
 | |
|     .replace(/_/g, '/');
 | |
| }
 | |
| 
 | |
| function typeError(template) {
 | |
|   var args = [].slice.call(arguments, 1);
 | |
|   var errMsg = util.format.bind(util, template).apply(null, args);
 | |
|   return new TypeError(errMsg);
 | |
| }
 | |
| 
 | |
| function bufferOrString(obj) {
 | |
|   return Buffer.isBuffer(obj) || typeof obj === 'string';
 | |
| }
 | |
| 
 | |
| function normalizeInput(thing) {
 | |
|   if (!bufferOrString(thing))
 | |
|     thing = JSON.stringify(thing);
 | |
|   return thing;
 | |
| }
 | |
| 
 | |
| function createHmacSigner(bits) {
 | |
|   return function sign(thing, secret) {
 | |
|     checkIsSecretKey(secret);
 | |
|     thing = normalizeInput(thing);
 | |
|     var hmac = crypto.createHmac('sha' + bits, secret);
 | |
|     var sig = (hmac.update(thing), hmac.digest('base64'))
 | |
|     return fromBase64(sig);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createHmacVerifier(bits) {
 | |
|   return function verify(thing, signature, secret) {
 | |
|     var computedSig = createHmacSigner(bits)(thing, secret);
 | |
|     return bufferEqual(Buffer.from(signature), Buffer.from(computedSig));
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createKeySigner(bits) {
 | |
|  return function sign(thing, privateKey) {
 | |
|     checkIsPrivateKey(privateKey);
 | |
|     thing = normalizeInput(thing);
 | |
|     // Even though we are specifying "RSA" here, this works with ECDSA
 | |
|     // keys as well.
 | |
|     var signer = crypto.createSign('RSA-SHA' + bits);
 | |
|     var sig = (signer.update(thing), signer.sign(privateKey, 'base64'));
 | |
|     return fromBase64(sig);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createKeyVerifier(bits) {
 | |
|   return function verify(thing, signature, publicKey) {
 | |
|     checkIsPublicKey(publicKey);
 | |
|     thing = normalizeInput(thing);
 | |
|     signature = toBase64(signature);
 | |
|     var verifier = crypto.createVerify('RSA-SHA' + bits);
 | |
|     verifier.update(thing);
 | |
|     return verifier.verify(publicKey, signature, 'base64');
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createPSSKeySigner(bits) {
 | |
|   return function sign(thing, privateKey) {
 | |
|     checkIsPrivateKey(privateKey);
 | |
|     thing = normalizeInput(thing);
 | |
|     var signer = crypto.createSign('RSA-SHA' + bits);
 | |
|     var sig = (signer.update(thing), signer.sign({
 | |
|       key: privateKey,
 | |
|       padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
 | |
|       saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
 | |
|     }, 'base64'));
 | |
|     return fromBase64(sig);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createPSSKeyVerifier(bits) {
 | |
|   return function verify(thing, signature, publicKey) {
 | |
|     checkIsPublicKey(publicKey);
 | |
|     thing = normalizeInput(thing);
 | |
|     signature = toBase64(signature);
 | |
|     var verifier = crypto.createVerify('RSA-SHA' + bits);
 | |
|     verifier.update(thing);
 | |
|     return verifier.verify({
 | |
|       key: publicKey,
 | |
|       padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
 | |
|       saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
 | |
|     }, signature, 'base64');
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createECDSASigner(bits) {
 | |
|   var inner = createKeySigner(bits);
 | |
|   return function sign() {
 | |
|     var signature = inner.apply(null, arguments);
 | |
|     signature = formatEcdsa.derToJose(signature, 'ES' + bits);
 | |
|     return signature;
 | |
|   };
 | |
| }
 | |
| 
 | |
| function createECDSAVerifer(bits) {
 | |
|   var inner = createKeyVerifier(bits);
 | |
|   return function verify(thing, signature, publicKey) {
 | |
|     signature = formatEcdsa.joseToDer(signature, 'ES' + bits).toString('base64');
 | |
|     var result = inner(thing, signature, publicKey);
 | |
|     return result;
 | |
|   };
 | |
| }
 | |
| 
 | |
| function createNoneSigner() {
 | |
|   return function sign() {
 | |
|     return '';
 | |
|   }
 | |
| }
 | |
| 
 | |
| function createNoneVerifier() {
 | |
|   return function verify(thing, signature) {
 | |
|     return signature === '';
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = function jwa(algorithm) {
 | |
|   var signerFactories = {
 | |
|     hs: createHmacSigner,
 | |
|     rs: createKeySigner,
 | |
|     ps: createPSSKeySigner,
 | |
|     es: createECDSASigner,
 | |
|     none: createNoneSigner,
 | |
|   }
 | |
|   var verifierFactories = {
 | |
|     hs: createHmacVerifier,
 | |
|     rs: createKeyVerifier,
 | |
|     ps: createPSSKeyVerifier,
 | |
|     es: createECDSAVerifer,
 | |
|     none: createNoneVerifier,
 | |
|   }
 | |
|   var match = algorithm.match(/^(RS|PS|ES|HS)(256|384|512)$|^(none)$/i);
 | |
|   if (!match)
 | |
|     throw typeError(MSG_INVALID_ALGORITHM, algorithm);
 | |
|   var algo = (match[1] || match[3]).toLowerCase();
 | |
|   var bits = match[2];
 | |
| 
 | |
|   return {
 | |
|     sign: signerFactories[algo](bits),
 | |
|     verify: verifierFactories[algo](bits),
 | |
|   }
 | |
| };
 |