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
11 changes: 6 additions & 5 deletions lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ function cfrgImportKey(
break;
}
case 'jwk': {
const isPublic = keyData.d === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);

const expectedUse = (name === 'X25519' || name === 'X448') ? 'enc' : 'sig';
validateJwk(keyData, 'OKP', extractable, usagesSet, expectedUse);

Expand All @@ -159,11 +165,6 @@ function cfrgImportKey(
'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;
}
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ function ecImportKey(
break;
}
case 'jwk': {
const isPublic = keyData.d === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);

const expectedUse = name === 'ECDH' ? 'enc' : 'sig';
validateJwk(keyData, 'EC', extractable, usagesSet, expectedUse);

Expand All @@ -185,11 +191,6 @@ function ecImportKey(
'DataError');
}

const isPublic = keyData.d === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);
handle = importJwkKey(isPublic, keyData);
break;
}
Expand Down
17 changes: 16 additions & 1 deletion lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const {
const { kMaxLength } = require('buffer');

const {
getDigestSizeInBytes,
jobPromise,
normalizeHashName,
toBuf,
Expand Down Expand Up @@ -142,6 +141,22 @@ function hkdfSync(hash, key, salt, info, length) {
return bits;
}

function getDigestSizeInBytes(name) {
switch (name) {
case 'SHA-1':
return 20;
case 'SHA-256': // Fall through
case 'SHA3-256':
return 32;
case 'SHA-384': // Fall through
case 'SHA3-384':
return 48;
case 'SHA-512': // Fall through
case 'SHA3-512':
return 64;
}
}

function validateHkdfDeriveBitsLength(length, hash) {
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/crypto/ml_dsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,18 @@ function mlDsaImportKey(
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');

const isPublic = keyData.priv === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? kUsages.public : kUsages.private);
handle = importJwkKey(isPublic, keyData);
break;
}
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/crypto/ml_kem.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,18 @@ function mlKemImportKey(
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');

const isPublic = keyData.priv === undefined;
verifyAcceptableKeyUse(
name,
usagesSet,
isPublic ? kUsages.public : kUsages.private);
handle = importJwkKey(isPublic, keyData);
break;
}
Expand Down
38 changes: 17 additions & 21 deletions lib/internal/crypto/rsa.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const {
MathCeil,
TypedArrayPrototypeGetBuffer,
Uint8Array,
} = primordials;
Expand All @@ -24,12 +23,11 @@ const {
} = internalBinding('crypto');

const {
validateInt32,
isInt32,
} = require('internal/validators');

const {
bigIntArrayToUnsignedInt,
getDigestSizeInBytes,
getUsagesMask,
jobPromise,
normalizeHashName,
Expand Down Expand Up @@ -188,6 +186,12 @@ function rsaImportKey(
break;
}
case 'jwk': {
const isPublic = keyData.d === undefined;
verifyAcceptableKeyUse(
algorithm.name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);

const expectedUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig';
validateJwk(keyData, 'RSA', extractable, usagesSet, expectedUse);

Expand All @@ -204,11 +208,6 @@ function rsaImportKey(
'DataError');
}

const isPublic = keyData.d === undefined;
verifyAcceptableKeyUse(
algorithm.name,
usagesSet,
isPublic ? allowedUsages.public : allowedUsages.private);
handle = importJwkKey(isPublic, keyData);
break;
}
Expand Down Expand Up @@ -241,19 +240,16 @@ function rsaSignVerify(key, data, { saltLength }, signature) {
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');

const algorithm = getCryptoKeyAlgorithm(key);
if (algorithm.name === 'RSA-PSS') {
try {
validateInt32(
saltLength,
'algorithm.saltLength',
0,
MathCeil((algorithm.modulusLength - 1) / 8) -
getDigestSizeInBytes(algorithm.hash.name) - 2);
} catch (err) {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
}
// RsaPssParams converts saltLength to an unsigned long, but SignJob only
// accepts int32 values.
if (algorithm.name === 'RSA-PSS' && !isInt32(saltLength)) {
// EMSA-PSS-VERIFY treats an impossible salt length as inconsistent.
if (mode === kSignJobModeVerify)
return false;

throw lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError');
}

return jobPromise(() => new SignJob(
Expand Down
17 changes: 0 additions & 17 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1042,22 +1042,6 @@ function getBlockSize(name) {
}
}

function getDigestSizeInBytes(name) {
switch (name) {
case 'SHA-1':
return 20;
case 'SHA-256': // Fall through
case 'SHA3-256':
return 32;
case 'SHA-384': // Fall through
case 'SHA3-384':
return 48;
case 'SHA-512': // Fall through
case 'SHA3-512':
return 64;
}
}

function validateKeyOps(keyOps, usagesSet) {
if (keyOps === undefined) return;
validateArray(keyOps, 'keyData.key_ops');
Expand Down Expand Up @@ -1132,7 +1116,6 @@ module.exports = {
bigIntArrayToUnsignedBigInt,
bigIntArrayToUnsignedInt,
getBlockSize,
getDigestSizeInBytes,
getStringOption,
getUsagesMask,
getUsagesFromMask,
Expand Down
30 changes: 17 additions & 13 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,45 +482,51 @@ function deriveKeyImpl(
}

function exportKeySpki(key) {
let exporter;
switch (getCryptoKeyAlgorithm(key).name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
return require('internal/crypto/rsa')
.rsaExportKey(key, kWebCryptoKeyFormatSPKI);
exporter = require('internal/crypto/rsa').rsaExportKey;
break;
case 'ECDSA':
// Fall through
case 'ECDH':
return require('internal/crypto/ec')
.ecExportKey(key, kWebCryptoKeyFormatSPKI);
exporter = require('internal/crypto/ec').ecExportKey;
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
return require('internal/crypto/cfrg')
.cfrgExportKey(key, kWebCryptoKeyFormatSPKI);
exporter = require('internal/crypto/cfrg').cfrgExportKey;
break;
case 'ML-DSA-44':
// Fall through
case 'ML-DSA-65':
// Fall through
case 'ML-DSA-87':
return require('internal/crypto/ml_dsa')
.mlDsaExportKey(key, kWebCryptoKeyFormatSPKI);
exporter = require('internal/crypto/ml_dsa').mlDsaExportKey;
break;
case 'ML-KEM-512':
// Fall through
case 'ML-KEM-768':
// Fall through
case 'ML-KEM-1024':
return require('internal/crypto/ml_kem')
.mlKemExportKey(key, kWebCryptoKeyFormatSPKI);
exporter = require('internal/crypto/ml_kem').mlKemExportKey;
break;
default:
return undefined;
}

if (getCryptoKeyType(key) !== 'public')
throw lazyDOMException('Key must be a public key', 'InvalidAccessError');

return exporter(key, kWebCryptoKeyFormatSPKI);
}

function exportKeyPkcs8(key) {
Expand Down Expand Up @@ -767,9 +773,7 @@ function exportKeySync(format, key) {
let result;
switch (format) {
case 'spki': {
if (type === 'public') {
result = exportKeySpki(key);
}
result = exportKeySpki(key);
break;
}
case 'pkcs8': {
Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-webcrypto-export-import-cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,41 @@ async function testImportRaw({ name, publicUsages }) {
await Promise.all(tests);
})().then(common.mustCall());

// JWK key usage validation precedes `key_ops` validation.
(async function() {
for (const { name, publicUsages, privateUsages } of testVectors) {
const jwk = keyData[name].jwk;
const publicJwk = {
kty: jwk.kty,
crv: jwk.crv,
x: jwk.x,
};
const isKeyAgreement = name.startsWith('X');
const invalidUsage = isKeyAgreement ?
privateUsages[0] : publicUsages[0];
const invalidJwk = isKeyAgreement ? publicJwk : jwk;

await assert.rejects(
subtle.importKey(
'jwk',
{ ...invalidJwk, key_ops: [invalidUsage, invalidUsage] },
{ name },
true,
[invalidUsage]),
{ name: 'SyntaxError', message: /Unsupported key usage/ });

const validUsage = privateUsages[0];
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, key_ops: [validUsage, validUsage] },
{ name },
true,
[validUsage]),
{ name: 'DataError', message: 'Duplicate key operation' });
}
})().then(common.mustCall());

{
const rsaPublic = crypto.createPublicKey(
fixtures.readKey('rsa_public_2048.pem'));
Expand Down
Loading
Loading