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 CFRG curves to Web Crypto API #42507

Merged
merged 23 commits into from
Jun 4, 2022
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
341 changes: 129 additions & 212 deletions doc/api/webcrypto.md

Large diffs are not rendered by default.

369 changes: 369 additions & 0 deletions lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
'use strict';

const {
Promise,
SafeSet,
} = primordials;

const { Buffer } = require('buffer');

const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');

const {
codes: {
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');

const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');

const {
emitExperimentalWarning,
lazyDOMException,
} = require('internal/util');

const {
generateKeyPair,
} = require('internal/crypto/keygen');

const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
isKeyObject,
} = require('internal/crypto/keys');

function verifyAcceptableCfrgKeyUse(name, type, usages) {
let checkSet;
switch (name) {
case 'X25519':
// Fall through
case 'X448':
checkSet = ['deriveKey', 'deriveBits'];
break;
case 'Ed25519':
// Fall through
case 'Ed448':
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}

function createECPublicKeyRaw(name, keyData) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');
if (handle.initECRaw(name.toLowerCase(), keyData))
return new PublicKeyObject(handle);
}

function createCFRGRawKey(name, keyData, isPublic) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');

switch (name) {
case 'Ed25519':
case 'X25519':
if (keyData.byteLength !== 32) {
throw lazyDOMException(
`${name} raw keys must be exactly 32-bytes`);
}
break;
case 'Ed448':
if (keyData.byteLength !== 57) {
throw lazyDOMException(
`${name} raw keys must be exactly 57-bytes`);
}
break;
case 'X448':
if (keyData.byteLength !== 56) {
throw lazyDOMException(
`${name} raw keys must be exactly 56-bytes`);
}
break;
}

const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(name, keyData, keyType)) {
throw lazyDOMException('Failure to generate key object');
}

return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
}

async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
const { name } = algorithm;
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);

const usageSet = new SafeSet(keyUsages);
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;
}
return new Promise((resolve, reject) => {
let genKeyType;
switch (name) {
case 'Ed25519':
genKeyType = 'ed25519';
break;
case 'Ed448':
genKeyType = 'ed448';
break;
case 'X25519':
genKeyType = 'x25519';
break;
case 'X448':
genKeyType = 'x448';
break;
}
generateKeyPair(genKeyType, undefined, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}

const algorithm = { 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 = [];
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}

const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
publicUsages,
true);

const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
privateUsages,
extractable);

resolve({ publicKey, privateKey });
});
});
}

function cfrgExportKey(key, format) {
emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`);
return jobPromise(new ECKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}

async function cfrgImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {

const { name } = algorithm;
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableCfrgKeyUse(name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableCfrgKeyUse(name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (keyData.kty !== 'OKP')
throw lazyDOMException('Invalid key type', 'DataError');
const isPublic = keyData.d === undefined;

if (usagesSet.size > 0 && keyData.use !== undefined) {
let checkUse;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
checkUse = 'sig';
break;
case 'X25519':
// Fall through
case 'X448':
checkUse = 'enc';
break;
}
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid use type', 'DataError');
}

validateKeyOps(keyData.key_ops, usagesSet);

if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}

if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (
(name === 'Ed25519' || name === 'Ed448') &&
keyData.alg !== 'EdDSA'
) {
throw lazyDOMException('Invalid alg', 'DataError');
}
}

verifyAcceptableCfrgKeyUse(
name,
isPublic ? 'public' : 'private',
usagesSet);
keyObject = createCFRGRawKey(
name,
Buffer.from(
isPublic ? keyData.x : keyData.d,
'base64'),
isPublic);
break;
}
case 'raw': {
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
keyObject = createECPublicKeyRaw(name, keyData);
if (keyObject === undefined)
throw lazyDOMException('Unable to import CFRG key', 'OperationError');
break;
}
}

if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
throw lazyDOMException('Invalid key type', 'DataError');
}

return new InternalCryptoKey(
keyObject,
{ name },
keyUsages,
extractable);
}

function eddsaSignVerify(key, data, { name, context }, signature) {
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';

if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');

if (name === 'Ed448' && context !== undefined) {
context =
getArrayBufferOrView(context, 'algorithm.context');
if (context.byteLength !== 0) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
}

return jobPromise(new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
undefined,
undefined,
undefined,
undefined,
signature));
}

module.exports = {
cfrgExportKey,
cfrgImportKey,
cfrgGenerateKey,
eddsaSignVerify,
};
Loading