node/lib/internal/crypto/dsa.js
James M Snell dae283d96f
crypto: refactoring internals, add WebCrypto
Fixes: https://github.com/nodejs/node/issues/678
Refs: https://github.com/nodejs/node/issues/26854

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/35093
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
2020-10-07 17:27:05 -07:00

266 lines
6.2 KiB
JavaScript

'use strict';
const {
Promise,
SafeSet,
} = primordials;
const {
DSAKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
validateUint32,
} = require('internal/validators');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const {
getUsagesUnion,
hasAnyNotIn,
jobPromise,
lazyDOMException,
normalizeHashName,
validateKeyOps,
kKeyObject,
kHandle,
} = require('internal/crypto/util');
function verifyAcceptableDsaKeyUse(name, type, usages) {
let checkSet;
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
if (hasAnyNotIn(usages, ...checkSet)) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
}
async function dsaGenerateKey(
algorithm,
extractable,
keyUsages) {
const {
name,
modulusLength,
divisorLength,
hash
} = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
validateUint32(modulusLength, 'algorithm.modulusLength');
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for a DSA key',
'SyntaxError');
}
return new Promise((resolve, reject) => {
generateKeyPair('dsa', {
modulusLength,
divisorLength,
}, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = {
name,
modulusLength,
divisorLength,
hash: { name: hash.name }
};
const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
getUsagesUnion(usageSet, 'verify'),
true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
getUsagesUnion(usageSet, 'sign'),
extractable);
resolve({ publicKey, privateKey });
});
});
}
function dsaExportKey(key, format) {
return jobPromise(new DSAKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
async function dsaImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
let keyObject;
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');
verifyAcceptableDsaKeyUse(algorithm.name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableDsaKeyUse(algorithm.name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableDsaKeyUse(algorithm.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');
verifyAcceptableDsaKeyUse(
algorithm.name,
keyData.x !== undefined ? 'private' : 'public',
usagesSet);
if (keyData.kty !== 'DSA')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'sig') {
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');
const hash =
normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto);
if (hash !== algorithm.hash.name)
throw lazyDOMException('Hash mismatch', 'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import DSA key with format ${format}`,
'NotSupportedError');
}
const {
modulusLength,
divisorLength,
} = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(keyObject, {
name: algorithm.name,
modulusLength,
divisorLength,
hash: algorithm.hash
}, keyUsages, extractable);
}
function dsaSignVerify(key, data, algorithm, signature) {
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');
return jobPromise(new SignJob(
kCryptoJobAsync,
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
key[kKeyObject][kHandle],
data,
normalizeHashName(key.algorithm.hash.name),
undefined, // Salt-length is not used in DSA
undefined, // Padding is not used in DSA
signature));
}
module.exports = {
dsaExportKey,
dsaGenerateKey,
dsaImportKey,
dsaSignVerify,
};