Introduce prepareSubtleMethod() and convertSubtleArgument() for the common SubtleCrypto call prelude. Methods now reuse the same receiver check, required-argument check, error prefix construction, argument context selection, and WebIDL conversion path. Introduce WebCrypto key usage helpers for common usage validation and key pair usage splitting. Algorithm modules now define their allowed public, private, and key generation usages once, then call the shared helpers from generateKey() and importKey() paths. This removes repeated function-invocation setup in webcrypto.js and repeated key usage checks across the WebCrypto algorithm modules while preserving the existing validation behavior. Mark normalized-algorithm fallback branches as unreachable assertions. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/63975 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
257 lines
7 KiB
JavaScript
257 lines
7 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
SafeSet,
|
|
TypedArrayPrototypeGetBuffer,
|
|
TypedArrayPrototypeGetByteLength,
|
|
} = primordials;
|
|
|
|
const {
|
|
EcKeyPairGenJob,
|
|
KeyObjectHandle,
|
|
SignJob,
|
|
kCryptoJobWebCrypto,
|
|
kKeyFormatDER,
|
|
kKeyFormatRawPublic,
|
|
kKeyTypePublic,
|
|
kSignJobModeSign,
|
|
kSignJobModeVerify,
|
|
kSigEncP1363,
|
|
kWebCryptoKeyFormatPKCS8,
|
|
kWebCryptoKeyFormatRaw,
|
|
kWebCryptoKeyFormatSPKI,
|
|
} = internalBinding('crypto');
|
|
|
|
const {
|
|
crypto: {
|
|
POINT_CONVERSION_UNCOMPRESSED,
|
|
},
|
|
} = internalBinding('constants');
|
|
|
|
const {
|
|
getUsagesMask,
|
|
jobPromise,
|
|
normalizeHashName,
|
|
kNamedCurveAliases,
|
|
} = require('internal/crypto/util');
|
|
|
|
const {
|
|
lazyDOMException,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
InternalCryptoKey,
|
|
getCryptoKeyAlgorithm,
|
|
getCryptoKeyHandle,
|
|
getCryptoKeyType,
|
|
} = require('internal/crypto/keys');
|
|
|
|
const {
|
|
createKeyUsages,
|
|
getKeyPairUsages,
|
|
importDerKey,
|
|
importJwkKey,
|
|
importRawKey,
|
|
validateJwk,
|
|
validateKeyUsages,
|
|
validateUsagesNotEmpty,
|
|
verifyAcceptableKeyUse,
|
|
} = require('internal/crypto/webcrypto_util');
|
|
|
|
const kDeriveUsages = createKeyUsages([], ['deriveKey', 'deriveBits']);
|
|
|
|
const kSignVerifyUsages = createKeyUsages(['verify'], ['sign']);
|
|
|
|
const kUsages = {
|
|
'__proto__': null,
|
|
'ECDH': kDeriveUsages,
|
|
'ECDSA': kSignVerifyUsages,
|
|
};
|
|
|
|
function ecGenerateKey(algorithm, extractable, usages) {
|
|
const { name, namedCurve } = algorithm;
|
|
const allowedUsages = kUsages[name];
|
|
const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name);
|
|
|
|
const keyAlgorithm = { name, namedCurve };
|
|
const keyUsages = getKeyPairUsages(usagesSet, allowedUsages);
|
|
validateUsagesNotEmpty(keyUsages.private);
|
|
|
|
return jobPromise(() => new EcKeyPairGenJob(
|
|
kCryptoJobWebCrypto,
|
|
namedCurve,
|
|
undefined,
|
|
keyAlgorithm,
|
|
getUsagesMask(keyUsages.public),
|
|
getUsagesMask(keyUsages.private),
|
|
extractable));
|
|
}
|
|
|
|
function ecExportKey(key, format) {
|
|
try {
|
|
const handle = getCryptoKeyHandle(key);
|
|
switch (format) {
|
|
case kWebCryptoKeyFormatRaw: {
|
|
return TypedArrayPrototypeGetBuffer(
|
|
handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED));
|
|
}
|
|
case kWebCryptoKeyFormatSPKI: {
|
|
let spki = handle.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI);
|
|
// WebCrypto requires uncompressed point format for SPKI exports.
|
|
// This is a very rare edge case dependent on the imported key
|
|
// using compressed point format.
|
|
// Expected SPKI DER byte lengths with uncompressed points:
|
|
// P-256: 91 = 26 bytes of SPKI ASN.1 + 65-byte uncompressed point.
|
|
// P-384: 120 = 23 bytes of SPKI ASN.1 + 97-byte uncompressed point.
|
|
// P-521: 158 = 25 bytes of SPKI ASN.1 + 133-byte uncompressed point.
|
|
// Difference in initial SPKI ASN.1 is caused by OIDs and length encoding.
|
|
const { namedCurve } = getCryptoKeyAlgorithm(key);
|
|
if (TypedArrayPrototypeGetByteLength(spki) !== {
|
|
'__proto__': null, 'P-256': 91, 'P-384': 120, 'P-521': 158,
|
|
}[namedCurve]) {
|
|
const raw = handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED);
|
|
const tmp = new KeyObjectHandle();
|
|
tmp.init(kKeyTypePublic, raw, kKeyFormatRawPublic,
|
|
'ec', null, namedCurve);
|
|
spki = tmp.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI);
|
|
}
|
|
return TypedArrayPrototypeGetBuffer(spki);
|
|
}
|
|
case kWebCryptoKeyFormatPKCS8: {
|
|
return TypedArrayPrototypeGetBuffer(
|
|
handle.export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null));
|
|
}
|
|
default:
|
|
return undefined;
|
|
}
|
|
} catch (err) {
|
|
throw lazyDOMException(
|
|
'The operation failed for an operation-specific reason',
|
|
{ name: 'OperationError', cause: err });
|
|
}
|
|
}
|
|
|
|
function ecImportKey(
|
|
format,
|
|
keyData,
|
|
algorithm,
|
|
extractable,
|
|
usages,
|
|
) {
|
|
const { name, namedCurve } = algorithm;
|
|
|
|
let handle;
|
|
const allowedUsages = kUsages[name];
|
|
const usagesSet = new SafeSet(usages);
|
|
switch (format) {
|
|
case 'KeyObjectHandle':
|
|
verifyAcceptableKeyUse(
|
|
name,
|
|
usagesSet,
|
|
keyData.getKeyType() === kKeyTypePublic ?
|
|
allowedUsages.public :
|
|
allowedUsages.private);
|
|
handle = keyData;
|
|
break;
|
|
case 'spki': {
|
|
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public);
|
|
handle = importDerKey(keyData, true);
|
|
break;
|
|
}
|
|
case 'pkcs8': {
|
|
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.private);
|
|
handle = importDerKey(keyData, false);
|
|
break;
|
|
}
|
|
case 'jwk': {
|
|
const expectedUse = name === 'ECDH' ? 'enc' : 'sig';
|
|
validateJwk(keyData, 'EC', extractable, usagesSet, expectedUse);
|
|
|
|
if (keyData.crv !== namedCurve)
|
|
throw lazyDOMException(
|
|
'JWK "crv" does not match the requested algorithm',
|
|
'DataError');
|
|
|
|
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
|
|
let algNamedCurve;
|
|
switch (keyData.alg) {
|
|
case 'ES256': algNamedCurve = 'P-256'; break;
|
|
case 'ES384': algNamedCurve = 'P-384'; break;
|
|
case 'ES512': algNamedCurve = 'P-521'; break;
|
|
}
|
|
if (algNamedCurve !== namedCurve)
|
|
throw lazyDOMException(
|
|
'JWK "alg" does not match the requested algorithm',
|
|
'DataError');
|
|
}
|
|
|
|
const isPublic = keyData.d === undefined;
|
|
verifyAcceptableKeyUse(
|
|
name,
|
|
usagesSet,
|
|
isPublic ? allowedUsages.public : allowedUsages.private);
|
|
handle = importJwkKey(isPublic, keyData);
|
|
break;
|
|
}
|
|
case 'raw': {
|
|
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public);
|
|
handle = importRawKey(true, keyData, kKeyFormatRawPublic, 'ec', namedCurve);
|
|
break;
|
|
}
|
|
default:
|
|
return undefined;
|
|
}
|
|
|
|
switch (algorithm.name) {
|
|
case 'ECDSA':
|
|
// Fall through
|
|
case 'ECDH':
|
|
if (handle.getAsymmetricKeyType() !== 'ec')
|
|
throw lazyDOMException('Invalid key type', 'DataError');
|
|
break;
|
|
}
|
|
|
|
if (!handle.checkEcKeyData()) {
|
|
throw lazyDOMException('Invalid keyData', 'DataError');
|
|
}
|
|
|
|
if (kNamedCurveAliases[namedCurve] !== handle.keyDetail({}).namedCurve)
|
|
throw lazyDOMException('Named curve mismatch', 'DataError');
|
|
|
|
return new InternalCryptoKey(
|
|
handle,
|
|
{ name, namedCurve },
|
|
getUsagesMask(usagesSet),
|
|
extractable);
|
|
}
|
|
|
|
function ecdsaSignVerify(key, data, { name, hash }, signature) {
|
|
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
|
|
const type = mode === kSignJobModeSign ? 'private' : 'public';
|
|
|
|
if (getCryptoKeyType(key) !== type)
|
|
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
|
|
|
|
return jobPromise(() => new SignJob(
|
|
kCryptoJobWebCrypto,
|
|
mode,
|
|
getCryptoKeyHandle(key),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
data,
|
|
normalizeHashName(hash.name),
|
|
undefined, // Salt length, not used with ECDSA
|
|
undefined, // PSS Padding, not used with ECDSA
|
|
kSigEncP1363,
|
|
undefined,
|
|
signature));
|
|
}
|
|
|
|
module.exports = {
|
|
ecExportKey,
|
|
ecImportKey,
|
|
ecGenerateKey,
|
|
ecdsaSignVerify,
|
|
};
|