From 932b770c8f94da0290268fc8ae212f442a2c0c09 Mon Sep 17 00:00:00 2001 From: Christian Risi <75698846+CnF-Gris@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:52:46 +0000 Subject: [PATCH] Added utils for cryptography and filesystem interactions --- src/lib/utils/constants.ts | 4 +- src/lib/utils/filesystem-utils.ts | 29 +++++++++ src/lib/utils/jtw-utils.ts | 103 ++++++++++++++++++++++++++++++ src/lib/utils/openssl-utils.ts | 49 ++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/lib/utils/filesystem-utils.ts create mode 100644 src/lib/utils/openssl-utils.ts diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index 948c151..4f407e3 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -1,3 +1,5 @@ export const PKG = __PKG__ export const DB_PATH = "src/db/db.sqlite" -// TODO: Add jose keys \ No newline at end of file +export const SERVER_PRIVATE_DIR = "src/private" +export const SERVER_PRIVATE_KEY_PATH = `${SERVER_PRIVATE_DIR}/key.pem` +export const SERVER_PUBLIC_KEY_PATH = `${SERVER_PRIVATE_DIR}/pub.pem` \ No newline at end of file diff --git a/src/lib/utils/filesystem-utils.ts b/src/lib/utils/filesystem-utils.ts new file mode 100644 index 0000000..47357de --- /dev/null +++ b/src/lib/utils/filesystem-utils.ts @@ -0,0 +1,29 @@ + +export async function doesFileExists(path: string) { + + const file = Bun.file(path) + return file.exists() + +} + +export async function loadFile(path: string, create?: boolean): Promise { + + if (!create) { + create = false + } + + + if (await doesFileExists(path)) { + return Bun.file(path) + } + + if (create) { + const file = Bun.file(path) + file.write("") + return file + } + + + // UGLY: make more specific + throw new Error("The required file does not exist") +} \ No newline at end of file diff --git a/src/lib/utils/jtw-utils.ts b/src/lib/utils/jtw-utils.ts index e69de29..5eb5853 100644 --- a/src/lib/utils/jtw-utils.ts +++ b/src/lib/utils/jtw-utils.ts @@ -0,0 +1,103 @@ +import * as jose from "jose"; +import { loadFile } from "./filesystem-utils"; +import { SERVER_PRIVATE_KEY_PATH, SERVER_PUBLIC_KEY_PATH } from "./constants"; + + +export class JoseSingleton { + + private static initialized = false + + private static privateKey: CryptoKey + private static publicKey: CryptoKey + + public static async init() { + + JoseSingleton.assureNotInitialized() + + JoseSingleton.privateKey = await JoseSingleton.loadPrivateKey() + JoseSingleton.publicKey = await JoseSingleton.loadPublicKey() + + } + + private static async loadPrivateKey() { + + JoseSingleton.assureNotInitialized() + + const privateKeyFile = await loadFile(SERVER_PRIVATE_KEY_PATH) + return await jose.importPKCS8( + await privateKeyFile.text(), + "ES512" + ) + + } + + private static async loadPublicKey() { + + JoseSingleton.assureNotInitialized() + + const publicKeyFile = await loadFile(SERVER_PUBLIC_KEY_PATH) + return await jose.importPKCS8( + await publicKeyFile.text(), + "ES512" + ) + + } + + public static async signObject(object: any) { + + JoseSingleton.assureInitialized() + + const payload = new TextEncoder().encode( + JSON.stringify(object) + ) + + return await new jose.CompactSign( + payload + ).setProtectedHeader({ + alg: "ES512" + }).sign(JoseSingleton.privateKey) + + } + + public static async verifyObject(jwt: string) { + + JoseSingleton.assureInitialized() + + let _payload: Uint8Array + + try { + const { payload, protectedHeader } = await jose.compactVerify( + jwt, + JoseSingleton.publicKey + ) + _payload = payload + } catch { + return null + } + + return JSON.parse( + new TextDecoder().decode(_payload) + ) + + } + + private static assureInitialized() { + + if (!JoseSingleton.initialized) { + // UGLY: Be specific + throw new Error("JoseSingleton hasn't been initialized") + } + + } + + private static assureNotInitialized() { + + if (JoseSingleton.initialized) { + // UGLY: Be specific + throw new Error("JoseSingleton has already been initialized") + } + + } + +} + diff --git a/src/lib/utils/openssl-utils.ts b/src/lib/utils/openssl-utils.ts new file mode 100644 index 0000000..117e6b6 --- /dev/null +++ b/src/lib/utils/openssl-utils.ts @@ -0,0 +1,49 @@ +import { $ } from "bun"; +import { doesFileExists, loadFile } from "./filesystem-utils"; +import { SERVER_PRIVATE_KEY_PATH, SERVER_PUBLIC_KEY_PATH } from "./constants"; + +export async function openSSLInit() { + + await openSSLCreatePrivateKey() + await openSSLCreatePublicKey() + +} + +export async function openSSLCreatePrivateKey() { + + // UGLY: may be refactored to output only the private key + + const outputPromise = $`openssl ecparam -genkey -name secp521r1 -noout`.text() + const filePromise = loadFile(SERVER_PRIVATE_KEY_PATH, true) + + const [output, file] = await Promise.all([ + outputPromise, + filePromise + ]) + + await file.write(output) + +} + + +export async function openSSLCreatePublicKey() { + + // UGLY: may be refactored to output only the private key + if (! await doesFileExists(SERVER_PRIVATE_KEY_PATH)) { + // UGLY: make more specific + throw new Error("You must generate the private key before attempting to generate the public one") + } + + const outputPromise = $`openssl ec -in ${SERVER_PRIVATE_KEY_PATH} -pubout `.text() + const filePromise = loadFile(SERVER_PUBLIC_KEY_PATH, true) + + const [output, file] = await Promise.all([ + outputPromise, + filePromise + ]) + + await file.write(output) + +} + +