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