1'use strict'; 2 3const { 4 SafeSet, 5 Uint8Array, 6} = primordials; 7 8const { 9 KeyObjectHandle, 10 RSACipherJob, 11 RSAKeyExportJob, 12 SignJob, 13 kCryptoJobAsync, 14 kSignJobModeSign, 15 kSignJobModeVerify, 16 kKeyVariantRSA_SSA_PKCS1_v1_5, 17 kKeyVariantRSA_PSS, 18 kKeyVariantRSA_OAEP, 19 kKeyTypePrivate, 20 kWebCryptoCipherEncrypt, 21 RSA_PKCS1_PSS_PADDING, 22} = internalBinding('crypto'); 23 24const { 25 validateInt32, 26} = require('internal/validators'); 27 28const { 29 bigIntArrayToUnsignedInt, 30 getUsagesUnion, 31 hasAnyNotIn, 32 jobPromise, 33 normalizeHashName, 34 validateKeyOps, 35 validateMaxBufferLength, 36 kHandle, 37 kKeyObject, 38} = require('internal/crypto/util'); 39 40const { 41 lazyDOMException, 42 promisify, 43} = require('internal/util'); 44 45const { 46 InternalCryptoKey, 47 PrivateKeyObject, 48 PublicKeyObject, 49 createPublicKey, 50 createPrivateKey, 51} = require('internal/crypto/keys'); 52 53const { 54 generateKeyPair: _generateKeyPair, 55} = require('internal/crypto/keygen'); 56 57const kRsaVariants = { 58 'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5, 59 'RSA-PSS': kKeyVariantRSA_PSS, 60 'RSA-OAEP': kKeyVariantRSA_OAEP, 61}; 62const generateKeyPair = promisify(_generateKeyPair); 63 64function verifyAcceptableRsaKeyUse(name, isPublic, usages) { 65 let checkSet; 66 switch (name) { 67 case 'RSA-OAEP': 68 checkSet = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey']; 69 break; 70 case 'RSA-PSS': 71 // Fall through 72 case 'RSASSA-PKCS1-v1_5': 73 checkSet = isPublic ? ['verify'] : ['sign']; 74 break; 75 default: 76 throw lazyDOMException( 77 'The algorithm is not supported', 'NotSupportedError'); 78 } 79 if (hasAnyNotIn(usages, checkSet)) { 80 throw lazyDOMException( 81 `Unsupported key usage for an ${name} key`, 82 'SyntaxError'); 83 } 84} 85 86function rsaOaepCipher(mode, key, data, { label }) { 87 const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private'; 88 if (key.type !== type) { 89 throw lazyDOMException( 90 'The requested operation is not valid for the provided key', 91 'InvalidAccessError'); 92 } 93 if (label !== undefined) { 94 validateMaxBufferLength(label, 'algorithm.label'); 95 } 96 97 return jobPromise(() => new RSACipherJob( 98 kCryptoJobAsync, 99 mode, 100 key[kKeyObject][kHandle], 101 data, 102 kKeyVariantRSA_OAEP, 103 normalizeHashName(key.algorithm.hash.name), 104 label)); 105} 106 107async function rsaKeyGenerate( 108 algorithm, 109 extractable, 110 keyUsages) { 111 112 const { 113 name, 114 modulusLength, 115 publicExponent, 116 hash, 117 } = algorithm; 118 119 const usageSet = new SafeSet(keyUsages); 120 121 const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent); 122 if (publicExponentConverted === undefined) { 123 throw lazyDOMException( 124 'The publicExponent must be equivalent to an unsigned 32-bit value', 125 'OperationError'); 126 } 127 128 switch (name) { 129 case 'RSA-OAEP': 130 if (hasAnyNotIn(usageSet, 131 ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) { 132 throw lazyDOMException( 133 'Unsupported key usage for a RSA key', 134 'SyntaxError'); 135 } 136 break; 137 default: 138 if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { 139 throw lazyDOMException( 140 'Unsupported key usage for a RSA key', 141 'SyntaxError'); 142 } 143 } 144 145 const keypair = await generateKeyPair('rsa', { 146 modulusLength, 147 publicExponent: publicExponentConverted, 148 }).catch((err) => { 149 throw lazyDOMException( 150 'The operation failed for an operation-specific reason', 151 { name: 'OperationError', cause: err }); 152 }); 153 154 const keyAlgorithm = { 155 name, 156 modulusLength, 157 publicExponent, 158 hash: { name: hash.name }, 159 }; 160 161 let publicUsages; 162 let privateUsages; 163 switch (name) { 164 case 'RSA-OAEP': { 165 publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey'); 166 privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey'); 167 break; 168 } 169 default: { 170 publicUsages = getUsagesUnion(usageSet, 'verify'); 171 privateUsages = getUsagesUnion(usageSet, 'sign'); 172 break; 173 } 174 } 175 176 const publicKey = 177 new InternalCryptoKey( 178 keypair.publicKey, 179 keyAlgorithm, 180 publicUsages, 181 true); 182 183 const privateKey = 184 new InternalCryptoKey( 185 keypair.privateKey, 186 keyAlgorithm, 187 privateUsages, 188 extractable); 189 190 return { publicKey, privateKey }; 191} 192 193function rsaExportKey(key, format) { 194 return jobPromise(() => new RSAKeyExportJob( 195 kCryptoJobAsync, 196 format, 197 key[kKeyObject][kHandle], 198 kRsaVariants[key.algorithm.name])); 199} 200 201async function rsaImportKey( 202 format, 203 keyData, 204 algorithm, 205 extractable, 206 keyUsages) { 207 const usagesSet = new SafeSet(keyUsages); 208 let keyObject; 209 switch (format) { 210 case 'spki': { 211 verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet); 212 try { 213 keyObject = createPublicKey({ 214 key: keyData, 215 format: 'der', 216 type: 'spki', 217 }); 218 } catch (err) { 219 throw lazyDOMException( 220 'Invalid keyData', { name: 'DataError', cause: err }); 221 } 222 break; 223 } 224 case 'pkcs8': { 225 verifyAcceptableRsaKeyUse(algorithm.name, false, usagesSet); 226 try { 227 keyObject = createPrivateKey({ 228 key: keyData, 229 format: 'der', 230 type: 'pkcs8', 231 }); 232 } catch (err) { 233 throw lazyDOMException( 234 'Invalid keyData', { name: 'DataError', cause: err }); 235 } 236 break; 237 } 238 case 'jwk': { 239 if (!keyData.kty) 240 throw lazyDOMException('Invalid keyData', 'DataError'); 241 242 if (keyData.kty !== 'RSA') 243 throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); 244 245 verifyAcceptableRsaKeyUse( 246 algorithm.name, 247 keyData.d === undefined, 248 usagesSet); 249 250 if (usagesSet.size > 0 && keyData.use !== undefined) { 251 const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig'; 252 if (keyData.use !== checkUse) 253 throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); 254 } 255 256 validateKeyOps(keyData.key_ops, usagesSet); 257 258 if (keyData.ext !== undefined && 259 keyData.ext === false && 260 extractable === true) { 261 throw lazyDOMException( 262 'JWK "ext" Parameter and extractable mismatch', 263 'DataError'); 264 } 265 266 if (keyData.alg !== undefined) { 267 const hash = 268 normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto); 269 if (hash !== algorithm.hash.name) 270 throw lazyDOMException( 271 'JWK "alg" does not match the requested algorithm', 272 'DataError'); 273 } 274 275 const handle = new KeyObjectHandle(); 276 const type = handle.initJwk(keyData); 277 if (type === undefined) 278 throw lazyDOMException('Invalid JWK', 'DataError'); 279 280 keyObject = type === kKeyTypePrivate ? 281 new PrivateKeyObject(handle) : 282 new PublicKeyObject(handle); 283 284 break; 285 } 286 default: 287 throw lazyDOMException( 288 `Unable to import RSA key with format ${format}`, 289 'NotSupportedError'); 290 } 291 292 if (keyObject.asymmetricKeyType !== 'rsa') { 293 throw lazyDOMException('Invalid key type', 'DataError'); 294 } 295 296 const { 297 modulusLength, 298 publicExponent, 299 } = keyObject[kHandle].keyDetail({}); 300 301 return new InternalCryptoKey(keyObject, { 302 name: algorithm.name, 303 modulusLength, 304 publicExponent: new Uint8Array(publicExponent), 305 hash: algorithm.hash, 306 }, keyUsages, extractable); 307} 308 309function rsaSignVerify(key, data, { saltLength }, signature) { 310 let padding; 311 if (key.algorithm.name === 'RSA-PSS') { 312 padding = RSA_PKCS1_PSS_PADDING; 313 // TODO(@jasnell): Validate maximum size of saltLength 314 // based on the key size: 315 // Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2 316 validateInt32(saltLength, 'algorithm.saltLength', -2); 317 } 318 319 const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; 320 const type = mode === kSignJobModeSign ? 'private' : 'public'; 321 322 if (key.type !== type) 323 throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); 324 325 return jobPromise(() => new SignJob( 326 kCryptoJobAsync, 327 signature === undefined ? kSignJobModeSign : kSignJobModeVerify, 328 key[kKeyObject][kHandle], 329 undefined, 330 undefined, 331 undefined, 332 data, 333 normalizeHashName(key.algorithm.hash.name), 334 saltLength, 335 padding, 336 undefined, 337 signature)); 338} 339 340 341module.exports = { 342 rsaCipher: rsaOaepCipher, 343 rsaExportKey, 344 rsaImportKey, 345 rsaKeyGenerate, 346 rsaSignVerify, 347}; 348