diff --git a/backend/src/lib/crypto/sign/signing-fns.ts b/backend/src/lib/crypto/sign/signing-fns.ts new file mode 100644 index 000000000..40257d5ab --- /dev/null +++ b/backend/src/lib/crypto/sign/signing-fns.ts @@ -0,0 +1,35 @@ +import crypto from "crypto"; +import fs from "fs/promises"; +import os from "os"; +import path from "path"; + +import { logger } from "@app/lib/logger"; + +const baseDir = path.join(os.tmpdir(), "temporary-signing"); +const randomPath = () => `${crypto.randomBytes(32).toString("hex")}-`; + +export const createTemporaryDirectory = async (name: string) => { + const tempDirPath = path.join(baseDir, `${name}-${randomPath()}-${randomPath()}`); + await fs.mkdir(tempDirPath, { recursive: true }); + + return tempDirPath; +}; + +export const removeTemporaryBaseDirectory = async () => { + await fs.rm(baseDir, { force: true, recursive: true }).catch((err) => { + logger.error(err, `Failed to remove temporary base directory [path=${baseDir}]`); + }); +}; + +export const cleanTemporaryDirectory = async (dirPath: string) => { + await fs.rm(dirPath, { recursive: true, force: true }).catch((err) => { + logger.error(err, `Failed to cleanup temporary directory [path=${dirPath}]`); + }); +}; + +export const writeToTemporaryFile = async (tempDirPath: string, data: string | Buffer) => { + await fs.writeFile(tempDirPath, data, { mode: 0o600 }).catch((err) => { + logger.error(err, `Failed to write to temporary file [path=${tempDirPath}]`); + throw err; + }); +}; diff --git a/backend/src/lib/crypto/sign/signing.ts b/backend/src/lib/crypto/sign/signing.ts index 4b3cdea64..60fbc8055 100644 --- a/backend/src/lib/crypto/sign/signing.ts +++ b/backend/src/lib/crypto/sign/signing.ts @@ -1,13 +1,13 @@ import { execFile } from "child_process"; import crypto from "crypto"; import fs from "fs/promises"; -import os from "os"; import path from "path"; import { promisify } from "util"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +import { cleanTemporaryDirectory, createTemporaryDirectory, writeToTemporaryFile } from "./signing-fns"; import { AsymmetricKeyAlgorithm, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types"; const execFileAsync = promisify(execFile); @@ -30,11 +30,6 @@ const SHA256_DIGEST_LENGTH = 32; const SHA384_DIGEST_LENGTH = 48; const SHA512_DIGEST_LENGTH = 64; -const makeTempDir = async (name: string) => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `${name}-${crypto.randomBytes(16).toString("hex")}-`)); - return tempDir; -}; - /** * Service for cryptographic signing and verification operations using asymmetric keys * @@ -124,14 +119,14 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi }; const $signRsaDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => { - const tempDir = await makeTempDir("kms-rsa-sign"); + const tempDir = await createTemporaryDirectory("kms-rsa-sign"); const digestPath = path.join(tempDir, "digest.bin"); - const keyPath = path.join(tempDir, "private_key.pem"); const sigPath = path.join(tempDir, "signature.bin"); + const keyPath = path.join(tempDir, "key.pem"); try { - await fs.writeFile(digestPath, digest, { mode: 0o600 }); - await fs.writeFile(keyPath, privateKey, { mode: 0o600 }); + await writeToTemporaryFile(digestPath, digest); + await writeToTemporaryFile(keyPath, privateKey); const { stderr } = await execFileAsync( "openssl", @@ -170,19 +165,19 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi return signature; } finally { - await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); + await cleanTemporaryDirectory(tempDir); } }; const $signEccDigest = async (digest: Buffer, privateKey: Buffer, hashAlgorithm: SupportedHashAlgorithm) => { - const tempDir = await makeTempDir("ecc-sign"); + const tempDir = await createTemporaryDirectory("ecc-sign"); const digestPath = path.join(tempDir, "digest.bin"); - const keyPath = path.join(tempDir, "private_key.pem"); + const keyPath = path.join(tempDir, "key.pem"); const sigPath = path.join(tempDir, "signature.bin"); try { - await fs.writeFile(digestPath, digest); - await fs.writeFile(keyPath, privateKey); + await writeToTemporaryFile(digestPath, digest); + await writeToTemporaryFile(keyPath, privateKey); const { stderr } = await execFileAsync( "openssl", @@ -222,7 +217,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi return signature; } finally { - await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); + await cleanTemporaryDirectory(tempDir); } }; @@ -232,15 +227,15 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi publicKey: Buffer, hashAlgorithm: SupportedHashAlgorithm ) => { - const tempDir = await makeTempDir("ecc-signature-verification"); - const pubKeyFile = path.join(tempDir, "public-key.pem"); + const tempDir = await createTemporaryDirectory("ecc-signature-verification"); + const publicKeyFile = path.join(tempDir, "public-key.pem"); const sigFile = path.join(tempDir, "signature.sig"); const digestFile = path.join(tempDir, "digest.bin"); try { - await fs.writeFile(pubKeyFile, publicKey, { mode: 0o600 }); - await fs.writeFile(sigFile, signature, { mode: 0o600 }); - await fs.writeFile(digestFile, digest, { mode: 0o600 }); + await writeToTemporaryFile(publicKeyFile, publicKey); + await writeToTemporaryFile(sigFile, signature); + await writeToTemporaryFile(digestFile, digest); await execFileAsync( "openssl", @@ -250,7 +245,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi "-in", digestFile, "-inkey", - pubKeyFile, + publicKeyFile, "-pubin", // Important for EC public keys "-sigfile", sigFile, @@ -272,7 +267,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } return false; } finally { - await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); + await cleanTemporaryDirectory(tempDir); } }; @@ -282,15 +277,15 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi publicKey: Buffer, hashAlgorithm: SupportedHashAlgorithm ) => { - const tempDir = await makeTempDir("kms-signature-verification"); + const tempDir = await createTemporaryDirectory("kms-signature-verification"); const publicKeyFile = path.join(tempDir, "public-key.pub"); const signatureFile = path.join(tempDir, "signature.sig"); const digestFile = path.join(tempDir, "digest.bin"); try { - await fs.writeFile(publicKeyFile, publicKey, { mode: 0o600 }); - await fs.writeFile(signatureFile, signature, { mode: 0o600 }); - await fs.writeFile(digestFile, digest, { mode: 0o600 }); + await writeToTemporaryFile(publicKeyFile, publicKey); + await writeToTemporaryFile(signatureFile, signature); + await writeToTemporaryFile(digestFile, digest); await execFileAsync( "openssl", @@ -320,7 +315,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi } return false; } finally { - await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); + await cleanTemporaryDirectory(tempDir); } }; diff --git a/backend/src/main.ts b/backend/src/main.ts index d5c54991b..cf5082252 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,6 +9,7 @@ import { runMigrations } from "./auto-start-migrations"; import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; +import { removeTemporaryBaseDirectory } from "./lib/crypto/sign/signing-fns"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; @@ -21,6 +22,8 @@ const run = async () => { const logger = initLogger(); const envConfig = initEnvConfig(logger); + await removeTemporaryBaseDirectory(); + const db = initDbConnection({ dbConnectionUri: envConfig.DB_CONNECTION_URI, dbRootCert: envConfig.DB_ROOT_CERT, @@ -71,6 +74,7 @@ const run = async () => { process.on("SIGINT", async () => { await server.close(); await db.destroy(); + await removeTemporaryBaseDirectory(); hsmModule.finalize(); process.exit(0); }); @@ -79,6 +83,7 @@ const run = async () => { process.on("SIGTERM", async () => { await server.close(); await db.destroy(); + await removeTemporaryBaseDirectory(); hsmModule.finalize(); process.exit(0); });