mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 00:29:34 +00:00
feat: fips inside support (checkpoint)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
|
||||
|
||||
import { crypto, DigestType } from "../crypto/cryptography";
|
||||
|
||||
export const createDigestAuthRequestInterceptor = (
|
||||
axiosInstance: AxiosInstance,
|
||||
username: string,
|
||||
@@ -30,18 +30,13 @@ export const createDigestAuthRequestInterceptor = (
|
||||
const cnonce = crypto.randomBytes(24).toString("hex");
|
||||
const realm = authDetails.find((el) => el[0].toLowerCase().indexOf("realm") > -1)?.[1]?.replaceAll('"', "") || "";
|
||||
const nonce = authDetails.find((el) => el[0].toLowerCase().indexOf("nonce") > -1)?.[1]?.replaceAll('"', "") || "";
|
||||
const ha1 = crypto.createHash("md5").update(`${username}:${realm}:${password}`).digest("hex");
|
||||
const ha1 = crypto.hashing().md5(`${username}:${realm}:${password}`, DigestType.Hex);
|
||||
const path = opts.url;
|
||||
|
||||
const ha2 = crypto
|
||||
.createHash("md5")
|
||||
.update(`${opts.method ?? "GET"}:${path}`)
|
||||
.digest("hex");
|
||||
const ha2 = crypto.hashing().md5(`${opts.method ?? "GET"}:${path}`, DigestType.Hex);
|
||||
|
||||
const response = crypto.hashing().md5(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`, DigestType.Hex);
|
||||
|
||||
const response = crypto
|
||||
.createHash("md5")
|
||||
.update(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`)
|
||||
.digest("hex");
|
||||
const authorization = `Digest username="${username}",realm="${realm}",nonce="${nonce}",uri="${path}",qop="auth",algorithm="MD5",response="${response}",nc="${nonceCount}",cnonce="${cnonce}"`;
|
||||
|
||||
if (opts.headers) {
|
||||
|
||||
@@ -63,7 +63,7 @@ const envSchema = z
|
||||
DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()),
|
||||
DB_NAME: zpStr(z.string().describe("Postgres database name").optional()),
|
||||
DB_READ_REPLICAS: zpStr(z.string().describe("Postgres read replicas").optional()),
|
||||
BCRYPT_SALT_ROUND: z.number().default(12),
|
||||
BCRYPT_SALT_ROUND: z.number().optional(), // note(daniel): this is deprecated, use SALT_ROUNDS instead. only keeping this for backwards compatibility.
|
||||
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
|
||||
SALT_ROUNDS: z.coerce.number().default(10),
|
||||
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
|
||||
@@ -306,6 +306,7 @@ const envSchema = z
|
||||
)
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
SALT_ROUNDS: data.SALT_ROUNDS || data.BCRYPT_SALT_ROUND || 12,
|
||||
DB_READ_REPLICAS: data.DB_READ_REPLICAS
|
||||
? databaseReadReplicaSchema.parse(JSON.parse(data.DB_READ_REPLICAS))
|
||||
: undefined,
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import crypto from "node:crypto";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
|
||||
export const generateCacheKeyFromData = (data: unknown) =>
|
||||
crypto
|
||||
.createHash("md5")
|
||||
.update(JSON.stringify(data))
|
||||
.digest("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
crypto.rawCrypto.createHash("sha256").update(JSON.stringify(data)).digest("base64");
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import crypto from "crypto";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
|
||||
import { SymmetricKeyAlgorithm, TSymmetricEncryptionFns } from "./types";
|
||||
|
||||
const getIvLength = () => {
|
||||
return 12;
|
||||
};
|
||||
|
||||
const getTagLength = () => {
|
||||
return 16;
|
||||
};
|
||||
const IV_LENGTH = 12;
|
||||
const TAG_LENGTH = 16;
|
||||
|
||||
export const symmetricCipherService = (
|
||||
type: SymmetricKeyAlgorithm.AES_GCM_128 | SymmetricKeyAlgorithm.AES_GCM_256
|
||||
): TSymmetricEncryptionFns => {
|
||||
const IV_LENGTH = getIvLength();
|
||||
const TAG_LENGTH = getTagLength();
|
||||
|
||||
const encrypt = (text: Buffer, key: Buffer) => {
|
||||
const iv = crypto.randomBytes(IV_LENGTH);
|
||||
const cipher = crypto.createCipheriv(type, key, iv);
|
||||
const cipher = crypto.rawCrypto.createCipheriv(type, key, iv);
|
||||
|
||||
let encrypted = cipher.update(text);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
@@ -37,7 +29,7 @@ export const symmetricCipherService = (
|
||||
const tag = ciphertextBlob.subarray(-TAG_LENGTH);
|
||||
const encrypted = ciphertextBlob.subarray(IV_LENGTH, -TAG_LENGTH);
|
||||
|
||||
const decipher = crypto.createDecipheriv(type, key, iv);
|
||||
const decipher = crypto.rawCrypto.createDecipheriv(type, key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
// NOTE: DO NOT USE crypto-js ANYWHERE EXCEPT THIS FILE.
|
||||
// We use crypto-js purely to get around our native node crypto FIPS restrictions in FIPS mode.
|
||||
import crypto, { subtle } from "node:crypto";
|
||||
|
||||
import bcrypt from "bcrypt";
|
||||
import cryptoJs from "crypto-js";
|
||||
import nacl from "tweetnacl";
|
||||
import naclUtils from "tweetnacl-util";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||
import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service";
|
||||
|
||||
import { getConfig } from "../config/env";
|
||||
import { logger } from "../logger";
|
||||
|
||||
enum DigestType {
|
||||
Hex = "hex",
|
||||
Base64 = "base64"
|
||||
}
|
||||
|
||||
export enum SymmetricKeySize {
|
||||
Bits128 = "128-bits",
|
||||
Bits256 = "256-bits"
|
||||
}
|
||||
|
||||
type TDecryptSymmetricInput =
|
||||
| {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
key: string | Buffer; // can be hex encoded or buffer
|
||||
keySize: SymmetricKeySize.Bits128;
|
||||
}
|
||||
| {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
key: string; // must be base64 encoded
|
||||
keySize: SymmetricKeySize.Bits256;
|
||||
};
|
||||
|
||||
type TEncryptSymmetricInput =
|
||||
| {
|
||||
plaintext: string;
|
||||
key: string;
|
||||
keySize: SymmetricKeySize.Bits256;
|
||||
}
|
||||
| {
|
||||
plaintext: string;
|
||||
key: string | Buffer;
|
||||
keySize: SymmetricKeySize.Bits128;
|
||||
};
|
||||
|
||||
type TDecryptAsymmetricInput = {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
};
|
||||
|
||||
const IV_BYTES_SIZE = 12;
|
||||
const BLOCK_SIZE_BYTES_16 = 16;
|
||||
|
||||
const hasherFipsValidated = () => {
|
||||
const $hashPassword = (password: string, salt: string, iterations: number, keyLength: number) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
crypto.pbkdf2(password, salt, iterations, keyLength, "sha256", (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(derivedKey.toString("hex"));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const $validatePassword = (
|
||||
inputPassword: string,
|
||||
storedHash: string,
|
||||
salt: string,
|
||||
iterations: number,
|
||||
keyLength: number
|
||||
) => {
|
||||
return $hashPassword(inputPassword, salt, iterations, keyLength).then((hash) => hash === storedHash);
|
||||
};
|
||||
|
||||
const hash = async (password: string, saltRounds: number) => {
|
||||
const salt = crypto.randomBytes(16).toString("hex");
|
||||
const derivedKey = await $hashPassword(password, salt, saltRounds, 32);
|
||||
return `$infisical$${saltRounds}$${salt}$${derivedKey}`;
|
||||
};
|
||||
|
||||
const compare = async (password: string, hashedPassword: string) => {
|
||||
if (!hashedPassword.startsWith("$infisical$")) {
|
||||
throw new Error("Invalid hash format");
|
||||
}
|
||||
|
||||
const [, , iterations, salt, storedHash] = hashedPassword.split("$");
|
||||
return $validatePassword(password, storedHash, salt, Number(iterations), 32);
|
||||
};
|
||||
|
||||
return {
|
||||
hash,
|
||||
compare
|
||||
};
|
||||
};
|
||||
|
||||
const generateAsymmetricKeyPairFipsValidated = () => {
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519");
|
||||
|
||||
return {
|
||||
publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"),
|
||||
privateKey: privateKey.export({ type: "pkcs8", format: "der" }).toString("base64")
|
||||
};
|
||||
};
|
||||
|
||||
const encryptAsymmetricFipsValidated = (data: string, publicKey: string, privateKey: string) => {
|
||||
const pubKeyObj = crypto.createPublicKey({
|
||||
key: Buffer.from(publicKey, "base64"),
|
||||
type: "spki",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
const privKeyObj = crypto.createPrivateKey({
|
||||
key: Buffer.from(privateKey, "base64"),
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
// Generate shared secret using X25519
|
||||
const sharedSecret = crypto.diffieHellman({
|
||||
privateKey: privKeyObj,
|
||||
publicKey: pubKeyObj
|
||||
});
|
||||
|
||||
// Generate 24-byte nonce (same as NaCl)
|
||||
const nonce = crypto.randomBytes(24);
|
||||
|
||||
// Derive 32-byte key from shared secret
|
||||
const key = crypto.createHash("sha256").update(sharedSecret).digest();
|
||||
|
||||
// Use first 12 bytes of nonce as IV for AES-GCM
|
||||
const iv = nonce.subarray(0, 12);
|
||||
|
||||
// Encrypt with AES-256-GCM
|
||||
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
||||
|
||||
const ciphertext = cipher.update(data, "utf8");
|
||||
cipher.final();
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// Combine ciphertext and auth tag
|
||||
const combined = Buffer.concat([ciphertext, authTag]);
|
||||
|
||||
return {
|
||||
ciphertext: combined.toString("base64"),
|
||||
nonce: nonce.toString("base64")
|
||||
};
|
||||
};
|
||||
|
||||
const decryptAsymmetricFipsValidated = ({
|
||||
ciphertext,
|
||||
nonce,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}) => {
|
||||
// Convert base64 keys back to key objects
|
||||
const pubKeyObj = crypto.createPublicKey({
|
||||
key: Buffer.from(publicKey, "base64"),
|
||||
type: "spki",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
const privKeyObj = crypto.createPrivateKey({
|
||||
key: Buffer.from(privateKey, "base64"),
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
// Generate same shared secret
|
||||
const sharedSecret = crypto.diffieHellman({
|
||||
privateKey: privKeyObj,
|
||||
publicKey: pubKeyObj
|
||||
});
|
||||
|
||||
const nonceBuffer = Buffer.from(nonce, "base64");
|
||||
const combinedBuffer = Buffer.from(ciphertext, "base64");
|
||||
|
||||
// Split ciphertext and auth tag (last 16 bytes for GCM)
|
||||
const actualCiphertext = combinedBuffer.subarray(0, -16);
|
||||
const authTag = combinedBuffer.subarray(-16);
|
||||
|
||||
// Derive same 32-byte key
|
||||
const key = crypto.createHash("sha256").update(sharedSecret).digest();
|
||||
|
||||
// Use first 12 bytes of nonce as IV
|
||||
const iv = nonceBuffer.subarray(0, 12);
|
||||
|
||||
// Decrypt
|
||||
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
const plaintext = decipher.update(actualCiphertext);
|
||||
|
||||
try {
|
||||
const final = decipher.final();
|
||||
return Buffer.concat([plaintext, final]).toString("utf8");
|
||||
} catch (error) {
|
||||
throw new Error("Invalid ciphertext or keys");
|
||||
}
|
||||
};
|
||||
|
||||
const generateAsymmetricKeyPairNoFipsValidation = () => {
|
||||
const pair = nacl.box.keyPair();
|
||||
|
||||
return {
|
||||
publicKey: naclUtils.encodeBase64(pair.publicKey),
|
||||
privateKey: naclUtils.encodeBase64(pair.secretKey)
|
||||
};
|
||||
};
|
||||
|
||||
export const encryptAsymmetricNoFipsValidation = (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)
|
||||
};
|
||||
};
|
||||
|
||||
const decryptAsymmetricNoFipsValidation = ({ 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 const generateAsymmetricKeyPair = () => {
|
||||
const pair = nacl.box.keyPair();
|
||||
|
||||
return {
|
||||
publicKey: naclUtils.encodeBase64(pair.publicKey),
|
||||
privateKey: naclUtils.encodeBase64(pair.secretKey)
|
||||
};
|
||||
};
|
||||
|
||||
export const computeMd5 = (message: string, digest: DigestType = DigestType.Hex) => {
|
||||
let encoder;
|
||||
switch (digest) {
|
||||
case DigestType.Hex:
|
||||
encoder = cryptoJs.enc.Hex;
|
||||
break;
|
||||
case DigestType.Base64:
|
||||
encoder = cryptoJs.enc.Base64;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid digest type: ${digest as string}`);
|
||||
}
|
||||
|
||||
return cryptoJs.MD5(message).toString(encoder);
|
||||
};
|
||||
|
||||
const cryptographyFactory = () => {
|
||||
// placeholder for now
|
||||
let $fipsEnabled = false;
|
||||
let $isInitialized = false;
|
||||
|
||||
const $checkIsInitialized = () => {
|
||||
if (!$isInitialized) {
|
||||
throw new Error("Internal cryptography module is not initialized");
|
||||
}
|
||||
};
|
||||
|
||||
const isFipsModeEnabled = () => {
|
||||
$checkIsInitialized();
|
||||
return $fipsEnabled;
|
||||
};
|
||||
const $setFipsModeEnabled = (enabled: boolean) => {
|
||||
$fipsEnabled = enabled;
|
||||
$isInitialized = true;
|
||||
};
|
||||
|
||||
const initialize = async (superAdminDAL: TSuperAdminDALFactory) => {
|
||||
if ($isInitialized) {
|
||||
return isFipsModeEnabled();
|
||||
}
|
||||
|
||||
if (process.env.FIPS_ENABLED !== "true") {
|
||||
logger.info("[FIPS]: Instance is running in non-FIPS mode.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const serverCfg = await superAdminDAL.findById(ADMIN_CONFIG_DB_UUID).catch(() => null);
|
||||
|
||||
// if fips mode is enabled, we need to check if the deployment is a new deployment or an old one.
|
||||
if (serverCfg) {
|
||||
if (serverCfg.fipsEnabled) {
|
||||
logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled.");
|
||||
$setFipsModeEnabled(true);
|
||||
return true;
|
||||
}
|
||||
logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS.");
|
||||
$setFipsModeEnabled(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("[FIPS]: First time initializing cryptography module on a new deployment. FIPS mode is enabled.");
|
||||
|
||||
// TODO(daniel): check if it's an enterprise deployment
|
||||
|
||||
// if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true.
|
||||
$setFipsModeEnabled(true);
|
||||
return true;
|
||||
};
|
||||
|
||||
const encryption = () => {
|
||||
$checkIsInitialized();
|
||||
|
||||
const asymmetric = () => {
|
||||
const generateKeyPair = () => {
|
||||
if (isFipsModeEnabled()) {
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is enabled.");
|
||||
return generateAsymmetricKeyPairFipsValidated();
|
||||
}
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Generating asymmetric key pair. FIPS mode is DISABLED.");
|
||||
return generateAsymmetricKeyPairNoFipsValidation();
|
||||
};
|
||||
|
||||
const encrypt = (data: string, publicKey: string, privateKey: string) => {
|
||||
if (isFipsModeEnabled()) {
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is enabled.");
|
||||
return encryptAsymmetricFipsValidated(data, publicKey, privateKey);
|
||||
}
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Encrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
return encryptAsymmetricNoFipsValidation(data, publicKey, privateKey);
|
||||
};
|
||||
|
||||
const decrypt = ({ ciphertext, nonce, publicKey, privateKey }: TDecryptAsymmetricInput) => {
|
||||
if (isFipsModeEnabled()) {
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is enabled.");
|
||||
return decryptAsymmetricFipsValidated({ ciphertext, nonce, publicKey, privateKey });
|
||||
}
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
logger.info("[FIPS]: Decrypting asymmetric data. FIPS mode is DISABLED.");
|
||||
return decryptAsymmetricNoFipsValidation({ ciphertext, nonce, publicKey, privateKey });
|
||||
};
|
||||
|
||||
return {
|
||||
generateKeyPair,
|
||||
encrypt,
|
||||
decrypt
|
||||
};
|
||||
};
|
||||
|
||||
const decryptSymmetric = ({ ciphertext, iv, tag, key, keySize }: TDecryptSymmetricInput): string => {
|
||||
let decipher;
|
||||
|
||||
if (keySize === SymmetricKeySize.Bits128) {
|
||||
// Not ideal: 128-bit hex key (32 chars) gets interpreted as 32 UTF-8 bytes (256 bits)
|
||||
// This works but reduces effective key entropy from 256 to 128 bits
|
||||
// Note: Never use this for FIPS mode of operation.
|
||||
decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64"));
|
||||
} else {
|
||||
const secretKey = crypto.createSecretKey(key, "base64");
|
||||
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;
|
||||
};
|
||||
|
||||
const encryptSymmetric = ({ plaintext, key, keySize }: TEncryptSymmetricInput) => {
|
||||
let iv;
|
||||
let cipher;
|
||||
|
||||
if (keySize === SymmetricKeySize.Bits128) {
|
||||
iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
|
||||
cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv);
|
||||
} else {
|
||||
iv = crypto.randomBytes(IV_BYTES_SIZE);
|
||||
cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, crypto.createSecretKey(key, "base64"), iv);
|
||||
}
|
||||
|
||||
let ciphertext = cipher.update(plaintext, "utf8", "base64");
|
||||
ciphertext += cipher.final("base64");
|
||||
|
||||
return {
|
||||
ciphertext,
|
||||
iv: iv.toString("base64"),
|
||||
tag: cipher.getAuthTag().toString("base64")
|
||||
};
|
||||
};
|
||||
|
||||
const encryptWithRootEncryptionKey = (data: string) => {
|
||||
const appCfg = getConfig();
|
||||
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg.ENCRYPTION_KEY;
|
||||
if (rootEncryptionKey) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric({
|
||||
plaintext: data,
|
||||
key: rootEncryptionKey,
|
||||
keySize: SymmetricKeySize.Bits256
|
||||
});
|
||||
return {
|
||||
iv,
|
||||
tag,
|
||||
ciphertext,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
encoding: SecretKeyEncoding.BASE64
|
||||
};
|
||||
}
|
||||
if (encryptionKey) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric({
|
||||
plaintext: data,
|
||||
key: encryptionKey,
|
||||
keySize: SymmetricKeySize.Bits128
|
||||
});
|
||||
return {
|
||||
iv,
|
||||
tag,
|
||||
ciphertext,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
encoding: SecretKeyEncoding.UTF8
|
||||
};
|
||||
}
|
||||
throw new Error("Missing both encryption keys");
|
||||
};
|
||||
|
||||
const decryptWithRootEncryptionKey = <T = string>({
|
||||
keyEncoding,
|
||||
ciphertext,
|
||||
tag,
|
||||
iv
|
||||
}: Omit<TDecryptSymmetricInput, "key" | "keySize"> & {
|
||||
keyEncoding: SecretKeyEncoding;
|
||||
}) => {
|
||||
logger.info(
|
||||
`[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}`
|
||||
);
|
||||
logger.info(
|
||||
`[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}`
|
||||
);
|
||||
logger.info(
|
||||
`[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}`
|
||||
);
|
||||
logger.info(
|
||||
`[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}`
|
||||
);
|
||||
logger.info(
|
||||
`[FIPS]: decryptWithRootEncryptionKey -> Decrypting symmetric data. FIPS mode is: ${isFipsModeEnabled()}`
|
||||
);
|
||||
const appCfg = getConfig();
|
||||
// the or gate is used used in migration
|
||||
const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
|
||||
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
|
||||
const data = decryptSymmetric({
|
||||
key: rootEncryptionKey,
|
||||
iv,
|
||||
tag,
|
||||
ciphertext,
|
||||
keySize: SymmetricKeySize.Bits256
|
||||
});
|
||||
return data as T;
|
||||
}
|
||||
if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) {
|
||||
const data = decryptSymmetric({ key: encryptionKey, iv, tag, ciphertext, keySize: SymmetricKeySize.Bits128 });
|
||||
return data as T;
|
||||
}
|
||||
throw new Error("Missing both encryption keys");
|
||||
};
|
||||
|
||||
return {
|
||||
asymmetric,
|
||||
encryptWithRootEncryptionKey,
|
||||
decryptWithRootEncryptionKey,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric
|
||||
};
|
||||
};
|
||||
|
||||
const hashing = () => {
|
||||
$checkIsInitialized();
|
||||
// mark this function as deprecated
|
||||
/**
|
||||
* @deprecated Do not use MD5 unless you absolutely have to. It is considered an unsafe hashing algorithm, and should only be used if absolutely necessary.
|
||||
*/
|
||||
const md5 = (message: string, digest: DigestType = DigestType.Hex) => {
|
||||
logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: md5 -> Hashing message. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
// If FIPS is enabled and we need MD5, we use the crypto-js implementation.
|
||||
// Avoid this at all costs unless strictly necessary, like for mongo atlas digest auth.
|
||||
if (isFipsModeEnabled()) {
|
||||
return computeMd5(message, digest);
|
||||
}
|
||||
return crypto.createHash("md5").update(message).digest(digest);
|
||||
};
|
||||
|
||||
const createHash = async (password: string, saltRounds: number) => {
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: createHash -> Hashing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
if (isFipsModeEnabled()) {
|
||||
const hasher = hasherFipsValidated();
|
||||
|
||||
// For the salt when using pkdf2, we do salt rounds * 100.000.
|
||||
// The reason for this is because pbkdf2 is not as compute intense as bcrypt, making it faster to brute-force.
|
||||
// From my testing, doing salt rounds * 100.000 brings the computational power required to roughly the same as bcrypt.
|
||||
// OWASP recommends a minimum of 600.000 iterations for pbkdf2.
|
||||
// Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
|
||||
const hash = await hasher.hash(password, saltRounds * 100_000);
|
||||
return hash;
|
||||
}
|
||||
const hash = await bcrypt.hash(password, saltRounds);
|
||||
return hash;
|
||||
};
|
||||
|
||||
const compareHash = async (password: string, hash: string) => {
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
logger.info(`[FIPS]: compareHash -> Comparing password. FIPS mode is: ${isFipsModeEnabled()}`);
|
||||
if (isFipsModeEnabled()) {
|
||||
const isValid = await hasherFipsValidated().compare(password, hash);
|
||||
return isValid;
|
||||
}
|
||||
const isValid = await bcrypt.compare(password, hash);
|
||||
return isValid;
|
||||
};
|
||||
|
||||
return {
|
||||
md5,
|
||||
createHash,
|
||||
compareHash
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
initialize,
|
||||
isFipsModeEnabled,
|
||||
hashing,
|
||||
encryption,
|
||||
randomBytes: crypto.randomBytes,
|
||||
randomInt: crypto.randomInt,
|
||||
rawCrypto: {
|
||||
createHash: crypto.createHash,
|
||||
createHmac: crypto.createHmac,
|
||||
sign: crypto.sign,
|
||||
verify: crypto.verify,
|
||||
createSign: crypto.createSign,
|
||||
createVerify: crypto.createVerify,
|
||||
generateKeyPair: crypto.generateKeyPair,
|
||||
createCipheriv: crypto.createCipheriv,
|
||||
createDecipheriv: crypto.createDecipheriv,
|
||||
createPublicKey: crypto.createPublicKey,
|
||||
createPrivateKey: crypto.createPrivateKey,
|
||||
getRandomValues: crypto.getRandomValues,
|
||||
randomUUID: crypto.randomUUID,
|
||||
subtle: {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
generateKey: subtle.generateKey,
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
importKey: subtle.importKey,
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
exportKey: subtle.exportKey
|
||||
},
|
||||
constants: crypto.constants,
|
||||
X509Certificate: crypto.X509Certificate,
|
||||
KeyObject: crypto.KeyObject
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const factoryInstance = cryptographyFactory();
|
||||
|
||||
export type TCryptographyFactory = ReturnType<typeof cryptographyFactory>;
|
||||
|
||||
export { factoryInstance as crypto, DigestType };
|
||||
@@ -1,133 +1,10 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import argon2 from "argon2";
|
||||
import nacl from "tweetnacl";
|
||||
import naclUtils from "tweetnacl-util";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
|
||||
import { getConfig } from "../config/env";
|
||||
import { crypto, SymmetricKeySize } from "./cryptography";
|
||||
|
||||
export const decodeBase64 = (s: string) => naclUtils.decodeBase64(s);
|
||||
export const encodeBase64 = (u: Uint8Array) => naclUtils.encodeBase64(u);
|
||||
|
||||
export const randomSecureBytes = (length = 32) => crypto.randomBytes(length);
|
||||
|
||||
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 | Buffer) => {
|
||||
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
|
||||
}: Omit<TDecryptSymmetricInput, "key"> & { key: string | Buffer }): 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 const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
|
||||
|
||||
export const generateHash = (value: string | Buffer) => crypto.createHash("sha256").update(value).digest("hex");
|
||||
|
||||
export const generateAsymmetricKeyPair = () => {
|
||||
const pair = nacl.box.keyPair();
|
||||
|
||||
return {
|
||||
publicKey: naclUtils.encodeBase64(pair.publicKey),
|
||||
privateKey: naclUtils.encodeBase64(pair.secretKey)
|
||||
};
|
||||
};
|
||||
|
||||
export type TGenSecretBlindIndex = {
|
||||
type TBuildSecretBlindIndexDTO = {
|
||||
secretName: string;
|
||||
keyEncoding: SecretKeyEncoding;
|
||||
rootEncryptionKey?: string;
|
||||
@@ -137,6 +14,10 @@ export type TGenSecretBlindIndex = {
|
||||
ciphertext: string;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated `buildSecretBlindIndexFromName` is no longer used for newer projects. It remains a relic from V1 secrets which is still supported on very old projects.
|
||||
*/
|
||||
export const buildSecretBlindIndexFromName = async ({
|
||||
secretName,
|
||||
ciphertext,
|
||||
@@ -145,13 +26,17 @@ export const buildSecretBlindIndexFromName = async ({
|
||||
tag,
|
||||
encryptionKey,
|
||||
rootEncryptionKey
|
||||
}: TGenSecretBlindIndex) => {
|
||||
}: TBuildSecretBlindIndexDTO) => {
|
||||
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 });
|
||||
salt = crypto
|
||||
.encryption()
|
||||
.decryptSymmetric({ iv, ciphertext, key: rootEncryptionKey, tag, keySize: SymmetricKeySize.Bits256 });
|
||||
} else if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) {
|
||||
salt = decryptSymmetric128BitHexKeyUTF8({ iv, ciphertext, key: encryptionKey, tag });
|
||||
salt = crypto
|
||||
.encryption()
|
||||
.decryptSymmetric({ iv, ciphertext, key: encryptionKey, tag, keySize: SymmetricKeySize.Bits128 });
|
||||
}
|
||||
if (!salt) throw new Error("Missing secret blind index key");
|
||||
|
||||
@@ -167,75 +52,3 @@ export const buildSecretBlindIndexFromName = async ({
|
||||
|
||||
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");
|
||||
};
|
||||
|
||||
export const infisicalSymmetricEncypt = (data: string) => {
|
||||
const appCfg = getConfig();
|
||||
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg.ENCRYPTION_KEY;
|
||||
if (rootEncryptionKey) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric(data, rootEncryptionKey);
|
||||
return {
|
||||
iv,
|
||||
tag,
|
||||
ciphertext,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
encoding: SecretKeyEncoding.BASE64
|
||||
};
|
||||
}
|
||||
if (encryptionKey) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(data, encryptionKey);
|
||||
return {
|
||||
iv,
|
||||
tag,
|
||||
ciphertext,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
encoding: SecretKeyEncoding.UTF8
|
||||
};
|
||||
}
|
||||
throw new Error("Missing both encryption keys");
|
||||
};
|
||||
|
||||
export const infisicalSymmetricDecrypt = <T = string>({
|
||||
keyEncoding,
|
||||
ciphertext,
|
||||
tag,
|
||||
iv
|
||||
}: Omit<TDecryptSymmetricInput, "key"> & {
|
||||
keyEncoding: SecretKeyEncoding;
|
||||
}) => {
|
||||
const appCfg = getConfig();
|
||||
// the or gate is used used in migration
|
||||
const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
|
||||
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
|
||||
const data = decryptSymmetric({ key: rootEncryptionKey, iv, tag, ciphertext });
|
||||
return data as T;
|
||||
}
|
||||
if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) {
|
||||
const data = decryptSymmetric128BitHexKeyUTF8({ key: encryptionKey, iv, tag, ciphertext });
|
||||
return data as T;
|
||||
}
|
||||
throw new Error("Missing both encryption keys");
|
||||
};
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
export {
|
||||
buildSecretBlindIndexFromName,
|
||||
createSecretBlindIndex,
|
||||
decodeBase64,
|
||||
decryptAsymmetric,
|
||||
decryptSymmetric,
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
encodeBase64,
|
||||
encryptAsymmetric,
|
||||
encryptSymmetric,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
generateAsymmetricKeyPair,
|
||||
randomSecureBytes
|
||||
} from "./encryption";
|
||||
export { crypto, SymmetricKeySize } from "./cryptography";
|
||||
export { buildSecretBlindIndexFromName } from "./encryption";
|
||||
export {
|
||||
decryptIntegrationAuths,
|
||||
decryptSecretApprovals,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import crypto from "crypto";
|
||||
import nodeCrypto from "crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
TSecrets,
|
||||
TSecretVersions
|
||||
} from "../../db/schemas";
|
||||
import { decryptAsymmetric } from "./encryption";
|
||||
import { crypto } from "./cryptography";
|
||||
|
||||
const DecryptedValuesSchema = z.object({
|
||||
id: z.string(),
|
||||
@@ -68,7 +68,7 @@ const decryptCipher = ({
|
||||
tag: string;
|
||||
key: string | Buffer;
|
||||
}) => {
|
||||
const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64"));
|
||||
const decipher = nodeCrypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64"));
|
||||
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
||||
|
||||
let cleartext = decipher.update(ciphertext, "base64", "utf8");
|
||||
@@ -91,7 +91,7 @@ const getDecryptedValues = (data: Array<{ ciphertext: string; iv: string; tag: s
|
||||
return results;
|
||||
};
|
||||
export const decryptSecrets = (encryptedSecrets: TSecrets[], privateKey: string, latestKey: TLatestKey) => {
|
||||
const key = decryptAsymmetric({
|
||||
const key = crypto.encryption().asymmetric().decrypt({
|
||||
ciphertext: latestKey.encryptedKey,
|
||||
nonce: latestKey.nonce,
|
||||
publicKey: latestKey.sender.publicKey,
|
||||
@@ -143,7 +143,7 @@ export const decryptSecretVersions = (
|
||||
privateKey: string,
|
||||
latestKey: TLatestKey
|
||||
) => {
|
||||
const key = decryptAsymmetric({
|
||||
const key = crypto.encryption().asymmetric().decrypt({
|
||||
ciphertext: latestKey.encryptedKey,
|
||||
nonce: latestKey.nonce,
|
||||
publicKey: latestKey.sender.publicKey,
|
||||
@@ -195,7 +195,7 @@ export const decryptSecretApprovals = (
|
||||
privateKey: string,
|
||||
latestKey: TLatestKey
|
||||
) => {
|
||||
const key = decryptAsymmetric({
|
||||
const key = crypto.encryption().asymmetric().decrypt({
|
||||
ciphertext: latestKey.encryptedKey,
|
||||
nonce: latestKey.nonce,
|
||||
publicKey: latestKey.sender.publicKey,
|
||||
@@ -247,7 +247,7 @@ export const decryptIntegrationAuths = (
|
||||
privateKey: string,
|
||||
latestKey: TLatestKey
|
||||
) => {
|
||||
const key = decryptAsymmetric({
|
||||
const key = crypto.encryption().asymmetric().decrypt({
|
||||
ciphertext: latestKey.encryptedKey,
|
||||
nonce: latestKey.nonce,
|
||||
publicKey: latestKey.sender.publicKey,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { execFile } from "child_process";
|
||||
import crypto from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { cleanTemporaryDirectory, createTemporaryDirectory, writeToTemporaryFile } from "@app/lib/files";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@@ -43,19 +43,19 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
case SigningAlgorithm.RSASSA_PSS_SHA_512:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA512,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: SHA512_DIGEST_LENGTH
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PSS_SHA_256:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: SHA256_DIGEST_LENGTH
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PSS_SHA_384:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: SHA384_DIGEST_LENGTH
|
||||
};
|
||||
|
||||
@@ -63,17 +63,17 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_512:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA512,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_384:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA384,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
case SigningAlgorithm.RSASSA_PKCS1_V1_5_SHA_256:
|
||||
return {
|
||||
hashAlgorithm: SupportedHashAlgorithm.SHA256,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||||
padding: crypto.rawCrypto.constants.RSA_PKCS1_PADDING
|
||||
};
|
||||
|
||||
// ECDSA
|
||||
@@ -389,7 +389,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
return signature;
|
||||
}
|
||||
|
||||
const privateKeyObject = crypto.createPrivateKey({
|
||||
const privateKeyObject = crypto.rawCrypto.createPrivateKey({
|
||||
key: privateKey,
|
||||
format: "pem",
|
||||
type: "pkcs8"
|
||||
@@ -397,7 +397,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
|
||||
// For RSA signatures
|
||||
if (signingAlgorithm.startsWith("RSA")) {
|
||||
const signer = crypto.createSign(hashAlgorithm);
|
||||
const signer = crypto.rawCrypto.createSign(hashAlgorithm);
|
||||
signer.update(data);
|
||||
|
||||
return signer.sign({
|
||||
@@ -408,7 +408,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
}
|
||||
if (signingAlgorithm.startsWith("ECDSA")) {
|
||||
// For ECDSA signatures
|
||||
const signer = crypto.createSign(hashAlgorithm);
|
||||
const signer = crypto.rawCrypto.createSign(hashAlgorithm);
|
||||
signer.update(data);
|
||||
return signer.sign({
|
||||
key: privateKeyObject,
|
||||
@@ -452,7 +452,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
return signatureValid;
|
||||
}
|
||||
|
||||
const publicKeyObject = crypto.createPublicKey({
|
||||
const publicKeyObject = crypto.rawCrypto.createPublicKey({
|
||||
key: publicKey,
|
||||
format: "der",
|
||||
type: "spki"
|
||||
@@ -460,7 +460,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
|
||||
// For RSA signatures
|
||||
if (signingAlgorithm.startsWith("RSA")) {
|
||||
const verifier = crypto.createVerify(hashAlgorithm);
|
||||
const verifier = crypto.rawCrypto.createVerify(hashAlgorithm);
|
||||
verifier.update(data);
|
||||
|
||||
return verifier.verify(
|
||||
@@ -474,7 +474,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
}
|
||||
// For ECDSA signatures
|
||||
if (signingAlgorithm.startsWith("ECDSA")) {
|
||||
const verifier = crypto.createVerify(hashAlgorithm);
|
||||
const verifier = crypto.rawCrypto.createVerify(hashAlgorithm);
|
||||
verifier.update(data);
|
||||
return verifier.verify(
|
||||
{
|
||||
@@ -499,7 +499,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
const generateAsymmetricPrivateKey = async () => {
|
||||
const { privateKey } = await new Promise<{ privateKey: string }>((resolve, reject) => {
|
||||
if (algorithm.startsWith("RSA")) {
|
||||
crypto.generateKeyPair(
|
||||
crypto.rawCrypto.generateKeyPair(
|
||||
"rsa",
|
||||
{
|
||||
modulusLength: Number(algorithm.split("_")[1]),
|
||||
@@ -517,7 +517,7 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
} else {
|
||||
const { full: namedCurve } = $getEcCurveName(algorithm);
|
||||
|
||||
crypto.generateKeyPair(
|
||||
crypto.rawCrypto.generateKeyPair(
|
||||
"ec",
|
||||
{
|
||||
namedCurve,
|
||||
@@ -541,13 +541,13 @@ export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSi
|
||||
};
|
||||
|
||||
const getPublicKeyFromPrivateKey = (privateKey: Buffer) => {
|
||||
const privateKeyObj = crypto.createPrivateKey({
|
||||
const privateKeyObj = crypto.rawCrypto.createPrivateKey({
|
||||
key: privateKey,
|
||||
format: "pem",
|
||||
type: "pkcs8"
|
||||
});
|
||||
|
||||
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
||||
const publicKey = crypto.rawCrypto.createPublicKey(privateKeyObj).export({
|
||||
type: "spki",
|
||||
format: "der"
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
import { KeyObject } from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
import { crypto } from "./cryptography";
|
||||
|
||||
export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => {
|
||||
const verify = crypto.createVerify("SHA256");
|
||||
const verify = crypto.rawCrypto.createVerify("SHA256");
|
||||
verify.update(data);
|
||||
verify.end();
|
||||
return verify.verify(publicKey, signature);
|
||||
@@ -12,7 +14,7 @@ export const verifySignature = (data: string, signature: Buffer, publicKey: KeyO
|
||||
export const verifyOfflineLicense = async (licenseContents: string, signature: string) => {
|
||||
const publicKeyPem = await fs.readFile(path.join(__dirname, "license_public_key.pem"), "utf8");
|
||||
|
||||
const publicKey = crypto.createPublicKey({
|
||||
const publicKey = crypto.rawCrypto.createPublicKey({
|
||||
key: publicKeyPem,
|
||||
format: "pem",
|
||||
type: "pkcs1"
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import argon2 from "argon2";
|
||||
import crypto from "crypto";
|
||||
import jsrp from "jsrp";
|
||||
import nacl from "tweetnacl";
|
||||
import tweetnacl from "tweetnacl-util";
|
||||
|
||||
import { TUserEncryptionKeys } from "@app/db/schemas";
|
||||
import { UserEncryption } from "@app/services/user/user-types";
|
||||
|
||||
import { decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, encryptSymmetric } from "./encryption";
|
||||
import { crypto, SymmetricKeySize } from "./cryptography";
|
||||
|
||||
export const generateSrpServerKey = async (salt: string, verifier: string) => {
|
||||
// eslint-disable-next-line new-cap
|
||||
@@ -42,11 +39,10 @@ export const generateUserSrpKeys = async (
|
||||
password: string,
|
||||
customKeys?: { publicKey: string; privateKey: string }
|
||||
) => {
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const privateKey = customKeys?.privateKey || tweetnacl.encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = customKeys?.publicKey || tweetnacl.encodeBase64(publicKeyUint8Array);
|
||||
const pair = crypto.encryption().asymmetric().generateKeyPair();
|
||||
|
||||
const privateKey = customKeys?.privateKey || pair.privateKey;
|
||||
const publicKey = customKeys?.publicKey || pair.publicKey;
|
||||
|
||||
// eslint-disable-next-line
|
||||
const client = new jsrp.client();
|
||||
@@ -78,7 +74,11 @@ export const generateUserSrpKeys = async (
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = encryptSymmetric(privateKey, key.toString("base64"));
|
||||
} = crypto.encryption().encryptSymmetric({
|
||||
plaintext: privateKey,
|
||||
key: key.toString("base64"),
|
||||
keySize: SymmetricKeySize.Bits256
|
||||
});
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
@@ -86,7 +86,11 @@ export const generateUserSrpKeys = async (
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64"));
|
||||
} = crypto.encryption().encryptSymmetric({
|
||||
plaintext: key.toString("hex"),
|
||||
key: derivedKey.toString("base64"),
|
||||
keySize: SymmetricKeySize.Bits256
|
||||
});
|
||||
|
||||
return {
|
||||
protectedKey,
|
||||
@@ -117,11 +121,12 @@ export const getUserPrivateKey = async (
|
||||
>
|
||||
) => {
|
||||
if (user.encryptionVersion === UserEncryption.V1) {
|
||||
return decryptSymmetric128BitHexKeyUTF8({
|
||||
return crypto.encryption().decryptSymmetric({
|
||||
ciphertext: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
|
||||
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0"),
|
||||
keySize: SymmetricKeySize.Bits128
|
||||
});
|
||||
}
|
||||
if (
|
||||
@@ -140,18 +145,20 @@ export const getUserPrivateKey = async (
|
||||
raw: true
|
||||
});
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
const key = decryptSymmetric128BitHexKeyUTF8({
|
||||
const key = crypto.encryption().decryptSymmetric({
|
||||
ciphertext: user.protectedKey,
|
||||
iv: user.protectedKeyIV,
|
||||
tag: user.protectedKeyTag,
|
||||
key: derivedKey
|
||||
key: derivedKey,
|
||||
keySize: SymmetricKeySize.Bits128
|
||||
});
|
||||
|
||||
const privateKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
const privateKey = crypto.encryption().decryptSymmetric({
|
||||
ciphertext: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
key: Buffer.from(key, "hex")
|
||||
key: Buffer.from(key, "hex"),
|
||||
keySize: SymmetricKeySize.Bits128
|
||||
});
|
||||
return privateKey;
|
||||
}
|
||||
@@ -160,6 +167,6 @@ export const getUserPrivateKey = async (
|
||||
|
||||
export const buildUserProjectKey = async (privateKey: string, publickey: string) => {
|
||||
const randomBytes = crypto.randomBytes(16).toString("hex");
|
||||
const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey);
|
||||
const { nonce, ciphertext } = crypto.encryption().asymmetric().encrypt(randomBytes, publickey, privateKey);
|
||||
return { nonce, ciphertext };
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import crypto from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
const baseDir = path.join(os.tmpdir(), "infisical");
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import crypto from "node:crypto";
|
||||
import net from "node:net";
|
||||
|
||||
import quicDefault, * as quicModule from "@infisical/quic";
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
|
||||
import { BadRequestError } from "../errors";
|
||||
import { logger } from "../logger";
|
||||
import {
|
||||
@@ -48,8 +49,8 @@ const createQuicConnection = async (
|
||||
verifyPeer: true,
|
||||
verifyCallback: async (certs) => {
|
||||
if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired;
|
||||
const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0]));
|
||||
const caCertificate = new crypto.X509Certificate(tlsOptions.ca);
|
||||
const serverCertificate = new crypto.rawCrypto.X509Certificate(Buffer.from(certs[0]));
|
||||
const caCertificate = new crypto.rawCrypto.X509Certificate(tlsOptions.ca);
|
||||
const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey);
|
||||
if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate;
|
||||
|
||||
@@ -72,7 +73,7 @@ const createQuicConnection = async (
|
||||
crypto: {
|
||||
ops: {
|
||||
randomBytes: async (data) => {
|
||||
crypto.getRandomValues(new Uint8Array(data));
|
||||
crypto.rawCrypto.getRandomValues(new Uint8Array(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable */
|
||||
// Source code credits: https://github.com/mike-marcacci/node-redlock
|
||||
// Taken to avoid external dependency
|
||||
import { randomBytes, createHash } from "crypto";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
// AbortController became available as a global in node version 16. Once version
|
||||
@@ -251,14 +251,14 @@ export class Redlock extends EventEmitter {
|
||||
* Generate a sha1 hash compatible with redis evalsha.
|
||||
*/
|
||||
private _hash(value: string): string {
|
||||
return createHash("sha1").update(value).digest("hex");
|
||||
return crypto.rawCrypto.createHash("sha1").update(value).digest("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically random string.
|
||||
*/
|
||||
private _random(): string {
|
||||
return randomBytes(16).toString("hex");
|
||||
return crypto.randomBytes(16).toString("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import crypto from "node:crypto";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
|
||||
const TURN_TOKEN_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TOKEN_TTL) => {
|
||||
const timestamp = Math.floor((Date.now() + ttl) / 1000);
|
||||
const username = `${timestamp}:${id}`;
|
||||
|
||||
const hmac = crypto.createHmac("sha1", authSecret);
|
||||
const hmac = crypto.rawCrypto.createHmac("sha1", authSecret);
|
||||
hmac.update(username);
|
||||
const password = hmac.digest("base64");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user