-
Notifications
You must be signed in to change notification settings - Fork 30.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crypto: add CFRG curves to Web Crypto API
PR-URL: #42507 Reviewed-By: Tobias Nießen <tniessen@tnie.de>
- Loading branch information
1 parent
916e45e
commit 4cf9037
Showing
24 changed files
with
1,898 additions
and
1,334 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
Oops, something went wrong.