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
			| 
											3 years ago
										 | 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), | ||
|  |   } | ||
|  | }; |