mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-26 09:51:48 +01:00
Add encryption methods and encrypt device when sent to server
This commit is contained in:
parent
0dc3e51a36
commit
7f474af657
@ -4,6 +4,12 @@ import { generateMnemonic, validateMnemonic } from "@scure/bip39";
|
|||||||
import { wordlist } from "@scure/bip39/wordlists/english";
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
||||||
import forge from "node-forge";
|
import forge from "node-forge";
|
||||||
|
|
||||||
|
type Keys = {
|
||||||
|
privateKey: Uint8Array;
|
||||||
|
publicKey: Uint8Array;
|
||||||
|
seed: Uint8Array;
|
||||||
|
};
|
||||||
|
|
||||||
async function seedFromMnemonic(mnemonic: string) {
|
async function seedFromMnemonic(mnemonic: string) {
|
||||||
return pbkdf2Async(sha256, mnemonic, "mnemonic", {
|
return pbkdf2Async(sha256, mnemonic, "mnemonic", {
|
||||||
c: 2048,
|
c: 2048,
|
||||||
@ -15,7 +21,7 @@ export function verifyValidMnemonic(mnemonic: string) {
|
|||||||
return validateMnemonic(mnemonic, wordlist);
|
return validateMnemonic(mnemonic, wordlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function keysFromMnemonic(mnemonic: string) {
|
export async function keysFromMnemonic(mnemonic: string): Promise<Keys> {
|
||||||
const seed = await seedFromMnemonic(mnemonic);
|
const seed = await seedFromMnemonic(mnemonic);
|
||||||
|
|
||||||
const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair({
|
const { privateKey, publicKey } = forge.pki.ed25519.generateKeyPair({
|
||||||
@ -25,6 +31,7 @@ export async function keysFromMnemonic(mnemonic: string) {
|
|||||||
return {
|
return {
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey,
|
||||||
|
seed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +41,8 @@ export function genMnemonic(): string {
|
|||||||
|
|
||||||
export async function signCode(
|
export async function signCode(
|
||||||
code: string,
|
code: string,
|
||||||
privateKey: forge.pki.ed25519.NativeBuffer
|
privateKey: Uint8Array
|
||||||
): Promise<forge.pki.ed25519.NativeBuffer> {
|
): Promise<Uint8Array> {
|
||||||
return forge.pki.ed25519.sign({
|
return forge.pki.ed25519.sign({
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
message: code,
|
message: code,
|
||||||
@ -43,18 +50,85 @@ export async function signCode(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bytesToBase64(bytes: Uint8Array) {
|
||||||
|
return forge.util.encode64(String.fromCodePoint(...bytes));
|
||||||
|
}
|
||||||
|
|
||||||
export function bytesToBase64Url(bytes: Uint8Array): string {
|
export function bytesToBase64Url(bytes: Uint8Array): string {
|
||||||
return btoa(String.fromCodePoint(...bytes))
|
return bytesToBase64(bytes)
|
||||||
.replace(/\//g, "_")
|
.replace(/\//g, "_")
|
||||||
.replace(/\+/g, "-")
|
.replace(/\+/g, "-")
|
||||||
.replace(/=+$/, "");
|
.replace(/=+$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signChallenge(mnemonic: string, challengeCode: string) {
|
export async function signChallenge(keys: Keys, challengeCode: string) {
|
||||||
const keys = await keysFromMnemonic(mnemonic);
|
|
||||||
const signature = await signCode(challengeCode, keys.privateKey);
|
const signature = await signCode(challengeCode, keys.privateKey);
|
||||||
return {
|
return bytesToBase64Url(signature);
|
||||||
publicKey: bytesToBase64Url(keys.publicKey),
|
}
|
||||||
signature: bytesToBase64Url(signature),
|
|
||||||
};
|
export function base64ToBuffer(data: string) {
|
||||||
|
return forge.util.binary.base64.decode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64ToStringBugger(data: string) {
|
||||||
|
return forge.util.createBuffer(base64ToBuffer(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringBufferToBase64(buffer: forge.util.ByteStringBuffer) {
|
||||||
|
return forge.util.encode64(buffer.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encryptData(data: string, secret: Uint8Array) {
|
||||||
|
if (secret.byteLength !== 32)
|
||||||
|
throw new Error("Secret must be at least 256-bit");
|
||||||
|
|
||||||
|
const iv = await new Promise<string>((resolve, reject) => {
|
||||||
|
forge.random.getBytes(16, (err, bytes) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(bytes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const cipher = forge.cipher.createCipher(
|
||||||
|
"AES-GCM",
|
||||||
|
forge.util.createBuffer(secret)
|
||||||
|
);
|
||||||
|
cipher.start({
|
||||||
|
iv,
|
||||||
|
tagLength: 128,
|
||||||
|
});
|
||||||
|
cipher.update(forge.util.createBuffer(data));
|
||||||
|
cipher.finish();
|
||||||
|
|
||||||
|
const encryptedData = cipher.output;
|
||||||
|
const tag = cipher.mode.tag;
|
||||||
|
|
||||||
|
return `${forge.util.encode64(iv)}.${stringBufferToBase64(
|
||||||
|
encryptedData
|
||||||
|
)}.${stringBufferToBase64(tag)}` as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decryptData(
|
||||||
|
data: `${string}.${string}.${string}`,
|
||||||
|
secret: Uint8Array
|
||||||
|
) {
|
||||||
|
if (secret.byteLength !== 32) throw new Error("Secret must be 256-bit");
|
||||||
|
|
||||||
|
const [iv, encryptedData, tag] = data.split(".");
|
||||||
|
|
||||||
|
const decipher = forge.cipher.createDecipher(
|
||||||
|
"AES-GCM",
|
||||||
|
forge.util.createBuffer(secret)
|
||||||
|
);
|
||||||
|
decipher.start({
|
||||||
|
iv: base64ToStringBugger(iv),
|
||||||
|
tag: base64ToStringBugger(tag),
|
||||||
|
tagLength: 128,
|
||||||
|
});
|
||||||
|
decipher.update(base64ToStringBugger(encryptedData));
|
||||||
|
const pass = decipher.finish();
|
||||||
|
|
||||||
|
if (!pass) throw new Error("Error decrypting data");
|
||||||
|
|
||||||
|
return decipher.output.toString();
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ import { useCallback } from "react";
|
|||||||
|
|
||||||
import { removeSession } from "@/backend/accounts/auth";
|
import { removeSession } from "@/backend/accounts/auth";
|
||||||
import {
|
import {
|
||||||
|
bytesToBase64,
|
||||||
bytesToBase64Url,
|
bytesToBase64Url,
|
||||||
|
encryptData,
|
||||||
keysFromMnemonic,
|
keysFromMnemonic,
|
||||||
signChallenge,
|
signChallenge,
|
||||||
} from "@/backend/accounts/crypto";
|
} from "@/backend/accounts/crypto";
|
||||||
@ -49,22 +51,24 @@ export function useAuth() {
|
|||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (loginData: LoginData) => {
|
async (loginData: LoginData) => {
|
||||||
const keys = await keysFromMnemonic(loginData.mnemonic);
|
const keys = await keysFromMnemonic(loginData.mnemonic);
|
||||||
|
const publicKeyBase64Url = bytesToBase64Url(keys.publicKey);
|
||||||
const { challenge } = await getLoginChallengeToken(
|
const { challenge } = await getLoginChallengeToken(
|
||||||
backendUrl,
|
backendUrl,
|
||||||
bytesToBase64Url(keys.publicKey)
|
publicKeyBase64Url
|
||||||
);
|
);
|
||||||
const signResult = await signChallenge(loginData.mnemonic, challenge);
|
const signature = await signChallenge(keys, challenge);
|
||||||
const loginResult = await loginAccount(backendUrl, {
|
const loginResult = await loginAccount(backendUrl, {
|
||||||
challenge: {
|
challenge: {
|
||||||
code: challenge,
|
code: challenge,
|
||||||
signature: signResult.signature,
|
signature,
|
||||||
},
|
},
|
||||||
publicKey: signResult.publicKey,
|
publicKey: publicKeyBase64Url,
|
||||||
device: loginData.userData.device,
|
device: await encryptData(loginData.userData.device, keys.seed),
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await getUser(backendUrl, loginResult.token);
|
const user = await getUser(backendUrl, loginResult.token);
|
||||||
await userDataLogin(loginResult, user);
|
const seedBase64 = bytesToBase64(keys.seed);
|
||||||
|
await userDataLogin(loginResult, user, seedBase64);
|
||||||
},
|
},
|
||||||
[userDataLogin, backendUrl]
|
[userDataLogin, backendUrl]
|
||||||
);
|
);
|
||||||
@ -86,18 +90,23 @@ export function useAuth() {
|
|||||||
const register = useCallback(
|
const register = useCallback(
|
||||||
async (registerData: RegistrationData) => {
|
async (registerData: RegistrationData) => {
|
||||||
const { challenge } = await getRegisterChallengeToken(backendUrl);
|
const { challenge } = await getRegisterChallengeToken(backendUrl);
|
||||||
const signResult = await signChallenge(registerData.mnemonic, challenge);
|
const keys = await keysFromMnemonic(registerData.mnemonic);
|
||||||
|
const signature = await signChallenge(keys, challenge);
|
||||||
const registerResult = await registerAccount(backendUrl, {
|
const registerResult = await registerAccount(backendUrl, {
|
||||||
challenge: {
|
challenge: {
|
||||||
code: challenge,
|
code: challenge,
|
||||||
signature: signResult.signature,
|
signature,
|
||||||
},
|
},
|
||||||
publicKey: signResult.publicKey,
|
publicKey: bytesToBase64Url(keys.publicKey),
|
||||||
device: registerData.userData.device,
|
device: await encryptData(registerData.userData.device, keys.seed),
|
||||||
profile: registerData.userData.profile,
|
profile: registerData.userData.profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
await userDataLogin(registerResult, registerResult.user);
|
await userDataLogin(
|
||||||
|
registerResult,
|
||||||
|
registerResult.user,
|
||||||
|
bytesToBase64(keys.seed)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[backendUrl, userDataLogin]
|
[backendUrl, userDataLogin]
|
||||||
);
|
);
|
||||||
|
@ -23,12 +23,13 @@ export function useAuthData() {
|
|||||||
const replaceItems = useProgressStore((s) => s.replaceItems);
|
const replaceItems = useProgressStore((s) => s.replaceItems);
|
||||||
|
|
||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (account: LoginResponse, user: UserResponse) => {
|
async (account: LoginResponse, user: UserResponse, seed: string) => {
|
||||||
setAccount({
|
setAccount({
|
||||||
token: account.token,
|
token: account.token,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
sessionId: account.session.id,
|
sessionId: account.session.id,
|
||||||
profile: user.profile,
|
profile: user.profile,
|
||||||
|
seed,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setAccount]
|
[setAccount]
|
||||||
|
@ -14,6 +14,7 @@ export type AccountWithToken = Account & {
|
|||||||
sessionId: string;
|
sessionId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
seed: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AuthStore {
|
interface AuthStore {
|
||||||
|
Loading…
Reference in New Issue
Block a user