1'use strict'; 2 3const { 4 ArrayBufferIsView, 5 ArrayBufferPrototypeSlice, 6 ArrayFrom, 7 ArrayPrototypeIncludes, 8 ArrayPrototypePush, 9 MathFloor, 10 SafeSet, 11 TypedArrayPrototypeSlice, 12} = primordials; 13 14const { 15 AESCipherJob, 16 KeyObjectHandle, 17 kCryptoJobAsync, 18 kKeyVariantAES_CTR_128, 19 kKeyVariantAES_CBC_128, 20 kKeyVariantAES_GCM_128, 21 kKeyVariantAES_KW_128, 22 kKeyVariantAES_CTR_192, 23 kKeyVariantAES_CBC_192, 24 kKeyVariantAES_GCM_192, 25 kKeyVariantAES_KW_192, 26 kKeyVariantAES_CTR_256, 27 kKeyVariantAES_CBC_256, 28 kKeyVariantAES_GCM_256, 29 kKeyVariantAES_KW_256, 30 kWebCryptoCipherDecrypt, 31 kWebCryptoCipherEncrypt, 32} = internalBinding('crypto'); 33 34const { 35 hasAnyNotIn, 36 jobPromise, 37 validateByteLength, 38 validateKeyOps, 39 validateMaxBufferLength, 40 kAesKeyLengths, 41 kHandle, 42 kKeyObject, 43} = require('internal/crypto/util'); 44 45const { 46 lazyDOMException, 47 promisify, 48} = require('internal/util'); 49 50const { PromiseReject } = primordials; 51 52const { 53 InternalCryptoKey, 54 SecretKeyObject, 55 createSecretKey, 56} = require('internal/crypto/keys'); 57 58const { 59 generateKey: _generateKey, 60} = require('internal/crypto/keygen'); 61 62const kMaxCounterLength = 128; 63const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; 64const generateKey = promisify(_generateKey); 65 66function getAlgorithmName(name, length) { 67 switch (name) { 68 case 'AES-CBC': return `A${length}CBC`; 69 case 'AES-CTR': return `A${length}CTR`; 70 case 'AES-GCM': return `A${length}GCM`; 71 case 'AES-KW': return `A${length}KW`; 72 } 73} 74 75function validateKeyLength(length) { 76 if (length !== 128 && length !== 192 && length !== 256) 77 throw lazyDOMException('Invalid key length', 'DataError'); 78} 79 80function getVariant(name, length) { 81 switch (name) { 82 case 'AES-CBC': 83 switch (length) { 84 case 128: return kKeyVariantAES_CBC_128; 85 case 192: return kKeyVariantAES_CBC_192; 86 case 256: return kKeyVariantAES_CBC_256; 87 } 88 break; 89 case 'AES-CTR': 90 switch (length) { 91 case 128: return kKeyVariantAES_CTR_128; 92 case 192: return kKeyVariantAES_CTR_192; 93 case 256: return kKeyVariantAES_CTR_256; 94 } 95 break; 96 case 'AES-GCM': 97 switch (length) { 98 case 128: return kKeyVariantAES_GCM_128; 99 case 192: return kKeyVariantAES_GCM_192; 100 case 256: return kKeyVariantAES_GCM_256; 101 } 102 break; 103 case 'AES-KW': 104 switch (length) { 105 case 128: return kKeyVariantAES_KW_128; 106 case 192: return kKeyVariantAES_KW_192; 107 case 256: return kKeyVariantAES_KW_256; 108 } 109 break; 110 } 111} 112 113function asyncAesCtrCipher(mode, key, data, { counter, length }) { 114 validateByteLength(counter, 'algorithm.counter', 16); 115 // The length must specify an integer between 1 and 128. While 116 // there is no default, this should typically be 64. 117 if (length === 0 || length > kMaxCounterLength) { 118 throw lazyDOMException( 119 'AES-CTR algorithm.length must be between 1 and 128', 120 'OperationError'); 121 } 122 123 return jobPromise(() => new AESCipherJob( 124 kCryptoJobAsync, 125 mode, 126 key[kKeyObject][kHandle], 127 data, 128 getVariant('AES-CTR', key.algorithm.length), 129 counter, 130 length)); 131} 132 133function asyncAesCbcCipher(mode, key, data, { iv }) { 134 validateByteLength(iv, 'algorithm.iv', 16); 135 return jobPromise(() => new AESCipherJob( 136 kCryptoJobAsync, 137 mode, 138 key[kKeyObject][kHandle], 139 data, 140 getVariant('AES-CBC', key.algorithm.length), 141 iv)); 142} 143 144function asyncAesKwCipher(mode, key, data) { 145 return jobPromise(() => new AESCipherJob( 146 kCryptoJobAsync, 147 mode, 148 key[kKeyObject][kHandle], 149 data, 150 getVariant('AES-KW', key.algorithm.length))); 151} 152 153function asyncAesGcmCipher( 154 mode, 155 key, 156 data, 157 { iv, additionalData, tagLength = 128 }) { 158 if (!ArrayPrototypeIncludes(kTagLengths, tagLength)) { 159 return PromiseReject(lazyDOMException( 160 `${tagLength} is not a valid AES-GCM tag length`, 161 'OperationError')); 162 } 163 164 validateMaxBufferLength(iv, 'algorithm.iv'); 165 166 if (additionalData !== undefined) { 167 validateMaxBufferLength(additionalData, 'algorithm.additionalData'); 168 } 169 170 const tagByteLength = MathFloor(tagLength / 8); 171 let tag; 172 switch (mode) { 173 case kWebCryptoCipherDecrypt: { 174 const slice = ArrayBufferIsView(data) ? 175 TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; 176 tag = slice(data, -tagByteLength); 177 178 // Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations 179 // 180 // > If *plaintext* has a length less than *tagLength* bits, then `throw` 181 // > an `OperationError`. 182 if (tagByteLength > tag.byteLength) { 183 return PromiseReject(lazyDOMException( 184 'The provided data is too small.', 185 'OperationError')); 186 } 187 188 data = slice(data, 0, -tagByteLength); 189 break; 190 } 191 case kWebCryptoCipherEncrypt: 192 tag = tagByteLength; 193 break; 194 } 195 196 return jobPromise(() => new AESCipherJob( 197 kCryptoJobAsync, 198 mode, 199 key[kKeyObject][kHandle], 200 data, 201 getVariant('AES-GCM', key.algorithm.length), 202 iv, 203 tag, 204 additionalData)); 205} 206 207function aesCipher(mode, key, data, algorithm) { 208 switch (algorithm.name) { 209 case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm); 210 case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm); 211 case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm); 212 case 'AES-KW': return asyncAesKwCipher(mode, key, data); 213 } 214} 215 216async function aesGenerateKey(algorithm, extractable, keyUsages) { 217 const { name, length } = algorithm; 218 if (!ArrayPrototypeIncludes(kAesKeyLengths, length)) { 219 throw lazyDOMException( 220 'AES key length must be 128, 192, or 256 bits', 221 'OperationError'); 222 } 223 224 const checkUsages = ['wrapKey', 'unwrapKey']; 225 if (name !== 'AES-KW') 226 ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt'); 227 228 const usagesSet = new SafeSet(keyUsages); 229 if (hasAnyNotIn(usagesSet, checkUsages)) { 230 throw lazyDOMException( 231 'Unsupported key usage for an AES key', 232 'SyntaxError'); 233 } 234 235 const key = await generateKey('aes', { length }).catch((err) => { 236 throw lazyDOMException( 237 'The operation failed for an operation-specific reason' + 238 `[${err.message}]`, 239 { name: 'OperationError', cause: err }); 240 }); 241 242 return new InternalCryptoKey( 243 key, 244 { name, length }, 245 ArrayFrom(usagesSet), 246 extractable); 247} 248 249async function aesImportKey( 250 algorithm, 251 format, 252 keyData, 253 extractable, 254 keyUsages) { 255 const { name } = algorithm; 256 const checkUsages = ['wrapKey', 'unwrapKey']; 257 if (name !== 'AES-KW') 258 ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt'); 259 260 const usagesSet = new SafeSet(keyUsages); 261 if (hasAnyNotIn(usagesSet, checkUsages)) { 262 throw lazyDOMException( 263 'Unsupported key usage for an AES key', 264 'SyntaxError'); 265 } 266 267 let keyObject; 268 let length; 269 switch (format) { 270 case 'raw': { 271 validateKeyLength(keyData.byteLength * 8); 272 keyObject = createSecretKey(keyData); 273 break; 274 } 275 case 'jwk': { 276 if (!keyData.kty) 277 throw lazyDOMException('Invalid keyData', 'DataError'); 278 279 if (keyData.kty !== 'oct') 280 throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); 281 282 if (usagesSet.size > 0 && 283 keyData.use !== undefined && 284 keyData.use !== 'enc') { 285 throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); 286 } 287 288 validateKeyOps(keyData.key_ops, usagesSet); 289 290 if (keyData.ext !== undefined && 291 keyData.ext === false && 292 extractable === true) { 293 throw lazyDOMException( 294 'JWK "ext" Parameter and extractable mismatch', 295 'DataError'); 296 } 297 298 const handle = new KeyObjectHandle(); 299 handle.initJwk(keyData); 300 301 ({ length } = handle.keyDetail({ })); 302 validateKeyLength(length); 303 304 if (keyData.alg !== undefined) { 305 if (keyData.alg !== getAlgorithmName(algorithm.name, length)) 306 throw lazyDOMException( 307 'JWK "alg" does not match the requested algorithm', 308 'DataError'); 309 } 310 311 keyObject = new SecretKeyObject(handle); 312 break; 313 } 314 default: 315 throw lazyDOMException( 316 `Unable to import AES key with format ${format}`, 317 'NotSupportedError'); 318 } 319 320 if (length === undefined) { 321 ({ length } = keyObject[kHandle].keyDetail({ })); 322 validateKeyLength(length); 323 } 324 325 return new InternalCryptoKey( 326 keyObject, 327 { name, length }, 328 keyUsages, 329 extractable); 330} 331 332module.exports = { 333 aesCipher, 334 aesGenerateKey, 335 aesImportKey, 336 getAlgorithmName, 337}; 338