'use strict';
const {
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
} = primordials;
const {
kCryptoJobWebCrypto,
KEMDecapsulateJob,
KEMEncapsulateJob,
kKeyFormatDER,
kKeyFormatRawPublic,
kKeyFormatRawSeed,
kKeyTypePublic,
kWebCryptoKeyFormatPKCS8,
kWebCryptoKeyFormatRaw,
kWebCryptoKeyFormatSPKI,
NamedKeyPairGenJob,
} = internalBinding('crypto');
const {
getUsagesMask,
jobPromise,
toUsagesSet,
getBufferSourceByteLength,
} = require('internal/crypto/util');
const {
lazyDOMException,
} = require('internal/util');
const {
getCryptoKeyHandle,
getCryptoKeyType,
InternalCryptoKey,
} = require('internal/crypto/keys');
const {
createKeyUsages,
getKeyPairUsages,
importDerKey,
importJwkKey,
importRawKey,
validateJwk,
validateKeyUsages,
validateUsagesNotEmpty,
verifyAcceptableKeyUse,
} = require('internal/crypto/webcrypto_util');
const kUsages = createKeyUsages(
['encapsulateKey', 'encapsulateBits'],
['decapsulateKey', 'decapsulateBits']);
function mlKemGenerateKey(algorithm, extractable, usages) {
const { name } = algorithm;
const usagesSet = validateKeyUsages(usages, kUsages.keygen, name);
const keyAlgorithm = { name };
const keyUsages = getKeyPairUsages(usagesSet, kUsages);
validateUsagesNotEmpty(keyUsages.private);
return jobPromise(() => new NamedKeyPairGenJob(
kCryptoJobWebCrypto,
name,
keyAlgorithm,
getUsagesMask(keyUsages.public),
getUsagesMask(keyUsages.private),
extractable));
}
function mlKemExportKey(key, format) {
try {
const handle = getCryptoKeyHandle(key);
switch (format) {
case kWebCryptoKeyFormatRaw: {
return TypedArrayPrototypeGetBuffer(
getCryptoKeyType(key) === 'private' ? handle.rawSeed() : handle.rawPublicKey());
}
case kWebCryptoKeyFormatSPKI: {
return TypedArrayPrototypeGetBuffer(
handle.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI));
}
case kWebCryptoKeyFormatPKCS8: {
const pkcs8 = handle.export(
kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null);
// Edge case only possible when user creates a seedless KeyObject
// first and converts it with KeyObject.prototype.toCryptoKey.
// 86 = 22 bytes of PKCS#8 ASN.1 + 64-byte seed.
if (TypedArrayPrototypeGetByteLength(pkcs8) !== 86) {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError' });
}
return TypedArrayPrototypeGetBuffer(pkcs8);
}
default:
return undefined;
}
} catch (err) {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
}
}
function mlKemImportKey(
format,
keyData,
algorithm,
extractable,
usages) {
const { name } = algorithm;
let handle;
const usagesSet = toUsagesSet(usages);
switch (format) {
case 'KeyObjectHandle':
verifyAcceptableKeyUse(
name,
usagesSet,
keyData.getKeyType() === kKeyTypePublic ?
kUsages.public :
kUsages.private);
handle = keyData;
break;
case 'spki': {
verifyAcceptableKeyUse(name, usagesSet, kUsages.public);
handle = importDerKey(keyData, true);
break;
}
case 'pkcs8': {
verifyAcceptableKeyUse(name, usagesSet, kUsages.private);
const privOnlyLengths = {
'__proto__': null,
'ML-KEM-512': 1660,
'ML-KEM-768': 2428,
'ML-KEM-1024': 3196,
};
if (getBufferSourceByteLength(keyData) === privOnlyLengths[name]) {
throw lazyDOMException(
'Importing an ML-KEM PKCS#8 key without a seed is not supported',
'NotSupportedError');
}
handle = importDerKey(keyData, false);
break;
}
case 'raw-public':
case 'raw-seed': {
const isPublic = format === 'raw-public';
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? kUsages.public : kUsages.private);
handle = importRawKey(isPublic, keyData,
isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed,
StringPrototypeToLowerCase(name));
break;
}
case 'jwk': {
const isPublic = keyData.priv === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? kUsages.public : kUsages.private);
validateJwk(keyData, 'AKP', extractable, usagesSet, 'enc');
if (keyData.alg !== name)
throw lazyDOMException(
'JWK "alg" Parameter and algorithm name mismatch', 'DataError');
handle = importJwkKey(isPublic, keyData);
break;
}
default:
return undefined;
}
if (handle.getAsymmetricKeyType() !== StringPrototypeToLowerCase(name)) {
throw lazyDOMException('Invalid key type', 'DataError');
}
return new InternalCryptoKey(
handle,
{ name },
getUsagesMask(usagesSet),
extractable);
}
function mlKemEncapsulate(encapsulationKey) {
if (getCryptoKeyType(encapsulationKey) !== 'public') {
throw lazyDOMException(`Key must be a public key`, 'InvalidAccessError');
}
return jobPromise(() => new KEMEncapsulateJob(
kCryptoJobWebCrypto,
getCryptoKeyHandle(encapsulationKey),
undefined,
undefined,
undefined,
undefined));
}
function mlKemDecapsulate(decapsulationKey, ciphertext) {
if (getCryptoKeyType(decapsulationKey) !== 'private') {
throw lazyDOMException(`Key must be a private key`, 'InvalidAccessError');
}
return jobPromise(() => new KEMDecapsulateJob(
kCryptoJobWebCrypto,
getCryptoKeyHandle(decapsulationKey),
undefined,
undefined,
undefined,
undefined,
ciphertext));
}
module.exports = {
mlKemExportKey,
mlKemImportKey,
mlKemEncapsulate,
mlKemDecapsulate,
mlKemGenerateKey,
};