Skip to content

Commit

Permalink
crypto: check ed/x webcrypto key import algorithm names
Browse files Browse the repository at this point in the history
PR-URL: #37305
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
panva authored and targos committed Feb 28, 2021
1 parent bb81acc commit 291d9e9
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 14 deletions.
4 changes: 2 additions & 2 deletions doc/api/webcrypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1697,8 +1697,8 @@ added: v15.8.0

* Type: {boolean}

The `public` parameter is used to specify that the key is to be interpreted
as a public key.
The `public` parameter is used to specify that the `'raw'` format key is to be
interpreted as a public key. **Default:** `false`.

### `NODE-SCRYPT` Algorithm
<!-- YAML
Expand Down
54 changes: 46 additions & 8 deletions lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,6 @@ async function ecImportKey(
namedCurve,
'algorithm.namedCurve',
ObjectKeys(kNamedCurveAliases));
// Only used for NODE-EDnnnn key variants to distinguish between
// importing a raw public key or raw private key.
if (algorithm.public !== undefined)
validateBoolean(algorithm.public, 'algorithm.public');
let keyObject;
const usagesSet = new SafeSet(keyUsages);
let checkNamedCurve = true;
Expand All @@ -266,6 +262,24 @@ async function ecImportKey(
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');

switch (namedCurve) {
case 'NODE-X25519':
// Fall through
case 'NODE-X448':
checkNamedCurve = false;
if (algorithm.name !== 'ECDH')
throw lazyDOMException('Invalid algorithm name.', 'DataError');
break;
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
checkNamedCurve = false;
if (algorithm.name !== namedCurve)
throw lazyDOMException('Invalid algorithm name.', 'DataError');
break;
}

verifyAcceptableEcKeyUse(name, keyData.type, usagesSet);
keyObject = keyData;
break;
Expand Down Expand Up @@ -296,8 +310,24 @@ async function ecImportKey(
case 'OKP': {
checkNamedCurve = false;
const isPublic = keyData.d === undefined;
const type =
namedCurve === 'NODE-X25519' || 'NODE-X448' ? 'ECDH' : 'ECDSA';

let type;
switch (namedCurve) {
case 'NODE-ED25519':
// Fall through
case 'NODE-ED448':
type = `NODE-${keyData.crv.toUpperCase()}`;
break;
case 'NODE-X25519':
// Fall through
case 'NODE-X448':
type = 'ECDH';
break;
}

if (algorithm.name !== type)
throw lazyDOMException('Invalid algorithm name.', 'DataError');

verifyAcceptableEcKeyUse(
type,
isPublic ? 'public' : 'private',
Expand Down Expand Up @@ -364,8 +394,12 @@ async function ecImportKey(
// Fall through
case 'NODE-X448':
checkNamedCurve = false;
if (algorithm.public !== undefined)
validateBoolean(algorithm.public, 'algorithm.public');
if (algorithm.name !== 'ECDH')
throw lazyDOMException('Invalid algorithm name.', 'DataError');
verifyAcceptableEcKeyUse(
'ECDH',
algorithm.name,
algorithm.public === true ? 'public' : 'private',
usagesSet);
keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
Expand All @@ -374,8 +408,12 @@ async function ecImportKey(
// Fall through
case 'NODE-ED448':
checkNamedCurve = false;
if (algorithm.public !== undefined)
validateBoolean(algorithm.public, 'algorithm.public');
if (algorithm.name !== namedCurve)
throw lazyDOMException('Invalid algorithm name.', 'DataError');
verifyAcceptableEcKeyUse(
'ECDSA',
algorithm.name,
algorithm.public === true ? 'public' : 'private',
usagesSet);
keyObject = createECRawKey(namedCurve, keyData, algorithm.public);
Expand Down
54 changes: 53 additions & 1 deletion test/parallel/test-webcrypto-ed25519-ed448.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const { subtle } = require('crypto').webcrypto;
const {
generateKeyPairSync,
webcrypto: { subtle }
} = require('crypto');

async function generateKey(namedCurve) {
return subtle.generateKey(
Expand Down Expand Up @@ -365,3 +368,52 @@ assert.rejects(
{
message: /Unsupported named curves for ECDSA/
});

{
for (const asymmetricKeyType of ['ed25519', 'ed448']) {
const { publicKey, privateKey } = generateKeyPairSync(asymmetricKeyType);
for (const keyObject of [publicKey, privateKey]) {
const namedCurve = `NODE-${asymmetricKeyType.toUpperCase()}`;
subtle.importKey(
'node.keyObject',
keyObject,
{ name: namedCurve, namedCurve },
true,
keyObject.type === 'private' ? ['sign'] : ['verify'],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, namedCurve);
}, common.mustNotCall());

assert.rejects(
subtle.importKey(
'node.keyObject',
keyObject,
{
name: 'ECDSA',
namedCurve,
},
true,
keyObject.type === 'private' ? ['sign'] : ['verify']
),
{
message: /Invalid algorithm name/
});

assert.rejects(
subtle.importKey(
'node.keyObject',
keyObject,
{
name: 'ECDH',
namedCurve,
},
true,
keyObject.type === 'private' ? ['deriveBits', 'deriveKey'] : [],
),
{
message: /Invalid algorithm name/
});
}
}
}
27 changes: 24 additions & 3 deletions test/parallel/test-webcrypto-x25519-x448.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const { subtle } = require('crypto').webcrypto;

const {
generateKeyPairSync,
webcrypto: { subtle }
} = require('crypto');

// X25519 and X448 are ECDH named curves that should work
// with the existing ECDH mechanisms with no additional
Expand Down Expand Up @@ -260,7 +264,7 @@ assert.rejects(
namedCurve: 'NODE-X25519'
},
true,
['deriveBits']).then(common.mustCall());
['deriveBits']).then(common.mustCall(), common.mustNotCall());

// Public JWK import
subtle.importKey(
Expand All @@ -275,5 +279,22 @@ assert.rejects(
namedCurve: 'NODE-X25519'
},
true,
[]).then(common.mustCall());
[]).then(common.mustCall(), common.mustNotCall());

for (const asymmetricKeyType of ['x25519', 'x448']) {
const { publicKey, privateKey } = generateKeyPairSync(asymmetricKeyType);
for (const keyObject of [publicKey, privateKey]) {
const namedCurve = `NODE-${asymmetricKeyType.toUpperCase()}`;
subtle.importKey(
'node.keyObject',
keyObject,
{ name: 'ECDH', namedCurve },
true,
keyObject.type === 'private' ? ['deriveBits', 'deriveKey'] : [],
).then((cryptoKey) => {
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, 'ECDH');
}, common.mustNotCall());
}
}
}

0 comments on commit 291d9e9

Please sign in to comment.