1'use strict'; 2 3const { 4 ArrayFrom, 5 SafeSet, 6} = primordials; 7 8const { 9 HmacJob, 10 KeyObjectHandle, 11 kCryptoJobAsync, 12 kSignJobModeSign, 13 kSignJobModeVerify, 14} = internalBinding('crypto'); 15 16const { 17 getBlockSize, 18 hasAnyNotIn, 19 jobPromise, 20 normalizeHashName, 21 validateBitLength, 22 validateKeyOps, 23 kHandle, 24 kKeyObject, 25} = require('internal/crypto/util'); 26 27const { 28 lazyDOMException, 29 promisify, 30} = require('internal/util'); 31 32const { 33 generateKey: _generateKey, 34} = require('internal/crypto/keygen'); 35 36const { 37 InternalCryptoKey, 38 SecretKeyObject, 39 createSecretKey, 40} = require('internal/crypto/keys'); 41 42const generateKey = promisify(_generateKey); 43 44async function hmacGenerateKey(algorithm, extractable, keyUsages) { 45 const { hash, name } = algorithm; 46 let { length } = algorithm; 47 48 if (length === undefined) 49 length = getBlockSize(hash.name); 50 51 validateBitLength(length, 'algorithm.length', true); 52 53 const usageSet = new SafeSet(keyUsages); 54 if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { 55 throw lazyDOMException( 56 'Unsupported key usage for an HMAC key', 57 'SyntaxError'); 58 } 59 60 const key = await generateKey('hmac', { length }).catch((err) => { 61 throw lazyDOMException( 62 'The operation failed for an operation-specific reason', 63 { name: 'OperationError', cause: err }); 64 }); 65 66 return new InternalCryptoKey( 67 key, 68 { name, length, hash: { name: hash.name } }, 69 ArrayFrom(usageSet), 70 extractable); 71} 72 73function getAlgorithmName(hash) { 74 switch (hash) { 75 case 'SHA-1': // Fall through 76 case 'SHA-256': // Fall through 77 case 'SHA-384': // Fall through 78 case 'SHA-512': // Fall through 79 return `HS${hash.slice(4)}`; 80 default: 81 throw lazyDOMException('Unsupported digest algorithm', 'DataError'); 82 } 83} 84 85async function hmacImportKey( 86 format, 87 keyData, 88 algorithm, 89 extractable, 90 keyUsages) { 91 const usagesSet = new SafeSet(keyUsages); 92 if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) { 93 throw lazyDOMException( 94 'Unsupported key usage for an HMAC key', 95 'SyntaxError'); 96 } 97 let keyObject; 98 switch (format) { 99 case 'raw': { 100 const checkLength = keyData.byteLength * 8; 101 102 if (checkLength === 0 || algorithm.length === 0) 103 throw lazyDOMException('Zero-length key is not supported', 'DataError'); 104 105 // The Web Crypto spec allows for key lengths that are not multiples of 106 // 8. We don't. Our check here is stricter than that defined by the spec 107 // in that we require that algorithm.length match keyData.length * 8 if 108 // algorithm.length is specified. 109 if (algorithm.length !== undefined && 110 algorithm.length !== checkLength) { 111 throw lazyDOMException('Invalid key length', 'DataError'); 112 } 113 114 keyObject = createSecretKey(keyData); 115 break; 116 } 117 case 'jwk': { 118 if (!keyData.kty) 119 throw lazyDOMException('Invalid keyData', 'DataError'); 120 121 if (keyData.kty !== 'oct') 122 throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); 123 124 if (usagesSet.size > 0 && 125 keyData.use !== undefined && 126 keyData.use !== 'sig') { 127 throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); 128 } 129 130 validateKeyOps(keyData.key_ops, usagesSet); 131 132 if (keyData.ext !== undefined && 133 keyData.ext === false && 134 extractable === true) { 135 throw lazyDOMException( 136 'JWK "ext" Parameter and extractable mismatch', 137 'DataError'); 138 } 139 140 if (keyData.alg !== undefined) { 141 if (keyData.alg !== getAlgorithmName(algorithm.hash.name)) 142 throw lazyDOMException( 143 'JWK "alg" does not match the requested algorithm', 144 'DataError'); 145 } 146 147 const handle = new KeyObjectHandle(); 148 handle.initJwk(keyData); 149 keyObject = new SecretKeyObject(handle); 150 break; 151 } 152 default: 153 throw lazyDOMException(`Unable to import HMAC key with format ${format}`); 154 } 155 156 const { length } = keyObject[kHandle].keyDetail({}); 157 158 return new InternalCryptoKey( 159 keyObject, { 160 name: 'HMAC', 161 hash: algorithm.hash, 162 length, 163 }, 164 keyUsages, 165 extractable); 166} 167 168function hmacSignVerify(key, data, algorithm, signature) { 169 const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; 170 return jobPromise(() => new HmacJob( 171 kCryptoJobAsync, 172 mode, 173 normalizeHashName(key.algorithm.hash.name), 174 key[kKeyObject][kHandle], 175 data, 176 signature)); 177} 178 179module.exports = { 180 hmacImportKey, 181 hmacGenerateKey, 182 hmacSignVerify, 183}; 184