Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add KeyObject.prototype.toCryptoKey #55262

Merged
merged 1 commit into from
Oct 6, 2024
Merged
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
19 changes: 19 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,24 @@ added: v11.6.0
For secret keys, this property represents the size of the key in bytes. This
property is `undefined` for asymmetric keys.

### `keyObject.toCryptoKey(algorithm, extractable, keyUsages)`

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `algorithm`: {AlgorithmIdentifier|RsaHashedImportParams|EcKeyImportParams|HmacImportParams}

<!--lint enable maximum-line-length remark-lint-->

* `extractable`: {boolean}
* `keyUsages`: {string\[]} See [Key usages][].
* Returns: {CryptoKey}

Converts a `KeyObject` instance to a `CryptoKey`.

### `keyObject.type`

<!-- YAML
Expand Down Expand Up @@ -6087,6 +6105,7 @@ See the [list of SSL OP Flags][] for details.
[FIPS provider from OpenSSL 3]: https://www.openssl.org/docs/man3.0/man7/crypto.html#FIPS-provider
[HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed
[JWK]: https://tools.ietf.org/html/rfc7517
[Key usages]: webcrypto.md#cryptokeyusages
[NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf
[NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
Expand Down
7 changes: 6 additions & 1 deletion lib/internal/crypto/aes.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async function aesGenerateKey(algorithm, extractable, keyUsages) {
extractable);
}

async function aesImportKey(
function aesImportKey(
algorithm,
format,
keyData,
Expand All @@ -266,6 +266,11 @@ async function aesImportKey(
let keyObject;
let length;
switch (format) {
case 'KeyObject': {
validateKeyLength(keyData.symmetricKeySize * 8);
keyObject = keyData;
break;
}
case 'raw': {
validateKeyLength(keyData.byteLength * 8);
keyObject = createSecretKey(keyData);
Expand Down
7 changes: 6 additions & 1 deletion lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function cfrgExportKey(key, format) {
key[kKeyObject][kHandle]));
}

async function cfrgImportKey(
function cfrgImportKey(
format,
keyData,
algorithm,
Expand All @@ -208,6 +208,11 @@ async function cfrgImportKey(
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'KeyObject': {
verifyAcceptableCfrgKeyUse(name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
try {
Expand Down
7 changes: 6 additions & 1 deletion lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ function ecExportKey(key, format) {
key[kKeyObject][kHandle]));
}

async function ecImportKey(
function ecImportKey(
format,
keyData,
algorithm,
Expand All @@ -167,6 +167,11 @@ async function ecImportKey(
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'KeyObject': {
verifyAcceptableEcKeyUse(name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableEcKeyUse(name, true, usagesSet);
try {
Expand Down
159 changes: 159 additions & 0 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
ObjectDefineProperties,
ObjectDefineProperty,
ObjectSetPrototypeOf,
SafeSet,
Symbol,
SymbolToStringTag,
Uint8Array,
Expand Down Expand Up @@ -49,6 +50,8 @@ const {
kKeyObject,
getArrayBufferOrView,
bigIntArrayToUnsignedBigInt,
normalizeAlgorithm,
hasAnyNotIn,
} = require('internal/crypto/util');

const {
Expand All @@ -65,6 +68,7 @@ const {
const {
customInspectSymbol: kInspect,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');

const { inspect } = require('internal/util/inspect');
Expand Down Expand Up @@ -148,6 +152,8 @@ const {
},
});

let webidl;

class SecretKeyObject extends KeyObject {
constructor(handle) {
super('secret', handle);
Expand All @@ -168,6 +174,51 @@ const {
}
return this[kHandle].export();
}

toCryptoKey(algorithm, extractable, keyUsages) {
webidl ??= require('internal/crypto/webidl');
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
extractable = webidl.converters.boolean(extractable);
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);

let result;
switch (algorithm.name) {
case 'HMAC':
result = require('internal/crypto/mac')
.hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
result = require('internal/crypto/aes')
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
break;
case 'HKDF':
// Fall through
case 'PBKDF2':
result = importGenericSecretKey(
algorithm,
'KeyObject',
this,
extractable,
keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}

if (result.usages.length === 0) {
throw lazyDOMException(
`Usages cannot be empty when importing a ${result.type} key.`,
'SyntaxError');
}

return result;
}
}

const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
Expand Down Expand Up @@ -209,6 +260,51 @@ const {
return {};
}
}

toCryptoKey(algorithm, extractable, keyUsages) {
webidl ??= require('internal/crypto/webidl');
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
extractable = webidl.converters.boolean(extractable);
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);

let result;
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
result = require('internal/crypto/rsa')
.rsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
result = require('internal/crypto/ec')
.ecImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
result = require('internal/crypto/cfrg')
.cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}

if (result.type === 'private' && result.usages.length === 0) {
throw lazyDOMException(
`Usages cannot be empty when importing a ${result.type} key.`,
'SyntaxError');
}

return result;
}
}

class PublicKeyObject extends AsymmetricKeyObject {
Expand Down Expand Up @@ -801,6 +897,68 @@ function isCryptoKey(obj) {
return obj != null && obj[kKeyObject] !== undefined;
}

function importGenericSecretKey(
{ name, length },
format,
keyData,
extractable,
keyUsages) {
const usagesSet = new SafeSet(keyUsages);
if (extractable)
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');

if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}

switch (format) {
case 'KeyObject': {
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}

const checkLength = keyData.symmetricKeySize * 8;

// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
return new InternalCryptoKey(keyData, { name }, keyUsages, false);
}
case 'raw': {
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}

const checkLength = keyData.byteLength * 8;

// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}

const keyObject = createSecretKey(keyData);
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
}
}

throw lazyDOMException(
`Unable to import ${name} key with format ${format}`,
'NotSupportedError');
}

module.exports = {
// Public API.
createSecretKey,
Expand All @@ -822,4 +980,5 @@ module.exports = {
PrivateKeyObject,
isKeyObject,
isCryptoKey,
importGenericSecretKey,
};
20 changes: 19 additions & 1 deletion lib/internal/crypto/mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function getAlgorithmName(hash) {
}
}

async function hmacImportKey(
function hmacImportKey(
format,
keyData,
algorithm,
Expand All @@ -96,6 +96,24 @@ async function hmacImportKey(
}
let keyObject;
switch (format) {
case 'KeyObject': {
const checkLength = keyData.symmetricKeySize * 8;

if (checkLength === 0 || algorithm.length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');

// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (algorithm.length !== undefined &&
algorithm.length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}

keyObject = keyData;
break;
}
case 'raw': {
const checkLength = keyData.byteLength * 8;

Expand Down
7 changes: 6 additions & 1 deletion lib/internal/crypto/rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ function rsaExportKey(key, format) {
kRsaVariants[key.algorithm.name]));
}

async function rsaImportKey(
function rsaImportKey(
format,
keyData,
algorithm,
Expand All @@ -209,6 +209,11 @@ async function rsaImportKey(
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
case 'KeyObject': {
verifyAcceptableRsaKeyUse(algorithm.name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet);
try {
Expand Down
Loading
Loading