Skip to content

Commit

Permalink
Fix crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
mrruby committed Nov 30, 2023
1 parent 9a3dcdd commit 03a8d42
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 49 deletions.
14 changes: 6 additions & 8 deletions src/lib/components/Login.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
let password = '';
const { signInMutation } = sessionStorageQueries();
async function login() {
$signInMutation.mutate(password, {
onError: () => alert('Invalid password')
});
}
</script>

<div class="m-8">
Expand All @@ -27,7 +21,11 @@
extraProps="my-4 text-center max-w-xs"
text="Please enter your password to login into Holo Key Manager"
/>
<InputPassword bind:value={password} label="Enter password" />
<InputPassword
error={$signInMutation.error ? 'Invalid password' : ''}
bind:value={password}
label="Enter password"
/>
</div>
<Button label="Login" onClick={login} extraBottomMargin />
<Button label="Login" onClick={() => $signInMutation.mutate(password)} extraBottomMargin />
</div>
39 changes: 32 additions & 7 deletions src/lib/helpers/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SecureData } from '$types';
import type { SecureData, HashSalt } from '$types';

const hexStringToArrayBuffer = (hexString: string) => {
if (hexString.length % 2 !== 0) {
Expand Down Expand Up @@ -26,12 +26,36 @@ const arrayBufferToHexString = (buffer: ArrayBuffer) =>
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
.join('');

export const hashPassword = async (password: string): Promise<string> => {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const algo = { name: 'SHA-256' };
const hashBuffer = await crypto.subtle.digest(algo, data);
return arrayBufferToHexString(hashBuffer); // Convert ArrayBuffer to hex string
const deriveBits = async (password: string, salt: Uint8Array, iterations: number) => {
const encodedPassword = new TextEncoder().encode(password);
const algo = {
name: 'PBKDF2',
hash: 'SHA-256',
salt: new TextEncoder().encode(arrayBufferToHexString(salt)),
iterations: iterations
};
const key = await crypto.subtle.importKey('raw', encodedPassword, { name: 'PBKDF2' }, false, [
'deriveBits'
]);
return await crypto.subtle.deriveBits(algo, key, 256);
};

export const hashPassword = async (password: string): Promise<HashSalt> => {
const salt = crypto.getRandomValues(new Uint8Array(16));
const derivedBits = await deriveBits(password, salt, 100000);
return {
hash: arrayBufferToHexString(derivedBits),
salt: arrayBufferToHexString(salt)
};
};

export const verifyPassword = async (
inputPassword: string,
storedHashSalt: HashSalt
): Promise<boolean> => {
const saltBuffer = hexStringToArrayBuffer(storedHashSalt.salt);
const derivedBits = await deriveBits(inputPassword, new Uint8Array(saltBuffer), 100000);
return arrayBufferToHexString(derivedBits) === storedHashSalt.hash;
};

export const encryptData = async (
Expand All @@ -44,6 +68,7 @@ export const encryptData = async (
const key = await crypto.subtle.importKey('raw', passwordHash, algo, false, ['encrypt']);

const encrypted = await crypto.subtle.encrypt(algo, key, secretData);

return {
encryptedData: arrayBufferToBase64(encrypted),
iv: arrayBufferToBase64(iv)
Expand Down
35 changes: 18 additions & 17 deletions src/lib/queries/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import {
SESSION_DATA_KEY,
SETUP_PASSWORD
} from '$const';
import { decryptData, encryptData, hashPassword } from '$helpers';
import { decryptData, encryptData, hashPassword, verifyPassword } from '$helpers';
import { storageService } from '$services';
import { Password, SecureDataSchema } from '$types';
import { HashSaltSchema, SecureDataSchema } from '$types';
import { createMutation, createQuery, type QueryClient } from '@tanstack/svelte-query';

const getPassword = async () => {
const data = await storageService.getWithoutCallback({ key: PASSWORD, area: LOCAL });
return Password.safeParse(data);
return HashSaltSchema.safeParse(data);
};

export function createSetupPasswordQuery() {
return createQuery({
queryKey: [SETUP_PASSWORD],
queryFn: async () => {
const validatedResult = await getPassword();
return validatedResult.success;
const parsedResult = await getPassword();
return parsedResult.success;
}
});
}
Expand All @@ -46,9 +46,9 @@ export function createPasswordMutation(queryClient: QueryClient) {
export function createSignInMutation(queryClient: QueryClient) {
return createMutation({
mutationFn: async (password: string) => {
const validatedResult = await getPassword();
const parsedResult = await getPassword();

if (validatedResult.success && (await hashPassword(password)) === validatedResult.data) {
if (parsedResult.success && (await verifyPassword(password, parsedResult.data))) {
return storageService.set({
key: SESSION_DATA,
value: true,
Expand All @@ -67,11 +67,12 @@ export function createSignInMutation(queryClient: QueryClient) {
export function createChangePasswordWithDeviceKeyMutation(queryClient: QueryClient) {
return createMutation({
mutationFn: async (mutationData: { newPassword: string; oldPassword: string }) => {
const oldHash = await hashPassword(mutationData.oldPassword);
const parsedResult = await getPassword();

const validatePassword = await getPassword();

if (!validatePassword.success || oldHash !== validatePassword.data) {
if (
!parsedResult.success ||
!(await verifyPassword(mutationData.oldPassword, parsedResult.data))
) {
throw new Error('Invalid password');
}

Expand All @@ -80,23 +81,23 @@ export function createChangePasswordWithDeviceKeyMutation(queryClient: QueryClie
area: LOCAL
});

const validatedDeviceKey = SecureDataSchema.safeParse(deviceKey);
const parsedDeviceKey = SecureDataSchema.safeParse(deviceKey);

if (!validatedDeviceKey.success) {
if (!parsedDeviceKey.success) {
throw new Error('Invalid device key');
}

const decryptedData = await decryptData(validatedDeviceKey.data, oldHash);
const newHash = await hashPassword(mutationData.newPassword);
const decryptedData = await decryptData(parsedDeviceKey.data, parsedResult.data.hash);
const newHashSalt = await hashPassword(mutationData.newPassword);

storageService.set({
key: PASSWORD,
value: newHash,
value: newHashSalt,
area: LOCAL
});
storageService.set({
key: DEVICE_KEY,
value: await encryptData(decryptedData, newHash),
value: await encryptData(decryptedData, newHashSalt.hash),
area: LOCAL
});
storageService.set({
Expand Down
27 changes: 14 additions & 13 deletions src/lib/queries/sessionAndKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '$const';
import { encryptData } from '$helpers';
import { storageService } from '$services';
import { Password, SecureDataSchema, SessionStateSchema } from '$types';
import { HashSaltSchema, SecureDataSchema, SessionStateSchema } from '$types';
import { QueryClient, createMutation, createQuery } from '@tanstack/svelte-query';

export function createSessionQuery() {
Expand All @@ -20,8 +20,8 @@ export function createSessionQuery() {
key: SESSION_DATA,
area: SESSION
});
const validatedData = SessionStateSchema.safeParse(data);
return validatedData.success ? validatedData.data : false;
const parsedData = SessionStateSchema.safeParse(data);
return parsedData.success ? parsedData.data : false;
}
});
}
Expand All @@ -34,8 +34,9 @@ export function createSetupDeviceKeyQuery() {
key: DEVICE_KEY,
area: LOCAL
});
const validatedData = SecureDataSchema.safeParse(data);
return validatedData.success;

const parsedData = SecureDataSchema.safeParse(data);
return parsedData.success;
}
});
}
Expand All @@ -47,17 +48,17 @@ export function createStoreDeviceKey(queryClient: QueryClient) {
key: PASSWORD,
area: LOCAL
});
const validatePassword = Password.safeParse(result);
const parsedPassword = HashSaltSchema.safeParse(result);

if (validatePassword.success) {
storageService.set({
key: DEVICE_KEY,
value: await encryptData(deviceKey, validatePassword.data),
area: LOCAL
});
if (!parsedPassword.success) {
throw new Error('Something went wrong');
}

throw new Error('Something went wrong');
storageService.set({
key: DEVICE_KEY,
value: await encryptData(deviceKey, parsedPassword.data.hash),
area: LOCAL
});
},

onSuccess: () => {
Expand Down
7 changes: 6 additions & 1 deletion src/lib/types/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ export type KeysState = {
loading: boolean;
};

export const Password = z.string();
export const HashSaltSchema = z.object({
salt: z.string(),
hash: z.string()
});

export type HashSalt = z.infer<typeof HashSaltSchema>;

export const SecureDataSchema = z.object({
encryptedData: z.string(),
Expand Down
4 changes: 2 additions & 2 deletions src/lib/types/storage-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LOCAL, SESSION, SESSION_DATA, PASSWORD, DEVICE_KEY } from '$const';
import { z } from 'zod';
import type { SecureData } from './keys';
import type { HashSalt, SecureData } from './keys';

export type AreaName = typeof SESSION | typeof LOCAL | 'sync' | 'managed';

Expand All @@ -17,7 +17,7 @@ type GetSession = { key: typeof SESSION_DATA; area: typeof SESSION };

type SetPassword = {
key: typeof PASSWORD;
value: string;
value: HashSalt;
area: typeof LOCAL;
};
type GetPassword = { key: typeof PASSWORD; area: typeof LOCAL };
Expand Down
2 changes: 1 addition & 1 deletion src/routes/setup-pass/app-password/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
let password = '';
$: charCount = password.length;
$: isDisabled = charCount < 8 && confirmPassword !== password;
$: isDisabled = charCount < 8 || confirmPassword !== password;
</script>

<Title>Set Key Manager Password</Title>
Expand Down

0 comments on commit 03a8d42

Please sign in to comment.