nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
311 lines (276 loc) • 8.16 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/ec.js
import {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
kSigEncP1363,
} from "nstdlib/stub/binding/crypto";
import {
getUsagesUnion,
hasAnyNotIn,
jobPromise,
normalizeHashName,
validateKeyOps,
kHandle,
kKeyObject,
kNamedCurveAliases,
} from "nstdlib/lib/internal/crypto/util";
import { lazyDOMException, promisify } from "nstdlib/lib/internal/util";
import { generateKeyPair as _generateKeyPair } from "nstdlib/lib/internal/crypto/keygen";
import {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
} from "nstdlib/lib/internal/crypto/keys";
const generateKeyPair = promisify(_generateKeyPair);
function verifyAcceptableEcKeyUse(name, isPublic, usages) {
let checkSet;
switch (name) {
case "ECDH":
checkSet = isPublic ? [] : ["deriveKey", "deriveBits"];
break;
case "ECDSA":
checkSet = isPublic ? ["verify"] : ["sign"];
break;
default:
throw lazyDOMException(
"The algorithm is not supported",
"NotSupportedError",
);
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
"SyntaxError",
);
}
}
function createECPublicKeyRaw(namedCurve, keyData) {
const handle = new KeyObjectHandle();
if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
throw lazyDOMException("Invalid keyData", "DataError");
}
return new PublicKeyObject(handle);
}
async function ecGenerateKey(algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
if (
!Array.prototype.includes.call(Object.keys(kNamedCurveAliases), namedCurve)
) {
throw lazyDOMException("Unrecognized namedCurve", "NotSupportedError");
}
const usageSet = new Set(keyUsages);
switch (name) {
case "ECDSA":
if (hasAnyNotIn(usageSet, ["sign", "verify"])) {
throw lazyDOMException(
"Unsupported key usage for an ECDSA key",
"SyntaxError",
);
}
break;
case "ECDH":
if (hasAnyNotIn(usageSet, ["deriveKey", "deriveBits"])) {
throw lazyDOMException(
"Unsupported key usage for an ECDH key",
"SyntaxError",
);
}
// Fall through
}
const keypair = await generateKeyPair("ec", { namedCurve }).catch((err) => {
throw lazyDOMException(
"The operation failed for an operation-specific reason",
{ name: "OperationError", cause: err },
);
});
let publicUsages;
let privateUsages;
switch (name) {
case "ECDSA":
publicUsages = getUsagesUnion(usageSet, "verify");
privateUsages = getUsagesUnion(usageSet, "sign");
break;
case "ECDH":
publicUsages = [];
privateUsages = getUsagesUnion(usageSet, "deriveKey", "deriveBits");
break;
}
const keyAlgorithm = { name, namedCurve };
const publicKey = new InternalCryptoKey(
keypair.publicKey,
keyAlgorithm,
publicUsages,
true,
);
const privateKey = new InternalCryptoKey(
keypair.privateKey,
keyAlgorithm,
privateUsages,
extractable,
);
return { __proto__: null, publicKey, privateKey };
}
function ecExportKey(key, format) {
return jobPromise(
() => new ECKeyExportJob(kCryptoJobAsync, format, key[kKeyObject][kHandle]),
);
}
async function ecImportKey(format, keyData, algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
if (
!Array.prototype.includes.call(Object.keys(kNamedCurveAliases), namedCurve)
) {
throw lazyDOMException("Unrecognized namedCurve", "NotSupportedError");
}
let keyObject;
const usagesSet = new Set(keyUsages);
switch (format) {
case "spki": {
verifyAcceptableEcKeyUse(name, true, usagesSet);
try {
keyObject = createPublicKey({
key: keyData,
format: "der",
type: "spki",
});
} catch (err) {
throw lazyDOMException("Invalid keyData", {
name: "DataError",
cause: err,
});
}
break;
}
case "pkcs8": {
verifyAcceptableEcKeyUse(name, false, usagesSet);
try {
keyObject = createPrivateKey({
key: keyData,
format: "der",
type: "pkcs8",
});
} catch (err) {
throw lazyDOMException("Invalid keyData", {
name: "DataError",
cause: err,
});
}
break;
}
case "jwk": {
if (!keyData.kty) throw lazyDOMException("Invalid keyData", "DataError");
if (keyData.kty !== "EC")
throw lazyDOMException('Invalid JWK "kty" Parameter', "DataError");
if (keyData.crv !== namedCurve)
throw lazyDOMException(
'JWK "crv" does not match the requested algorithm',
"DataError",
);
verifyAcceptableEcKeyUse(name, keyData.d === undefined, usagesSet);
if (usagesSet.size > 0 && keyData.use !== undefined) {
const checkUse = name === "ECDH" ? "enc" : "sig";
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', "DataError");
}
validateKeyOps(keyData.key_ops, usagesSet);
if (
keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true
) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
"DataError",
);
}
if (algorithm.name === "ECDSA" && keyData.alg !== undefined) {
let algNamedCurve;
switch (keyData.alg) {
case "ES256":
algNamedCurve = "P-256";
break;
case "ES384":
algNamedCurve = "P-384";
break;
case "ES512":
algNamedCurve = "P-521";
break;
}
if (algNamedCurve !== namedCurve)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
"DataError",
);
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData, namedCurve);
if (type === undefined)
throw lazyDOMException("Invalid JWK", "DataError");
keyObject =
type === kKeyTypePrivate
? new PrivateKeyObject(handle)
: new PublicKeyObject(handle);
break;
}
case "raw": {
verifyAcceptableEcKeyUse(name, true, usagesSet);
keyObject = createECPublicKeyRaw(namedCurve, keyData);
break;
}
}
switch (algorithm.name) {
case "ECDSA":
// Fall through
case "ECDH":
if (keyObject.asymmetricKeyType !== "ec")
throw lazyDOMException("Invalid key type", "DataError");
break;
}
if (!keyObject[kHandle].checkEcKeyData()) {
throw lazyDOMException("Invalid keyData", "DataError");
}
const { namedCurve: checkNamedCurve } = keyObject[kHandle].keyDetail({});
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
throw lazyDOMException("Named curve mismatch", "DataError");
return new InternalCryptoKey(
keyObject,
{ name, namedCurve },
keyUsages,
extractable,
);
}
function ecdsaSignVerify(key, data, { name, hash }, 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");
const hashname = normalizeHashName(hash.name);
return jobPromise(
() =>
new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
hashname,
undefined, // Salt length, not used with ECDSA
undefined, // PSS Padding, not used with ECDSA
kSigEncP1363,
signature,
),
);
}
export { ecExportKey };
export { ecImportKey };
export { ecGenerateKey };
export { ecdsaSignVerify };