Skip to main content
Glama
crypto_interop.ts6.56 kB
import { assert } from "chai"; // polyfill for node if (!Uint8Array.fromBase64) Uint8Array.fromBase64 = (s: string) => new Uint8Array( atob(s) .split("") .map((c) => c.charCodeAt(0)), ); if (!Uint8Array.prototype.toBase64) Uint8Array.prototype.toBase64 = function (this: Uint8Array) { return btoa(String.fromCodePoint(...this)); } as any; const RSA_ALGORITHMS = ["RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP"].flatMap( (name) => [1024, 2048, 3072].flatMap((modulusLength) => [new Uint8Array([3]), new Uint8Array([1, 0, 1])].map( (publicExponent) => ({ name, modulusLength, publicExponent, hash: "SHA-256", }), ), ), ); const EC_ALGORITHMS = ["P-256", "P-384", "P-521"].map((namedCurve) => ({ name: "ECDSA", namedCurve, })); const AES_ALGORITHMS = ["AES-CTR", "AES-CBC", "AES-GCM"].flatMap((name) => [128, 192, 256].map((length) => ({ name, length, })), ); const KEY_ALGORITHMS = [ ...RSA_ALGORITHMS, ...EC_ALGORITHMS, { name: "HMAC", hash: "SHA-256" }, ...AES_ALGORITHMS, { name: "Ed25519" }, { name: "X25519" }, ] as const; const usages = (name: string): KeyUsage[] => { switch (name) { case "RSASSA-PKCS1-v1_5": case "RSA-PSS": case "ECDSA": case "HMAC": case "Ed25519": return ["sign", "verify"]; case "RSA-OAEP": case "AES-CTR": case "AES-CBC": case "AES-GCM": return ["decrypt", "encrypt"]; case "X25519": return ["deriveBits"]; default: throw new Error(`unknown ${name}`); } }; const formats = ["jwk", "spki", "pkcs8", "raw"]; const exportKey = async (key: CryptoKey): Promise<any> => { const r: Record<string, any> = {}; for (const format of formats) { try { const exported = await crypto.subtle.exportKey(format as any, key); if ( !("Convex" in globalThis) && key.algorithm.name === "Ed25519" && format === "jwk" ) { // Node.js doesn't write the `alg` (exported as JsonWebKey).alg = "Ed25519"; } const usages = key.usages; if (exported instanceof ArrayBuffer) { r[format] = { data: new Uint8Array(exported).toBase64(), usages }; } else { r[format] = { data: exported, usages }; } } catch (e) { if (e instanceof DOMException) { let exception = e.name; if (!("Convex" in globalThis)) { if ( (key.algorithm.name.startsWith("RSA") && format === "raw") || (["HMAC", "AES-CTR", "AES-CBC", "AES-GCM"].includes( key.algorithm.name, ) && ["spki", "pkcs8"].includes(format)) ) { // Node.js throws InvalidAccessError here, contrary to the WebCrypto spec if (exception === "InvalidAccessError") exception = "NotSupportedError"; } } r[format] = { exception }; } else { throw e; } } } return r; }; const sampleData = Uint8Array.fromBase64("Y29udmV4LnN1Y2tz".repeat(5)); const encryptAlg = (alg: any) => { const a = { ...alg }; if (alg.name === "AES-CTR") { const counter = new ArrayBuffer(16); new Uint8Array(counter).fill(0xff); a.counter = counter; a.length = 2; } if (alg.name === "AES-CBC") { a.iv = new ArrayBuffer(16); } if (alg.name === "AES-GCM") { // we only support 96 bit AES-GCM nonces a.iv = new ArrayBuffer(12); } return a; }; const createVectors = async ( alg: any, key: CryptoKey, vectors: Record<string, any>, ): Promise<void> => { if (key.usages.includes("sign")) { vectors.signature = new Uint8Array( await crypto.subtle.sign( { ...alg, saltLength: 32, hash: "SHA-256" }, key, sampleData, ), ).toBase64(); } if (key.usages.includes("encrypt")) { vectors.encrypted = new Uint8Array( await crypto.subtle.encrypt(encryptAlg(alg), key, sampleData), ).toBase64(); } }; const checkVectors = async ( key: CryptoKey, vectors: Record<string, any>, ): Promise<void> => { const alg: any = key.algorithm; if (key.usages.includes("verify")) { assert.isTrue( await crypto.subtle.verify( { ...alg, saltLength: 32, hash: "SHA-256" }, key, Uint8Array.fromBase64(vectors.signature), sampleData, ), ); } if (key.usages.includes("decrypt")) { assert.deepEqual( new Uint8Array( await crypto.subtle.decrypt( encryptAlg(alg), key, Uint8Array.fromBase64(vectors.encrypted), ), ), sampleData, ); } }; const importAndVerify = async ( alg: any, keys: Record<string, any>, vectors: Record<string, any>, ): Promise<void> => { for (const [format, key] of Object.entries(keys)) { if (typeof key === "object" && "exception" in key) continue; const cryptoKey = await crypto.subtle.importKey( format as any, format === "jwk" ? key.data : Uint8Array.fromBase64(key.data), alg, true, key.usages, ); assert.deepEqual(await exportKey(cryptoKey), keys); checkVectors(cryptoKey, vectors); } }; export const generateData = async () => { const algs: any[] = []; const promises: Promise<void>[] = []; for (const algorithm of KEY_ALGORITHMS) { const data: any = { vectors: {} }; algs.push({ algorithm, data }); // generate keys in parallel for a slight speedup promises.push( (async () => { const k = await crypto.subtle.generateKey( algorithm, true, usages(algorithm.name), ); if (k instanceof CryptoKey) { await createVectors(algorithm, k, data.vectors); data.key = await exportKey(k); } else { await createVectors(algorithm, k.privateKey, data.vectors); await createVectors(algorithm, k.publicKey, data.vectors); data.privateKey = await exportKey(k.privateKey); data.publicKey = await exportKey(k.publicKey); } })(), ); } await Promise.all(promises); return JSON.stringify(algs); }; export const consumeData = async (algsJson: string) => { for (const { algorithm, data } of JSON.parse(algsJson)) { if ("privateKey" in data) { await importAndVerify(algorithm, data.privateKey, data.vectors); await importAndVerify(algorithm, data.publicKey, data.vectors); } else { await importAndVerify(algorithm, data.key, data.vectors); } } };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server