'use strict';
const {
StringPrototypeToLowerCase,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
} = primordials;
const {
SignJob,
kCryptoJobWebCrypto,
kKeyFormatDER,
kKeyFormatRawPublic,
kKeyFormatRawSeed,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
kWebCryptoKeyFormatRaw,
kWebCryptoKeyFormatPKCS8,
kWebCryptoKeyFormatSPKI,
NamedKeyPairGenJob,
} = internalBinding('crypto');
const {
getUsagesMask,
jobPromise,
toUsagesSet,
validateAlgorithm,
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(['verify'], ['sign']);
function mlDsaGenerateKey(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 mlDsaExportKey(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.
// 54 = 22 bytes of PKCS#8 ASN.1 + 32-byte seed.
if (TypedArrayPrototypeGetByteLength(pkcs8) !== 54) {
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 mlDsaImportKey(
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-DSA-44': 2588,
'ML-DSA-65': 4060,
'ML-DSA-87': 4924,
};
if (getBufferSourceByteLength(keyData) === privOnlyLengths[name]) {
throw lazyDOMException(
'Importing an ML-DSA PKCS#8 key without a seed is not supported',
'NotSupportedError');
}
handle = importDerKey(keyData, false);
break;
}
case 'jwk': {
const isPublic = keyData.priv === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? kUsages.public : kUsages.private);
validateJwk(keyData, 'AKP', extractable, usagesSet, 'sig');
if (keyData.alg !== name)
throw lazyDOMException(
'JWK "alg" Parameter and algorithm name mismatch', 'DataError');
handle = importJwkKey(isPublic, keyData);
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;
}
default:
return undefined;
}
if (handle.getAsymmetricKeyType() !== StringPrototypeToLowerCase(name)) {
throw lazyDOMException('Invalid key type', 'DataError');
}
return new InternalCryptoKey(
handle,
{ name },
getUsagesMask(usagesSet),
extractable);
}
function mlDsaSignVerify(key, data, algorithm, 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');
validateAlgorithm(algorithm, mode === kSignJobModeSign ? 'sign' : 'verify');
return jobPromise(() => new SignJob(
kCryptoJobWebCrypto,
mode,
getCryptoKeyHandle(key),
undefined,
undefined,
undefined,
undefined,
data,
undefined,
undefined,
undefined,
undefined,
algorithm.context,
signature));
}
module.exports = {
mlDsaExportKey,
mlDsaImportKey,
mlDsaGenerateKey,
mlDsaSignVerify,
};