Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions doc/api/webcrypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1924,26 +1924,39 @@

<!-- YAML
added: v24.7.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63988

Check warning on line 1929 in doc/api/webcrypto.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Named cSHAKE variants are now accepted.
-->

* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}

The `functionName` member represents the function name, used by NIST to define
functions based on cSHAKE.
The Node.js Web Crypto API implementation only supports zero-length functionName
which is equivalent to not providing functionName at all.
The `functionName` member represents the NIST function-name byte string used to
domain-separate functions built on top of cSHAKE. Accepted values are:

* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
* the ASCII byte sequence `'KMAC'`
* the ASCII byte sequence `'TupleHash'`
* the ASCII byte sequence `'ParallelHash'`

#### `cShakeParams.customization`

<!-- YAML
added: v24.7.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63988

Check warning on line 1949 in doc/api/webcrypto.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Non-empty customization is now supported.
-->

* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}

The `customization` member represents the customization string.
The Node.js Web Crypto API implementation only supports zero-length customization
which is equivalent to not providing customization at all.
The `customization` member represents the customization data. Accepted
values are:

* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
* up to 512 bytes of arbitrary data

### Class: `EcdhKeyDeriveParams`

Expand Down Expand Up @@ -2472,9 +2485,7 @@
added: v24.15.0
-->

* Type: {number}

The length of the output in bytes. This must be a positive integer.
* Type: {number} represents the requested output length in bits.

#### `kmacParams.customization`

Expand Down
56 changes: 20 additions & 36 deletions lib/internal/crypto/aes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
'use strict';

const {
ArrayPrototypePush,
SafeSet,
} = primordials;

