feat(infisical-pg): completed root folder level secret, secret import and folder

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:23 +05:30
parent 8a6ab7f2f6
commit 0e1191f2ea
70 changed files with 3105 additions and 278 deletions
+184
View File
@@ -0,0 +1,184 @@
import crypto from "node:crypto";
import * as argon2 from "argon2";
import nacl from "tweetnacl";
import naclUtils from "tweetnacl-util";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
export type TDecryptSymmetricInput = {
ciphertext: string;
iv: string;
tag: string;
key: string;
};
export const IV_BYTES_SIZE = 12;
export const BLOCK_SIZE_BYTES_16 = 16;
export const decryptSymmetric = ({ ciphertext, iv, tag, key }: TDecryptSymmetricInput): string => {
const secretKey = crypto.createSecretKey(key, "base64");
const decipher = crypto.createDecipheriv(
SecretEncryptionAlgo.AES_256_GCM,
secretKey,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
};
export const encryptSymmetric = (plaintext: string, key: string) => {
const iv = crypto.randomBytes(IV_BYTES_SIZE);
const secretKey = crypto.createSecretKey(key, "base64");
const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, secretKey, iv);
let ciphertext = cipher.update(plaintext, "utf8", "base64");
ciphertext += cipher.final("base64");
return {
ciphertext,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64")
};
};
export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv);
let ciphertext = cipher.update(plaintext, "utf8", "base64");
ciphertext += cipher.final("base64");
return {
ciphertext,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64")
};
};
export const decryptSymmetric128BitHexKeyUTF8 = ({
ciphertext,
iv,
tag,
key
}: TDecryptSymmetricInput): string => {
const decipher = crypto.createDecipheriv(
SecretEncryptionAlgo.AES_256_GCM,
key,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
};
export const encryptAsymmetric = (plaintext: string, publicKey: string, privateKey: string) => {
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
naclUtils.decodeUTF8(plaintext),
nonce,
naclUtils.decodeBase64(publicKey),
naclUtils.decodeBase64(privateKey)
);
return {
ciphertext: naclUtils.encodeBase64(ciphertext),
nonce: naclUtils.encodeBase64(nonce)
};
};
export type TDecryptAsymmetricInput = {
ciphertext: string;
nonce: string;
publicKey: string;
privateKey: string;
};
export const decryptAsymmetric = ({
ciphertext,
nonce,
publicKey,
privateKey
}: TDecryptAsymmetricInput) => {
const plaintext: Uint8Array | null = nacl.box.open(
naclUtils.decodeBase64(ciphertext),
naclUtils.decodeBase64(nonce),
naclUtils.decodeBase64(publicKey),
naclUtils.decodeBase64(privateKey)
);
if (plaintext == null) throw Error("Invalid ciphertext or keys");
return naclUtils.encodeUTF8(plaintext);
};
export type TGenSecretBlindIndex = {
secretName: string;
keyEncoding: SecretKeyEncoding;
rootEncryptionKey?: string;
encryptionKey?: string;
iv: string;
tag: string;
ciphertext: string;
};
export const buildSecretBlindIndexFromName = async ({
secretName,
ciphertext,
keyEncoding,
iv,
tag,
encryptionKey,
rootEncryptionKey
}: TGenSecretBlindIndex) => {
if (!encryptionKey && !rootEncryptionKey) throw new Error("Missing secret blind index key");
let salt = "";
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
salt = decryptSymmetric({ iv, ciphertext, key: rootEncryptionKey, tag });
} else if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) {
salt = decryptSymmetric128BitHexKeyUTF8({ iv, ciphertext, key: encryptionKey, tag });
}
if (!salt) throw new Error("Missing secret blind index key");
const secretBlindIndex = await argon2.hash(secretName, {
type: argon2.argon2id,
salt: Buffer.from(salt, "base64"),
saltLength: 16, // default 16 bytes
memoryCost: 65536, // default pool of 64 MiB per thread.
hashLength: 32,
parallelism: 1,
raw: true
});
return secretBlindIndex.toString("base64");
};
export const createSecretBlindIndex = (rootEncryptionKey?: string, encryptionKey?: string) => {
if (!encryptionKey && !rootEncryptionKey) throw new Error("Atleast one encryption key needed");
const salt = crypto.randomBytes(16).toString("base64");
if (rootEncryptionKey) {
const data = encryptSymmetric(salt, rootEncryptionKey);
return {
...data,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.BASE64
};
}
if (encryptionKey) {
const data = encryptSymmetric128BitHexKeyUTF8(salt, encryptionKey);
return {
...data,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
};
}
throw new Error("Failed to generate blind index due to encryption key missing");
};
+10
View File
@@ -1 +1,11 @@
export {
buildSecretBlindIndexFromName,
createSecretBlindIndex,
decryptAsymmetric,
decryptSymmetric,
decryptSymmetric128BitHexKeyUTF8,
encryptAsymmetric,
encryptSymmetric,
encryptSymmetric128BitHexKeyUTF8
} from "./encryption";
export { generateSrpServerKey, srpCheckClientProof } from "./srp";
+2 -2
View File
@@ -28,9 +28,9 @@ export class BadRequestError extends Error {
error: unknown;
constructor({ name, error, message }: { message?: string; name: string; error?: unknown }) {
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
super(message ?? "The request is invalid");
this.name = name;
this.name = name || "";
this.error = error;
}
}
+1 -3
View File
@@ -1,8 +1,6 @@
import { Knex } from "knex";
import { Tables } from "knex/types/tables";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "../errors";
export * from "./join";
@@ -19,7 +17,7 @@ export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
// What is ormify
// It is to inject typical operations like find, findOne, update, delete, create
// This will avoid writing most common ones each time
export const ormify = <DbOps extends object, Tname extends TableName>(
export const ormify = <DbOps extends object, Tname extends keyof Tables>(
db: Knex,
tableName: Tname,
dal?: DbOps
-1
View File
@@ -25,7 +25,6 @@ export const mergeOneToManyRelation = <
prevPkId = pk;
prevPkIndex += 1;
}
console.log(prevPkIndex, prevPkId);
groupedRecord[prevPkIndex][childKey].push(childMapper(row));
}
return groupedRecord;