Manav Rathi 895ba6082b
Doc
2024-11-26 15:46:27 +05:30

231 lines
6.7 KiB
TypeScript

import { sharedCryptoWorker } from "@/base/crypto";
import log from "@/base/log";
import { generateLoginSubKey } from "@ente/shared/crypto/helpers";
import { getToken } from "@ente/shared/storage/localStorage/helpers";
import type { KeyAttributes } from "@ente/shared/user/types";
import { SRP, SrpClient } from "fast-srp-hap";
import { v4 as uuidv4 } from "uuid";
import {
completeSRPSetup,
createSRPSession,
startSRPSetup,
verifySRPSession,
type SRPAttributes,
type SRPSetupAttributes,
} from "./srp-remote";
import type { UserVerificationResponse } from "./user";
const SRP_PARAMS = SRP.params["4096"];
export const configureSRP = async ({
srpSalt,
srpUserID,
srpVerifier,
loginSubKey,
}: SRPSetupAttributes) => {
try {
const srpClient = await generateSRPClient(
srpSalt,
srpUserID,
loginSubKey,
);
const srpA = convertBufferToBase64(srpClient.computeA());
log.debug(() => `srp a: ${srpA}`);
const token = getToken();
const { setupID, srpB } = await startSRPSetup(token, {
srpA,
srpUserID,
srpSalt,
srpVerifier,
});
srpClient.setB(convertBase64ToBuffer(srpB));
const srpM1 = convertBufferToBase64(srpClient.computeM1());
const { srpM2 } = await completeSRPSetup(token, {
srpM1,
setupID,
});
srpClient.checkM2(convertBase64ToBuffer(srpM2));
} catch (e) {
log.error("Failed to configure SRP", e);
throw e;
}
};
export const generateSRPSetupAttributes = async (
loginSubKey: string,
): Promise<SRPSetupAttributes> => {
const cryptoWorker = await sharedCryptoWorker();
const srpSalt = await cryptoWorker.generateSaltToDeriveKey();
// Museum schema requires this to be a UUID.
const srpUserID = uuidv4();
const srpVerifierBuffer = SRP.computeVerifier(
SRP_PARAMS,
convertBase64ToBuffer(srpSalt),
Buffer.from(srpUserID),
convertBase64ToBuffer(loginSubKey),
);
const srpVerifier = convertBufferToBase64(srpVerifierBuffer);
const result = {
srpUserID,
srpSalt,
srpVerifier,
loginSubKey,
};
log.debug(
() => `SRP setup attributes generated: ${JSON.stringify(result)}`,
);
return result;
};
export const loginViaSRP = async (
srpAttributes: SRPAttributes,
kek: string,
): Promise<UserVerificationResponse> => {
try {
const loginSubKey = await generateLoginSubKey(kek);
const srpClient = await generateSRPClient(
srpAttributes.srpSalt,
srpAttributes.srpUserID,
loginSubKey,
);
const srpA = srpClient.computeA();
const { srpB, sessionID } = await createSRPSession(
srpAttributes.srpUserID,
convertBufferToBase64(srpA),
);
srpClient.setB(convertBase64ToBuffer(srpB));
const m1 = srpClient.computeM1();
log.debug(() => `srp m1: ${convertBufferToBase64(m1)}`);
const { srpM2, ...rest } = await verifySRPSession(
sessionID,
srpAttributes.srpUserID,
convertBufferToBase64(m1),
);
log.debug(() => `srp verify session successful,srpM2: ${srpM2}`);
srpClient.checkM2(convertBase64ToBuffer(srpM2));
log.debug(() => `srp server verify successful`);
return rest;
} catch (e) {
log.error("srp verify failed", e);
throw e;
}
};
// ====================
// HELPERS
// ====================
export const generateSRPClient = async (
srpSalt: string,
srpUserID: string,
loginSubKey: string,
) => {
return new Promise<SrpClient>((resolve, reject) => {
SRP.genKey(function (err, secret1) {
try {
if (err) {
reject(err);
}
if (!secret1) {
throw Error("secret1 gen failed");
}
const srpClient = new SrpClient(
SRP_PARAMS,
convertBase64ToBuffer(srpSalt),
Buffer.from(srpUserID),
convertBase64ToBuffer(loginSubKey),
secret1,
false,
);
resolve(srpClient);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
reject(e);
}
});
});
};
export const convertBufferToBase64 = (buffer: Buffer) => {
return buffer.toString("base64");
};
export const convertBase64ToBuffer = (base64: string) => {
return Buffer.from(base64, "base64");
};
export async function generateKeyAndSRPAttributes(passphrase: string): Promise<{
keyAttributes: KeyAttributes;
masterKey: string;
srpSetupAttributes: SRPSetupAttributes;
}> {
const cryptoWorker = await sharedCryptoWorker();
const masterKey = await cryptoWorker.generateEncryptionKey();
const recoveryKey = await cryptoWorker.generateEncryptionKey();
const kekSalt = await cryptoWorker.generateSaltToDeriveKey();
const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
const masterKeyEncryptedWithKek = await cryptoWorker.encryptToB64(
masterKey,
kek.key,
);
const masterKeyEncryptedWithRecoveryKey = await cryptoWorker.encryptToB64(
masterKey,
recoveryKey,
);
const recoveryKeyEncryptedWithMasterKey = await cryptoWorker.encryptToB64(
recoveryKey,
masterKey,
);
const keyPair = await cryptoWorker.generateKeyPair();
const encryptedKeyPairAttributes = await cryptoWorker.encryptToB64(
keyPair.privateKey,
masterKey,
);
const loginSubKey = await generateLoginSubKey(kek.key);
const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey);
const keyAttributes: KeyAttributes = {
kekSalt,
encryptedKey: masterKeyEncryptedWithKek.encryptedData,
keyDecryptionNonce: masterKeyEncryptedWithKek.nonce,
publicKey: keyPair.publicKey,
encryptedSecretKey: encryptedKeyPairAttributes.encryptedData,
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
opsLimit: kek.opsLimit,
memLimit: kek.memLimit,
masterKeyEncryptedWithRecoveryKey:
masterKeyEncryptedWithRecoveryKey.encryptedData,
masterKeyDecryptionNonce: masterKeyEncryptedWithRecoveryKey.nonce,
recoveryKeyEncryptedWithMasterKey:
recoveryKeyEncryptedWithMasterKey.encryptedData,
recoveryKeyDecryptionNonce: recoveryKeyEncryptedWithMasterKey.nonce,
};
return {
keyAttributes,
masterKey,
srpSetupAttributes,
};
}