const {
AESCipherJob,
kCryptoJobWebCrypto,
Expand All @@ -28,7 +23,6 @@ const {

const {
getUsagesMask,
hasAnyNotIn,
jobPromise,
} = require('internal/crypto/util');

Expand All @@ -40,16 +34,28 @@ const {
InternalCryptoKey,
getCryptoKeyAlgorithm,
getCryptoKeyHandle,
getKeyObjectHandle,
getKeyObjectSymmetricKeySize,
} = require('internal/crypto/keys');

const {
importJwkSecretKey,
importSecretKey,
validateJwk,
validateKeyUsages,
validateUsagesNotEmpty,
} = require('internal/crypto/webcrypto_util');

const kCipherUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
const kWrapUsages = ['wrapKey', 'unwrapKey'];

const kUsages = {
'__proto__': null,
'AES-CBC': kCipherUsages,
'AES-CTR': kCipherUsages,
'AES-GCM': kCipherUsages,
'AES-KW': kWrapUsages,
'AES-OCB': kCipherUsages,
};

function getAlgorithmName(name, length) {
switch (name) {
case 'AES-CBC': return `A${length}CBC`;
Expand Down Expand Up @@ -178,21 +184,8 @@ function aesCipher(mode, key, data, algorithm) {
function aesGenerateKey(algorithm, extractable, usages) {
const { name, length } = algorithm;

const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW')
ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt');

const usagesSet = new SafeSet(usages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
if (usagesSet.size === 0) {
throw lazyDOMException(
'Usages cannot be empty when creating a key.',
'SyntaxError');
}
const usagesSet = validateUsagesNotEmpty(
validateKeyUsages(usages, kUsages[name], name));

return jobPromise(() => new SecretKeyGenJob(
kCryptoJobWebCrypto,
Expand All @@ -209,24 +202,15 @@ function aesImportKey(
extractable,
usages) {
const { name } = algorithm;
const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW')
ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt');

const usagesSet = new SafeSet(usages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
const usagesSet = validateKeyUsages(usages, kUsages[name], name);

let handle;
let length;
switch (format) {
case 'KeyObject': {
length = getKeyObjectSymmetricKeySize(keyData) * 8;
case 'KeyObjectHandle': {
length = keyData.getSymmetricKeySize() * 8;
validateKeyLength(length);
handle = getKeyObjectHandle(keyData);
handle = keyData;
break;
}
case 'raw-secret':
Expand Down
122 changes: 39 additions & 83 deletions lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
kCryptoJobWebCrypto,
kKeyFormatDER,
kKeyFormatRawPublic,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
kWebCryptoKeyFormatPKCS8,
Expand All @@ -27,8 +28,6 @@ const {

const {
getUsagesMask,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
} = require('internal/crypto/util');

Expand All @@ -39,66 +38,37 @@ const {
const {
getCryptoKeyHandle,
getCryptoKeyType,
getKeyObjectHandle,
getKeyObjectType,
InternalCryptoKey,
} = require('internal/crypto/keys');

const {
createKeyUsages,
getKeyPairUsages,
importDerKey,
importJwkKey,
importRawKey,
validateJwk,
validateKeyUsages,
validateUsagesNotEmpty,
verifyAcceptableKeyUse,
} = require('internal/crypto/webcrypto_util');

function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
let checkSet;
switch (name) {
case 'X25519':
// Fall through
case 'X448':
checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
break;
case 'Ed25519':
// Fall through
case 'Ed448':
checkSet = isPublic ? ['verify'] : ['sign'];
break;
default:
throw lazyDOMException(
'The algorithm is not supported', 'NotSupportedError');
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}
const kDeriveUsages = createKeyUsages([], ['deriveKey', 'deriveBits']);

const kSignVerifyUsages = createKeyUsages(['verify'], ['sign']);

const kUsages = {
'__proto__': null,
'X25519': kDeriveUsages,
'X448': kDeriveUsages,
'Ed25519': kSignVerifyUsages,
'Ed448': kSignVerifyUsages,
};

function cfrgGenerateKey(algorithm, extractable, usages) {
const { name } = algorithm;

const usageSet = new SafeSet(usages);
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
case 'X25519':
// Fall through
case 'X448':
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
}
const allowedUsages = kUsages[name];
const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name);
const nid = {
'__proto__': null,
'Ed25519': EVP_PKEY_ED25519,
Expand All @@ -107,37 +77,16 @@ function cfrgGenerateKey(algorithm, extractable, usages) {
'X448': EVP_PKEY_X448,
}[name];

let publicUsages;
let privateUsages;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
case 'X25519':
// Fall through
case 'X448':
publicUsages = new SafeSet();
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}

const keyAlgorithm = { name };

if (privateUsages.size === 0) {
throw lazyDOMException(
'Usages cannot be empty when creating a key.',
'SyntaxError');
}
const keyUsages = getKeyPairUsages(usagesSet, allowedUsages);
validateUsagesNotEmpty(keyUsages.private);

return jobPromise(() => new NidKeyPairGenJob(
kCryptoJobWebCrypto,
nid,
keyAlgorithm,
getUsagesMask(publicUsages),
getUsagesMask(privateUsages),
getUsagesMask(keyUsages.public),
getUsagesMask(keyUsages.private),
extractable));
}

Expand Down Expand Up @@ -176,21 +125,25 @@ function cfrgImportKey(

const { name } = algorithm;
let handle;
const allowedUsages = kUsages[name];
const usagesSet = new SafeSet(usages);
switch (format) {
case 'KeyObject': {
verifyAcceptableCfrgKeyUse(
name, getKeyObjectType(keyData) === 'public', usagesSet);
handle = getKeyObjectHandle(keyData);
case 'KeyObjectHandle':
verifyAcceptableKeyUse(
name,
usagesSet,
keyData.getKeyType() === kKeyTypePublic ?
allowedUsages.public :
allowedUsages.private);
handle = keyData;
break;
}
case 'spki': {
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public);
handle = importDerKey(keyData, true);
break;
}
case 'pkcs8': {
verifyAcceptableCfrgKeyUse(name, false, usagesSet);
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.private);
handle = importDerKey(keyData, false);
break;
}
Expand All @@ -209,7 +162,10 @@ function cfrgImportKey(
}

const isPublic = keyData.d === undefined;
verifyAcceptableCfrgKeyUse(name, isPublic, usagesSet);
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);
handle = importJwkKey(isPublic, keyData);

if (!isPublic) {
Expand All @@ -220,7 +176,7 @@ function cfrgImportKey(
break;
}
case 'raw': {
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public);
handle = importRawKey(true, keyData, kKeyFormatRawPublic, name);
break;
}
Expand Down
Loading