2020-08-25 10:05:51 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const {
|
|
|
|
SafeSet,
|
|
|
|
} = primordials;
|
|
|
|
|
|
|
|
const {
|
|
|
|
ECKeyExportJob,
|
|
|
|
KeyObjectHandle,
|
|
|
|
SignJob,
|
|
|
|
kCryptoJobAsync,
|
|
|
|
kKeyTypePrivate,
|
|
|
|
kSignJobModeSign,
|
|
|
|
kSignJobModeVerify,
|
2021-02-24 13:13:42 +01:00
|
|
|
kSigEncP1363,
|
2020-08-25 10:05:51 -07:00
|
|
|
} = internalBinding('crypto');
|
|
|
|
|
|
|
|
const {
|
|
|
|
getUsagesUnion,
|
|
|
|
hasAnyNotIn,
|
|
|
|
jobPromise,
|
|
|
|
normalizeHashName,
|
|
|
|
validateKeyOps,
|
|
|
|
kHandle,
|
|
|
|
kKeyObject,
|
|
|
|
kNamedCurveAliases,
|
|
|
|
} = require('internal/crypto/util');
|
|
|
|
|
2021-06-21 11:34:48 +08:00
|
|
|
const {
|
|
|
|
lazyDOMException,
|
2022-08-12 20:29:14 +01:00
|
|
|
promisify,
|
2021-06-21 11:34:48 +08:00
|
|
|
} = require('internal/util');
|
|
|
|
|
2020-08-25 10:05:51 -07:00
|
|
|
const {
|
2022-08-12 20:29:14 +01:00
|
|
|
generateKeyPair: _generateKeyPair,
|
2020-08-25 10:05:51 -07:00
|
|
|
} = require('internal/crypto/keygen');
|
|
|
|
|
|
|
|
const {
|
|
|
|
InternalCryptoKey,
|
|
|
|
PrivateKeyObject,
|
|
|
|
PublicKeyObject,
|
|
|
|
createPrivateKey,
|
|
|
|
createPublicKey,
|
|
|
|
} = require('internal/crypto/keys');
|
|
|
|
|
2022-08-12 20:29:14 +01:00
|
|
|
const generateKeyPair = promisify(_generateKeyPair);
|
|
|
|
|
2022-11-22 12:46:05 +01:00
|
|
|
function verifyAcceptableEcKeyUse(name, isPublic, usages) {
|
2021-02-18 14:51:48 +01:00
|
|
|
let checkSet;
|
2020-08-25 10:05:51 -07:00
|
|
|
switch (name) {
|
|
|
|
case 'ECDH':
|
2022-11-22 12:46:05 +01:00
|
|
|
checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
|
2020-08-25 10:05:51 -07:00
|
|
|
break;
|
|
|
|
case 'ECDSA':
|
2022-11-22 12:46:05 +01:00
|
|
|
checkSet = isPublic ? ['verify'] : ['sign'];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw lazyDOMException(
|
|
|
|
'The algorithm is not supported', 'NotSupportedError');
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
2021-02-18 14:51:48 +01:00
|
|
|
if (hasAnyNotIn(usages, checkSet)) {
|
2020-08-25 10:05:51 -07:00
|
|
|
throw lazyDOMException(
|
|
|
|
`Unsupported key usage for a ${name} key`,
|
|
|
|
'SyntaxError');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createECPublicKeyRaw(namedCurve, keyData) {
|
|
|
|
const handle = new KeyObjectHandle();
|
2022-11-21 23:43:57 +01:00
|
|
|
|
|
|
|
if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
|
|
|
|
throw lazyDOMException('Invalid keyData', 'DataError');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new PublicKeyObject(handle);
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
|
|
|
|
2025-03-04 18:45:51 +01:00
|
|
|
async function ecGenerateKey(algorithm, extractable, keyUsages) {
|
|
|
|
const { name, namedCurve } = algorithm;
|
2020-08-25 10:05:51 -07:00
|
|
|
|
|
|
|
const usageSet = new SafeSet(keyUsages);
|
|
|
|
switch (name) {
|
|
|
|
case 'ECDSA':
|
2021-02-18 14:51:48 +01:00
|
|
|
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
|
2020-08-25 10:05:51 -07:00
|
|
|
throw lazyDOMException(
|
|
|
|
'Unsupported key usage for an ECDSA key',
|
|
|
|
'SyntaxError');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'ECDH':
|
2021-02-18 14:51:48 +01:00
|
|
|
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
|
2020-08-25 10:05:51 -07:00
|
|
|
throw lazyDOMException(
|
|
|
|
'Unsupported key usage for an ECDH key',
|
|
|
|
'SyntaxError');
|
|
|
|
}
|
2021-01-10 07:53:01 -08:00
|
|
|
// Fall through
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
|
|
|
|
2022-08-12 20:29:14 +01:00
|
|
|
const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => {
|
|
|
|
throw lazyDOMException(
|
|
|
|
'The operation failed for an operation-specific reason',
|
2022-10-10 03:12:28 +02:00
|
|
|
{ name: 'OperationError', cause: err });
|
2022-08-12 20:29:14 +01:00
|
|
|
});
|
2020-08-25 10:05:51 -07:00
|
|
|
|
2022-08-12 20:29:14 +01:00
|
|
|
let publicUsages;
|
|
|
|
let privateUsages;
|
|
|
|
switch (name) {
|
|
|
|
case 'ECDSA':
|
|
|
|
publicUsages = getUsagesUnion(usageSet, 'verify');
|
|
|
|
privateUsages = getUsagesUnion(usageSet, 'sign');
|
|
|
|
break;
|
|
|
|
case 'ECDH':
|
|
|
|
publicUsages = [];
|
|
|
|
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
|
|
|
|
break;
|
|
|
|
}
|
2020-08-25 10:05:51 -07:00
|
|
|
|
2022-08-12 20:29:14 +01:00
|
|
|
const keyAlgorithm = { name, namedCurve };
|
|
|
|
|
|
|
|
const publicKey =
|
|
|
|
new InternalCryptoKey(
|
|
|
|
keypair.publicKey,
|
|
|
|
keyAlgorithm,
|
|
|
|
publicUsages,
|
|
|
|
true);
|
|
|
|
|
|
|
|
const privateKey =
|
|
|
|
new InternalCryptoKey(
|
|
|
|
keypair.privateKey,
|
|
|
|
keyAlgorithm,
|
|
|
|
privateUsages,
|
|
|
|
extractable);
|
|
|
|
|
2022-10-20 22:12:03 -05:00
|
|
|
return { __proto__: null, publicKey, privateKey };
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function ecExportKey(key, format) {
|
2022-11-07 07:35:35 +01:00
|
|
|
return jobPromise(() => new ECKeyExportJob(
|
2020-08-25 10:05:51 -07:00
|
|
|
kCryptoJobAsync,
|
|
|
|
format,
|
|
|
|
key[kKeyObject][kHandle]));
|
|
|
|
}
|
|
|
|
|
2024-10-06 20:09:02 +02:00
|
|
|
function ecImportKey(
|
2020-08-25 10:05:51 -07:00
|
|
|
format,
|
|
|
|
keyData,
|
|
|
|
algorithm,
|
|
|
|
extractable,
|
2025-03-04 18:45:51 +01:00
|
|
|
keyUsages,
|
|
|
|
) {
|
2020-08-25 10:05:51 -07:00
|
|
|
const { name, namedCurve } = algorithm;
|
2022-08-08 12:02:00 +02:00
|
|
|
|
2020-08-25 10:05:51 -07:00
|
|
|
let keyObject;
|
|
|
|
const usagesSet = new SafeSet(keyUsages);
|
|
|
|
switch (format) {
|
2024-10-06 20:09:02 +02:00
|
|
|
case 'KeyObject': {
|
|
|
|
verifyAcceptableEcKeyUse(name, keyData.type === 'public', usagesSet);
|
|
|
|
keyObject = keyData;
|
|
|
|
break;
|
|
|
|
}
|
2020-08-25 10:05:51 -07:00
|
|
|
case 'spki': {
|
2022-11-22 12:46:05 +01:00
|
|
|
verifyAcceptableEcKeyUse(name, true, usagesSet);
|
2022-11-21 23:43:57 +01:00
|
|
|
try {
|
|
|
|
keyObject = createPublicKey({
|
|
|
|
key: keyData,
|
|
|
|
format: 'der',
|
2023-02-28 12:14:11 +01:00
|
|
|
type: 'spki',
|
2022-11-21 23:43:57 +01:00
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
throw lazyDOMException(
|
|
|
|
'Invalid keyData', { name: 'DataError', cause: err });
|
|
|
|
}
|
2020-08-25 10:05:51 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'pkcs8': {
|
2022-11-22 12:46:05 +01:00
|
|
|
verifyAcceptableEcKeyUse(name, false, usagesSet);
|
2022-11-21 23:43:57 +01:00
|
|
|
try {
|
|
|
|
keyObject = createPrivateKey({
|
|
|
|
key: keyData,
|
|
|
|
format: 'der',
|
2023-02-28 12:14:11 +01:00
|
|
|
type: 'pkcs8',
|
2022-11-21 23:43:57 +01:00
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
throw lazyDOMException(
|
|
|
|
'Invalid keyData', { name: 'DataError', cause: err });
|
|
|
|
}
|
2020-08-25 10:05:51 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'jwk': {
|
2023-01-17 09:57:58 +01:00
|
|
|
if (!keyData.kty)
|
|
|
|
throw lazyDOMException('Invalid keyData', 'DataError');
|
2022-06-04 08:20:27 +02:00
|
|
|
if (keyData.kty !== 'EC')
|
2023-01-17 09:57:58 +01:00
|
|
|
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
|
2022-06-15 13:44:07 +02:00
|
|
|
if (keyData.crv !== namedCurve)
|
2023-01-17 09:57:58 +01:00
|
|
|
throw lazyDOMException(
|
|
|
|
'JWK "crv" does not match the requested algorithm',
|
|
|
|
'DataError');
|
2022-06-04 08:20:27 +02:00
|
|
|
|
2022-11-22 12:46:05 +01:00
|
|
|
verifyAcceptableEcKeyUse(
|
|
|
|
name,
|
|
|
|
keyData.d === undefined,
|
|
|
|
usagesSet);
|
2022-06-04 08:20:27 +02:00
|
|
|
|
|
|
|
if (usagesSet.size > 0 && keyData.use !== undefined) {
|
2023-01-17 09:57:58 +01:00
|
|
|
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
|
|
|
|
if (keyData.use !== checkUse)
|
|
|
|
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
|
2022-06-04 08:20:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
validateKeyOps(keyData.key_ops, usagesSet);
|
|
|
|
|
|
|
|
if (keyData.ext !== undefined &&
|
|
|
|
keyData.ext === false &&
|
|
|
|
extractable === true) {
|
2023-01-17 09:57:58 +01:00
|
|
|
throw lazyDOMException(
|
|
|
|
'JWK "ext" Parameter and extractable mismatch',
|
|
|
|
'DataError');
|
2022-06-04 08:20:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
|
2022-06-15 13:44:07 +02:00
|
|
|
let algNamedCurve;
|
2022-06-04 08:20:27 +02:00
|
|
|
switch (keyData.alg) {
|
2022-06-15 13:44:07 +02:00
|
|
|
case 'ES256': algNamedCurve = 'P-256'; break;
|
|
|
|
case 'ES384': algNamedCurve = 'P-384'; break;
|
|
|
|
case 'ES512': algNamedCurve = 'P-521'; break;
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
2022-06-15 13:44:07 +02:00
|
|
|
if (algNamedCurve !== namedCurve)
|
2023-01-17 09:57:58 +01:00
|
|
|
throw lazyDOMException(
|
|
|
|
'JWK "alg" does not match the requested algorithm',
|
|
|
|
'DataError');
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
2022-06-04 08:20:27 +02:00
|
|
|
|
|
|
|
const handle = new KeyObjectHandle();
|
2024-09-23 17:28:44 +02:00
|
|
|
let type;
|
|
|
|
try {
|
|
|
|
type = handle.initJwk(keyData, namedCurve);
|
|
|
|
} catch (err) {
|
|
|
|
throw lazyDOMException(
|
|
|
|
'Invalid keyData', { name: 'DataError', cause: err });
|
|
|
|
}
|
2022-06-04 08:20:27 +02:00
|
|
|
if (type === undefined)
|
2024-09-23 17:28:44 +02:00
|
|
|
throw lazyDOMException('Invalid keyData', 'DataError');
|
2022-06-04 08:20:27 +02:00
|
|
|
keyObject = type === kKeyTypePrivate ?
|
|
|
|
new PrivateKeyObject(handle) :
|
|
|
|
new PublicKeyObject(handle);
|
2020-08-25 10:05:51 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'raw': {
|
2022-11-22 12:46:05 +01:00
|
|
|
verifyAcceptableEcKeyUse(name, true, usagesSet);
|
2022-06-04 08:20:27 +02:00
|
|
|
keyObject = createECPublicKeyRaw(namedCurve, keyData);
|
2020-08-25 10:05:51 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-31 19:48:29 +02:00
|
|
|
switch (algorithm.name) {
|
|
|
|
case 'ECDSA':
|
2022-06-04 08:20:27 +02:00
|
|
|
// Fall through
|
2021-08-31 19:48:29 +02:00
|
|
|
case 'ECDH':
|
2022-06-04 08:20:27 +02:00
|
|
|
if (keyObject.asymmetricKeyType !== 'ec')
|
2021-08-31 19:48:29 +02:00
|
|
|
throw lazyDOMException('Invalid key type', 'DataError');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-10-20 18:25:42 +02:00
|
|
|
if (!keyObject[kHandle].checkEcKeyData()) {
|
|
|
|
throw lazyDOMException('Invalid keyData', 'DataError');
|
|
|
|
}
|
|
|
|
|
2022-06-04 08:20:27 +02:00
|
|
|
const {
|
2023-02-28 12:14:11 +01:00
|
|
|
namedCurve: checkNamedCurve,
|
2022-06-04 08:20:27 +02:00
|
|
|
} = keyObject[kHandle].keyDetail({});
|
|
|
|
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
|
|
|
|
throw lazyDOMException('Named curve mismatch', 'DataError');
|
2020-08-25 10:05:51 -07:00
|
|
|
|
|
|
|
return new InternalCryptoKey(
|
|
|
|
keyObject,
|
|
|
|
{ name, namedCurve },
|
|
|
|
keyUsages,
|
|
|
|
extractable);
|
|
|
|
}
|
|
|
|
|
2021-01-10 07:53:01 -08:00
|
|
|
function ecdsaSignVerify(key, data, { name, hash }, signature) {
|
2020-08-25 10:05:51 -07:00
|
|
|
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');
|
|
|
|
|
2022-06-04 08:20:27 +02:00
|
|
|
const hashname = normalizeHashName(hash.name);
|
2021-01-10 07:53:01 -08:00
|
|
|
|
2022-11-07 07:35:35 +01:00
|
|
|
return jobPromise(() => new SignJob(
|
2020-08-25 10:05:51 -07:00
|
|
|
kCryptoJobAsync,
|
|
|
|
mode,
|
|
|
|
key[kKeyObject][kHandle],
|
2021-03-21 11:04:26 +01:00
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
undefined,
|
2020-08-25 10:05:51 -07:00
|
|
|
data,
|
2021-01-10 07:53:01 -08:00
|
|
|
hashname,
|
2020-08-25 10:05:51 -07:00
|
|
|
undefined, // Salt length, not used with ECDSA
|
|
|
|
undefined, // PSS Padding, not used with ECDSA
|
2021-03-21 11:04:26 +01:00
|
|
|
kSigEncP1363,
|
|
|
|
signature));
|
2020-08-25 10:05:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
ecExportKey,
|
|
|
|
ecImportKey,
|
|
|
|
ecGenerateKey,
|
|
|
|
ecdsaSignVerify,
|
|
|
|
};
|