mirror of
https://github.com/nodejs/node.git
synced 2025-04-30 23:56:58 +00:00

PR-URL: https://github.com/nodejs/node/pull/44890 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
372 lines
8.8 KiB
JavaScript
372 lines
8.8 KiB
JavaScript
'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 { 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,
|
|
};
|