UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

362 lines (329 loc) 8.97 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/cfrg.js import { Buffer } from "nstdlib/lib/buffer"; import { ECKeyExportJob, KeyObjectHandle, SignJob, kCryptoJobAsync, kKeyTypePrivate, kKeyTypePublic, kSignJobModeSign, kSignJobModeVerify, } from "nstdlib/stub/binding/crypto"; import { getUsagesUnion, hasAnyNotIn, jobPromise, validateKeyOps, kHandle, kKeyObject, } from "nstdlib/lib/internal/crypto/util"; import { emitExperimentalWarning, 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 verifyAcceptableCfrgKeyUse(name, isPublic, usages) { let checkSet; switch (name) { case "X25519": // Fall through case "X448": checkSet = isPublic ? [] : ["deriveKey", "deriveBits"]; break; case "Ed25519": // Fall through case "Ed448": 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 createCFRGRawKey(name, keyData, isPublic) { const handle = new KeyObjectHandle(); switch (name) { case "Ed25519": case "X25519": if (keyData.byteLength !== 32) { throw lazyDOMException( `${name} raw keys must be exactly 32-bytes`, "DataError", ); } break; case "Ed448": if (keyData.byteLength !== 57) { throw lazyDOMException( `${name} raw keys must be exactly 57-bytes`, "DataError", ); } break; case "X448": if (keyData.byteLength !== 56) { throw lazyDOMException( `${name} raw keys must be exactly 56-bytes`, "DataError", ); } break; } const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; if (!handle.initEDRaw(name, keyData, keyType)) { throw lazyDOMException("Invalid keyData", "DataError"); } 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 Set(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; } let genKeyType; switch (name) { case "Ed25519": genKeyType = "ed25519"; break; case "Ed448": genKeyType = "ed448"; break; case "X25519": genKeyType = "x25519"; break; case "X448": genKeyType = "x448"; break; } const keyPair = await generateKeyPair(genKeyType).catch((err) => { throw lazyDOMException( "The operation failed for an operation-specific reason", { name: "OperationError", cause: err }, ); }); 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 keyAlgorithm = { name }; const publicKey = new InternalCryptoKey( keyPair.publicKey, keyAlgorithm, publicUsages, true, ); const privateKey = new InternalCryptoKey( keyPair.privateKey, keyAlgorithm, privateUsages, extractable, ); return { __proto__: null, privateKey, publicKey }; } 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 Set(keyUsages); switch (format) { case "spki": { verifyAcceptableCfrgKeyUse(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": { verifyAcceptableCfrgKeyUse(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 !== "OKP") throw lazyDOMException('Invalid JWK "kty" Parameter', "DataError"); if (keyData.crv !== name) throw lazyDOMException( 'JWK "crv" Parameter and algorithm name mismatch', "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 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 (!isPublic && typeof keyData.x !== "string") { throw lazyDOMException("Invalid JWK", "DataError"); } verifyAcceptableCfrgKeyUse(name, isPublic, usagesSet); const publicKeyObject = createCFRGRawKey( name, Buffer.from(keyData.x, "base64"), true, ); if (isPublic) { keyObject = publicKeyObject; } else { keyObject = createCFRGRawKey( name, Buffer.from(keyData.d, "base64"), false, ); if (!createPublicKey(keyObject).equals(publicKeyObject)) { throw lazyDOMException("Invalid JWK", "DataError"); } } break; } case "raw": { verifyAcceptableCfrgKeyUse(name, true, usagesSet); keyObject = createCFRGRawKey(name, keyData, true); 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?.byteLength) { 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, ), ); } export { cfrgExportKey }; export { cfrgImportKey }; export { cfrgGenerateKey }; export { eddsaSignVerify };