Working on Advanced Encryption Standard (AES), between agent backup and server.

This commit is contained in:
charlesgauthereau
2025-10-26 11:54:25 +01:00
parent 2a90d4933d
commit 7d6038e5f1
15 changed files with 200 additions and 22 deletions
+38
View File
@@ -1,2 +1,40 @@
import fs from "node:fs";
import forge from "node-forge";
export async function decryptedDump(file: File, aesKeyHex: string, ivHex: string): Promise<File> {
const privateKeyPem = fs.readFileSync("private/keys/server_private.pem", "utf8");
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
// Decrypt AES key with RSA-OAEP
const encryptedAesKey = forge.util.hexToBytes(aesKeyHex);
const aesKey = privateKey.decrypt(encryptedAesKey, "RSA-OAEP", {
md: forge.md.sha256.create(),
mgf1: { md: forge.md.sha256.create() },
});
// Read encrypted file content
const encryptedBuffer = Buffer.from(await file.arrayBuffer());
const iv = forge.util.hexToBytes(ivHex);
// AES decryption
const decipher = forge.cipher.createDecipher("AES-CBC", aesKey);
decipher.start({ iv });
decipher.update(forge.util.createBuffer(encryptedBuffer.toString("binary")));
const success = decipher.finish();
if (!success) {
throw new Error("Decryption failed");
}
const decryptedBytes = decipher.output.getBytes();
const decryptedBuffer = Buffer.from(decryptedBytes, "binary");
// Return a File so you can use file.arrayBuffer() later
return new File(
[decryptedBuffer],
file.name.replace(/\.enc$/, ".dump"), // rename if needed
{type: "application/octet-stream"}
);
}
+18 -3
View File
@@ -3,12 +3,13 @@ import {isUuidv4} from "@/utils/verify-uuid";
import {uploadLocalPrivate, uploadS3Private} from "@/features/upload/private/upload.action";
import {v4 as uuidv4} from "uuid";
import {eventEmitter} from "../../../events/route";
import * as drizzleDb from "@/db";
import {db} from "@/db";
import {Backup} from "@/db/schema/07_database";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {env} from "@/env.mjs";
import {withUpdatedAt} from "@/db/utils";
import {decryptedDump} from "./helpers";
export async function POST(
request: Request,
@@ -27,9 +28,13 @@ export async function POST(
const agentId = (await params).agentId;
const formData = await request.formData();
const aesKeyHex = formData.get("aes_key") as string;
const ivHex = formData.get("iv") as string;
const generatedId = formData.get("generatedId") as string | null;
const method = formData.get("method") as string | null;
if (!generatedId || !isUuidv4(generatedId)) {
return NextResponse.json(
{error: "generatedId is not a valid UUID"},
@@ -102,6 +107,11 @@ export async function POST(
if (status === "success") {
const file = formData.get("file") as File | null;
if (!aesKeyHex || !ivHex) {
return NextResponse.json({error: "Missing fields"}, {status: 400});
}
if (!file) {
return NextResponse.json(
{error: "File is required for successful backup"},
@@ -109,9 +119,13 @@ export async function POST(
);
}
const decryptedFile = await decryptedDump(file, aesKeyHex, ivHex);
const uuid = uuidv4();
const fileName = `${uuid}.dump`;
const buffer = Buffer.from(await file.arrayBuffer());
// const buffer = Buffer.from(await fileDecrypted.arrayBuffer());
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
// const buffer = fileDecrypted
const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
if (!settings) {
@@ -173,4 +187,5 @@ export async function POST(
{status: 500}
);
}
}
}
+1 -2
View File
@@ -124,13 +124,12 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
try {
if (settings.storage == "local") {
data = await getFileUrlPresignedLocal(fileName!)
data = await getFileUrlPresignedLocal({fileName: fileName!})
} else if (settings.storage == "s3") {
data = await getFileUrlPreSignedS3Action(`backups/${backupToRestore?.database.project?.slug}/${fileName}`);
}
if (data?.data?.success) {
urlBackup = data.data.value ?? "";
} else {
+1 -1
View File
@@ -20,7 +20,7 @@ export type Body = {
// Function to test the get file url presigned local
export async function GET(request: Request) {
const url = await getFileUrlPresignedLocal("d4a7fa35-2506-4d01-a612-a8ef2e2cc1c5.dump")
const url = await getFileUrlPresignedLocal({fileName:"d4a7fa35-2506-4d01-a612-a8ef2e2cc1c5.dump"})
return Response.json({
message: url
})
+12 -7
View File
@@ -12,16 +12,21 @@ export async function GET(
const expires = searchParams.get('expires');
const fileName = (await params).fileName
const privateLocalDir = "private/uploads/files/";
const filePath = path.join(privateLocalDir, fileName);
console.log(token);
console.log(fileName);
const uploadsDir = "private/uploads/files/";
const keysDir = "private/keys/";
const uploadPath = path.join(uploadsDir, fileName);
const keyPath = path.join(keysDir, fileName);
const crypto = require('crypto');
if (!fs.existsSync(filePath)) {
return NextResponse.json(
{error: 'File not found'},
{status: 404}
);
let filePath = uploadPath;
if (!fs.existsSync(uploadPath)) {
if (fs.existsSync(keyPath)) filePath = keyPath;
else
return NextResponse.json({error: "File not found"}, {status: 404});
}
const expectedToken = crypto.createHash('sha256').update(`${fileName}${expires}`).digest('hex');