feat: fips inside support (checkpoint)

This commit is contained in:
Daniel Hougaard
2025-07-06 15:44:07 +04:00
parent 83f0a500bd
commit 9aa3c14bf2
121 changed files with 1766 additions and 1277 deletions
+6 -11
View File
@@ -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) {
+2 -1
View File
@@ -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,
+2 -8
View File
@@ -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");
+5 -13
View File
@@ -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()]);
+630
View File
@@ -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 };
+14 -201
View File
@@ -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");
};
+2 -14
View File
@@ -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,
+7 -7
View File
@@ -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,
+17 -17
View File
@@ -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"
});
+5 -3
View File
@@ -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"
+25 -18
View File
@@ -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 -1
View File
@@ -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");
+5 -4
View File
@@ -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));
}
}
}
+3 -3
View File
@@ -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");
}
/**
+2 -2
View File
@@ -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");