Files
infisical/backend/src/lib/crypto/srp.ts
T

173 lines
5.0 KiB
TypeScript

import argon2 from "argon2";
import jsrp from "jsrp";
import { TUserEncryptionKeys } from "@app/db/schemas";
import { UserEncryption } from "@app/services/user/user-types";
import { crypto, SymmetricKeySize } from "./cryptography";
export const generateSrpServerKey = async (salt: string, verifier: string) => {
// eslint-disable-next-line new-cap
const server = new jsrp.server();
await new Promise((resolve) => {
server.init({ salt, verifier }, () => resolve(null));
});
return { pubKey: server.getPublicKey(), privateKey: server.getPrivateKey() };
};
export const srpCheckClientProof = async (
salt: string,
verifier: string,
serverPrivateKey: string,
clientPublicKey: string,
clientProof: string
) => {
// eslint-disable-next-line new-cap
const server = new jsrp.server();
await new Promise((resolve) => {
server.init({ salt, verifier, b: serverPrivateKey }, () => resolve(null));
});
server.setClientPublicKey(clientPublicKey);
return server.checkClientProof(clientProof);
};
// Ghost user related:
// This functionality is intended for ghost user logic. This happens on the frontend when a user is being created.
// We replicate the same functionality on the backend when creating a ghost user.
export const generateUserSrpKeys = async (
email: string,
password: string,
customKeys?: { publicKey: string; privateKey: string }
) => {
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();
await new Promise((resolve) => {
client.init({ username: email, password }, () => resolve(null));
});
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>((resolve, reject) => {
client.createVerifier((err, res) => {
if (err) return reject(err);
return resolve(res);
});
});
const derivedKey = await argon2.hash(password, {
salt: Buffer.from(salt),
memoryCost: 65536,
timeCost: 3,
parallelism: 1,
hashLength: 32,
type: argon2.argon2id,
raw: true
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = 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
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = crypto.encryption().encryptSymmetric({
plaintext: key.toString("hex"),
key: derivedKey.toString("base64"),
keySize: SymmetricKeySize.Bits256
});
return {
protectedKey,
plainPrivateKey: privateKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
};
};
export const getUserPrivateKey = async (
password: string,
user: Pick<
TUserEncryptionKeys,
| "protectedKeyTag"
| "protectedKey"
| "protectedKeyIV"
| "encryptedPrivateKey"
| "iv"
| "salt"
| "tag"
| "encryptionVersion"
>
) => {
if (user.encryptionVersion === UserEncryption.V1) {
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"),
keySize: SymmetricKeySize.Bits128
});
}
if (
user.encryptionVersion === UserEncryption.V2 &&
user.protectedKey &&
user.protectedKeyIV &&
user.protectedKeyTag
) {
const derivedKey = await argon2.hash(password, {
salt: Buffer.from(user.salt),
memoryCost: 65536,
timeCost: 3,
parallelism: 1,
hashLength: 32,
type: argon2.argon2id,
raw: true
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.encryption().decryptSymmetric({
ciphertext: user.protectedKey,
iv: user.protectedKeyIV,
tag: user.protectedKeyTag,
key: derivedKey,
keySize: SymmetricKeySize.Bits128
});
const privateKey = crypto.encryption().decryptSymmetric({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
key: Buffer.from(key, "hex"),
keySize: SymmetricKeySize.Bits128
});
return privateKey;
}
throw new Error(`GetUserPrivateKey: Encryption version not found`);
};
export const buildUserProjectKey = async (privateKey: string, publickey: string) => {
const randomBytes = crypto.randomBytes(16).toString("hex");
const { nonce, ciphertext } = crypto.encryption().asymmetric().encrypt(randomBytes, publickey, privateKey);
return { nonce, ciphertext };
};