| 'use strict'; |
| |
| const { |
| SafeSet, |
| Uint8Array, |
| } = primordials; |
| |
| const { |
| KeyObjectHandle, |
| RSACipherJob, |
| RSAKeyExportJob, |
| SignJob, |
| kCryptoJobAsync, |
| kSignJobModeSign, |
| kSignJobModeVerify, |
| kKeyVariantRSA_SSA_PKCS1_v1_5, |
| kKeyVariantRSA_PSS, |
| kKeyVariantRSA_OAEP, |
| kKeyTypePrivate, |
| kWebCryptoCipherEncrypt, |
| RSA_PKCS1_PSS_PADDING, |
| } = internalBinding('crypto'); |
| |
| const { |
| codes: { |
| ERR_INVALID_ARG_TYPE, |
| ERR_MISSING_OPTION, |
| } |
| } = require('internal/errors'); |
| |
| const { |
| validateInt32, |
| validateUint32, |
| } = require('internal/validators'); |
| |
| const { |
| bigIntArrayToUnsignedInt, |
| getArrayBufferOrView, |
| getUsagesUnion, |
| hasAnyNotIn, |
| jobPromise, |
| normalizeHashName, |
| validateKeyOps, |
| validateMaxBufferLength, |
| kHandle, |
| kKeyObject, |
| } = require('internal/crypto/util'); |
| |
| const { |
| lazyDOMException, |
| promisify, |
| } = require('internal/util'); |
| |
| const { |
| isUint8Array, |
| } = require('internal/util/types'); |
| |
| const { |
| InternalCryptoKey, |
| PrivateKeyObject, |
| PublicKeyObject, |
| createPublicKey, |
| createPrivateKey, |
| } = require('internal/crypto/keys'); |
| |
| const { |
| generateKeyPair: _generateKeyPair, |
| } = require('internal/crypto/keygen'); |
| |
| const kRsaVariants = { |
| 'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5, |
| 'RSA-PSS': kKeyVariantRSA_PSS, |
| 'RSA-OAEP': kKeyVariantRSA_OAEP, |
| }; |
| const generateKeyPair = promisify(_generateKeyPair); |
| |
| function verifyAcceptableRsaKeyUse(name, type, usages) { |
| let checkSet; |
| switch (name) { |
| case 'RSA-OAEP': |
| switch (type) { |
| case 'private': |
| checkSet = ['decrypt', 'unwrapKey']; |
| break; |
| case 'public': |
| checkSet = ['encrypt', 'wrapKey']; |
| break; |
| } |
| break; |
| default: |
| switch (type) { |
| case 'private': |
| checkSet = ['sign']; |
| break; |
| case 'public': |
| checkSet = ['verify']; |
| break; |
| } |
| } |
| if (hasAnyNotIn(usages, checkSet)) { |
| throw lazyDOMException( |
| `Unsupported key usage for an ${name} key`, |
| 'SyntaxError'); |
| } |
| } |
| |
| function rsaOaepCipher(mode, key, data, { label }) { |
| const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private'; |
| if (key.type !== type) { |
| throw lazyDOMException( |
| 'The requested operation is not valid for the provided key', |
| 'InvalidAccessError'); |
| } |
| if (label !== undefined) { |
| label = getArrayBufferOrView(label, 'algorithm.label'); |
| validateMaxBufferLength(label, 'algorithm.label'); |
| } |
| |
| return jobPromise(new RSACipherJob( |
| kCryptoJobAsync, |
| mode, |
| key[kKeyObject][kHandle], |
| data, |
| kKeyVariantRSA_OAEP, |
| normalizeHashName(key.algorithm.hash.name), |
| label)); |
| } |
| |
| async function rsaKeyGenerate( |
| algorithm, |
| extractable, |
| keyUsages) { |
| |
| const { |
| name, |
| modulusLength, |
| publicExponent, |
| hash |
| } = algorithm; |
| |
| if (hash === undefined) |
| throw new ERR_MISSING_OPTION('algorithm.hash'); |
| validateUint32(modulusLength, 'algorithm.modulusLength'); |
| if (!isUint8Array(publicExponent)) { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'algorithm.publicExponent', |
| 'Uint8Array', |
| publicExponent); |
| } |
| |
| const usageSet = new SafeSet(keyUsages); |
| |
| const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent); |
| if (publicExponentConverted === undefined) { |
| throw lazyDOMException( |
| 'The publicExponent must be equivalent to an unsigned 32-bit value', |
| 'OperationError'); |
| } |
| |
| switch (name) { |
| case 'RSA-OAEP': |
| if (hasAnyNotIn(usageSet, |
| ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) { |
| throw lazyDOMException( |
| 'Unsupported key usage for a RSA key', |
| 'SyntaxError'); |
| } |
| break; |
| default: |
| if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { |
| throw lazyDOMException( |
| 'Unsupported key usage for a RSA key', |
| 'SyntaxError'); |
| } |
| } |
| |
| const keypair = await generateKeyPair('rsa', { |
| modulusLength, |
| publicExponent: publicExponentConverted, |
| }).catch((err) => { |
| throw lazyDOMException( |
| 'The operation failed for an operation-specific reason', |
| { name: 'OperationError', cause: err }); |
| }); |
| |
| const keyAlgorithm = { |
| name, |
| modulusLength, |
| publicExponent, |
| hash: { name: hash.name } |
| }; |
| |
| let publicUsages; |
| let privateUsages; |
| switch (name) { |
| case 'RSA-OAEP': { |
| publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey'); |
| privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey'); |
| break; |
| } |
| default: { |
| publicUsages = getUsagesUnion(usageSet, 'verify'); |
| privateUsages = getUsagesUnion(usageSet, 'sign'); |
| break; |
| } |
| } |
| |
| const publicKey = |
| new InternalCryptoKey( |
| keypair.publicKey, |
| keyAlgorithm, |
| publicUsages, |
| true); |
| |
| const privateKey = |
| new InternalCryptoKey( |
| keypair.privateKey, |
| keyAlgorithm, |
| privateUsages, |
| extractable); |
| |
| return { __proto__: null, publicKey, privateKey }; |
| } |
| |
| function rsaExportKey(key, format) { |
| return jobPromise(new RSAKeyExportJob( |
| kCryptoJobAsync, |
| format, |
| key[kKeyObject][kHandle], |
| kRsaVariants[key.algorithm.name])); |
| } |
| |
| async function rsaImportKey( |
| format, |
| keyData, |
| algorithm, |
| extractable, |
| keyUsages) { |
| const { hash } = algorithm; |
| if (hash === undefined) |
| throw new ERR_MISSING_OPTION('algorithm.hash'); |
| |
| const usagesSet = new SafeSet(keyUsages); |
| let keyObject; |
| switch (format) { |
| case 'spki': { |
| verifyAcceptableRsaKeyUse(algorithm.name, 'public', usagesSet); |
| keyObject = createPublicKey({ |
| key: keyData, |
| format: 'der', |
| type: 'spki' |
| }); |
| break; |
| } |
| case 'pkcs8': { |
| verifyAcceptableRsaKeyUse(algorithm.name, 'private', usagesSet); |
| keyObject = createPrivateKey({ |
| key: keyData, |
| format: 'der', |
| type: 'pkcs8' |
| }); |
| break; |
| } |
| case 'jwk': { |
| if (keyData == null || typeof keyData !== 'object') |
| throw lazyDOMException('Invalid JWK keyData', 'DataError'); |
| |
| verifyAcceptableRsaKeyUse( |
| algorithm.name, |
| keyData.d !== undefined ? 'private' : 'public', |
| usagesSet); |
| |
| if (keyData.kty !== 'RSA') |
| throw lazyDOMException('Invalid key type', 'DataError'); |
| |
| if (usagesSet.size > 0 && keyData.use !== undefined) { |
| const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig'; |
| if (keyData.use !== checkUse) |
| throw lazyDOMException('Invalid use type', 'DataError'); |
| } |
| |
| validateKeyOps(keyData.key_ops, usagesSet); |
| |
| if (keyData.ext !== undefined && |
| keyData.ext === false && |
| extractable === true) { |
| throw lazyDOMException('JWK is not extractable', 'DataError'); |
| } |
| |
| if (keyData.alg !== undefined) { |
| if (typeof keyData.alg !== 'string') |
| throw lazyDOMException('Invalid alg', 'DataError'); |
| const hash = |
| normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto); |
| if (hash !== algorithm.hash.name) |
| throw lazyDOMException('Hash mismatch', 'DataError'); |
| } |
| |
| const handle = new KeyObjectHandle(); |
| const type = handle.initJwk(keyData); |
| if (type === undefined) |
| throw lazyDOMException('Invalid JWK keyData', 'DataError'); |
| |
| keyObject = type === kKeyTypePrivate ? |
| new PrivateKeyObject(handle) : |
| new PublicKeyObject(handle); |
| |
| break; |
| } |
| default: |
| throw lazyDOMException( |
| `Unable to import RSA key with format ${format}`, |
| 'NotSupportedError'); |
| } |
| |
| if (keyObject.asymmetricKeyType !== 'rsa') { |
| throw lazyDOMException('Invalid key type', 'DataError'); |
| } |
| |
| const { |
| modulusLength, |
| publicExponent, |
| } = keyObject[kHandle].keyDetail({}); |
| |
| return new InternalCryptoKey(keyObject, { |
| name: algorithm.name, |
| modulusLength, |
| publicExponent: new Uint8Array(publicExponent), |
| hash: algorithm.hash |
| }, keyUsages, extractable); |
| } |
| |
| function rsaSignVerify(key, data, { saltLength }, signature) { |
| let padding; |
| if (key.algorithm.name === 'RSA-PSS') { |
| padding = RSA_PKCS1_PSS_PADDING; |
| // TODO(@jasnell): Validate maximum size of saltLength |
| // based on the key size: |
| // Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2 |
| validateInt32(saltLength, 'algorithm.saltLength', -2); |
| } |
| |
| const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; |
| const type = mode === kSignJobModeSign ? 'private' : 'public'; |
| |
| if (key.type !== type) |
| throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); |
| |
| return jobPromise(new SignJob( |
| kCryptoJobAsync, |
| signature === undefined ? kSignJobModeSign : kSignJobModeVerify, |
| key[kKeyObject][kHandle], |
| undefined, |
| undefined, |
| undefined, |
| data, |
| normalizeHashName(key.algorithm.hash.name), |
| saltLength, |
| padding, |
| undefined, |
| signature)); |
| } |
| |
| |
| module.exports = { |
| rsaCipher: rsaOaepCipher, |
| rsaExportKey, |
| rsaImportKey, |
| rsaKeyGenerate, |
| rsaSignVerify, |
| }; |