Skip to content

Commit

Permalink
Change functions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrruby committed Dec 8, 2023
1 parent b84dd03 commit 210880f
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 124 deletions.
46 changes: 1 addition & 45 deletions src/lib/helpers/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SecureData, HashSalt } from '$types';
import type { HashSalt } from '$types';

const hexStringToArrayBuffer = (hexString: string) => {
if (hexString.length % 2 !== 0) {
Expand All @@ -11,16 +11,6 @@ const hexStringToArrayBuffer = (hexString: string) => {
return new Uint8Array(hexBytes.map((byte) => parseInt(byte, 16))).buffer;
};

const arrayBufferToBase64 = (buffer: ArrayBuffer): string =>
btoa(String.fromCharCode(...new Uint8Array(buffer)));

const base64ToArrayBuffer = (base64: string): ArrayBuffer =>
new Uint8Array(
atob(base64)
.split('')
.map((char) => char.charCodeAt(0))
).buffer;

const arrayBufferToHexString = (buffer: ArrayBuffer) =>
Array.prototype.map
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
Expand Down Expand Up @@ -57,37 +47,3 @@ export const verifyPassword = async (
const derivedBits = await deriveBits(inputPassword, new Uint8Array(saltBuffer), 100000);
return arrayBufferToHexString(derivedBits) === storedHashSalt.hash;
};

export const encryptData = async (
secretData: Uint8Array,
passwordHashHex: string
): Promise<SecureData> => {
const iv = crypto.getRandomValues(new Uint8Array(12));
const algo: AesGcmParams = { name: 'AES-GCM', iv };
const passwordHash = hexStringToArrayBuffer(passwordHashHex);
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)
};
};

export const decryptData = async (
encryptedData: SecureData,
passwordHashHex: string // Accept hex string
): Promise<Uint8Array> => {
const iv = base64ToArrayBuffer(encryptedData.iv);
const algo = { name: 'AES-GCM', iv };
const passwordHash = hexStringToArrayBuffer(passwordHashHex); // Convert hex string to ArrayBuffer
const key = await crypto.subtle.importKey('raw', passwordHash, algo, false, ['decrypt']);

const decrypted = await crypto.subtle.decrypt(
algo,
key,
base64ToArrayBuffer(encryptedData.encryptedData)
);
return new Uint8Array(decrypted);
};
17 changes: 11 additions & 6 deletions src/lib/queries/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
SESSION_DATA_KEY,
SETUP_PASSWORD
} from '$const';
import { decryptData, encryptData, hashPassword, verifyPassword } from '$helpers';
import { storageService } from '$services';
import { HashSaltSchema, SecureDataSchema } from '$types';
import { hashPassword, verifyPassword } from '$helpers';
import { lockKey, storageService, unlockKey } from '$services';
import { EncryptedDeviceKeySchema, HashSaltSchema } from '$types';
import { createMutation, createQuery, type QueryClient } from '@tanstack/svelte-query';

const getPassword = async () => {
Expand Down Expand Up @@ -81,13 +81,17 @@ export function createChangePasswordWithDeviceKeyMutation(queryClient: QueryClie
area: LOCAL
});

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

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

const decryptedData = await decryptData(parsedDeviceKey.data, parsedResult.data.hash);
console.log(parsedDeviceKey.data);
console.log(parsedResult.data.hash);

const decryptedKey = await unlockKey(parsedDeviceKey.data, parsedResult.data.hash);
console.log('test2');
const newHashSalt = await hashPassword(mutationData.newPassword);

storageService.set({
Expand All @@ -97,9 +101,10 @@ export function createChangePasswordWithDeviceKeyMutation(queryClient: QueryClie
});
storageService.set({
key: DEVICE_KEY,
value: await encryptData(decryptedData, newHashSalt.hash),
value: await lockKey(decryptedKey, newHashSalt.hash),
area: LOCAL
});
decryptedKey.zero();
storageService.set({
key: SESSION_DATA,
value: false,
Expand Down
11 changes: 6 additions & 5 deletions src/lib/queries/sessionAndKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import {
SESSION_DATA_KEY,
SETUP_KEY
} from '$const';
import { encryptData } from '$helpers';
import { storageService } from '$services';
import { HashSaltSchema, SecureDataSchema, SessionStateSchema } from '$types';
import { EncryptedDeviceKeySchema, HashSaltSchema, SessionStateSchema } from '$types';
import { QueryClient, createMutation, createQuery } from '@tanstack/svelte-query';

export function createSessionQuery() {
Expand All @@ -35,15 +34,15 @@ export function createSetupDeviceKeyQuery() {
area: LOCAL
});

const parsedData = SecureDataSchema.safeParse(data);
const parsedData = EncryptedDeviceKeySchema.safeParse(data);
return parsedData.success;
}
});
}

export function createStoreDeviceKey(queryClient: QueryClient) {
return createMutation({
mutationFn: async (deviceKey: Uint8Array) => {
mutationFn: async (deviceKey: string) => {
const result = await storageService.getWithoutCallback({
key: PASSWORD,
area: LOCAL
Expand All @@ -56,9 +55,11 @@ export function createStoreDeviceKey(queryClient: QueryClient) {

storageService.set({
key: DEVICE_KEY,
value: await encryptData(deviceKey, parsedPassword.data.hash),
value: deviceKey,
area: LOCAL
});

console.log(deviceKey);
},

onSuccess: () => {
Expand Down
110 changes: 77 additions & 33 deletions src/lib/services/generate-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,42 @@
import * as hcSeedBundle from 'hcSeedBundle';
import type { GeneratedKeys } from '$types';

export async function generateKeys(passphrase: string): Promise<GeneratedKeys> {
const uint8ArrayToBase64 = (bytes: Uint8Array) => btoa(String.fromCharCode(...bytes));

const base64ToArrayBuffer = (base64: string) => {
const binaryString = atob(base64);
return new Uint8Array([...binaryString].map((char) => char.charCodeAt(0))).buffer;
};

const lock = (root: never, password: string) =>
root.lock([
new hcSeedBundle.SeedCipherPwHash(
hcSeedBundle.parseSecret(new TextEncoder().encode(password)),
'minimum'
)
]);

const deriveAndLock = (
master: never,
derivationPath: number,
bundleType: string,
passphrase: string
) => {
const root = master.derive(derivationPath, {
bundle_type: bundleType
});
root.setAppData({
device_number: derivationPath,
generate_by: 'keymanager-v1.0'
});

return lock(root, passphrase);
};

export async function generateKeys(
passphrase: string,
extensionPassword: string
): Promise<GeneratedKeys> {
await hcSeedBundle.seedBundleReady;

const master = hcSeedBundle.UnlockedSeedBundle.newRandom({
Expand All @@ -11,44 +46,53 @@ export async function generateKeys(passphrase: string): Promise<GeneratedKeys> {
master.setAppData({
generate_by: 'keymanager-v1.0'
});
const encodedMasterBytes: Uint8Array = master.lock([
new hcSeedBundle.SeedCipherPwHash(
hcSeedBundle.parseSecret(new TextEncoder().encode(passphrase)),
'minimum'
)
]);

const revocationDerivationPath = 0;
const encodedRevocationBytes: Uint8Array = await derive(
revocationDerivationPath,
'revocationRoot'
const encodedMasterBytes: Uint8Array = lock(master, passphrase);
const encodedRevocationBytes = deriveAndLock(master, 0, 'revocationRoot', passphrase);
const encodedDeviceBytes = deriveAndLock(master, 1, 'deviceRoot', passphrase);
const encodedDeviceBytesWithExtensionPassword = deriveAndLock(
master,
1,
'deviceRoot',
extensionPassword
);

const deviceNumber = 1;
const encodedDeviceBytes: Uint8Array = await derive(deviceNumber, 'deviceRoot');

master.zero();

return {
master: encodedMasterBytes,
device: encodedDeviceBytes,
revocation: encodedRevocationBytes
encodedMaster: encodedMasterBytes,
encodedDeviceWithExtensionPassword: uint8ArrayToBase64(encodedDeviceBytesWithExtensionPassword),
encodedDevice: encodedDeviceBytes,
encodedRevocation: encodedRevocationBytes
};
}

export const lockKey = async (key: unknown, password: string) => {
await hcSeedBundle.seedBundleReady;

const pw = new TextEncoder().encode(password);
const encodedBytes = key.lock([
new hcSeedBundle.SeedCipherPwHash(hcSeedBundle.parseSecret(pw), 'minimum')
]);

key.zero();

return uint8ArrayToBase64(encodedBytes);
};

async function derive(derivationPath, bundleType) {
const root = master.derive(derivationPath, {
bundle_type: bundleType
});
root.setAppData({
device_number: derivationPath,
generate_by: 'keymanager-v1.0'
});
const encodedBytes = root.lock([
new hcSeedBundle.SeedCipherPwHash(
hcSeedBundle.parseSecret(new TextEncoder().encode(passphrase)),
'minimum'
)
]);
return encodedBytes;
export const unlockKey = async (encodedBytesString: string, password: string) => {
await hcSeedBundle.seedBundleReady;

const cipherList = hcSeedBundle.UnlockedSeedBundle.fromLocked(
base64ToArrayBuffer(encodedBytesString)
);

if (!(cipherList[0] instanceof hcSeedBundle.LockedSeedCipherPwHash)) {
throw new Error('Expecting PwHash');
}
}

const pw = new TextEncoder().encode(password);
const key = cipherList[0].unlock(hcSeedBundle.parseSecret(pw));

return key;
};
1 change: 1 addition & 0 deletions src/lib/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './passphrase';
export * from './password';
export * from './keys-store';
11 changes: 6 additions & 5 deletions src/lib/stores/keys-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { generateKeys } from '../services/generate-keys';
const initKeysStore = () => {
const initialState: KeysState = {
keys: {
master: null,
revocation: null,
device: null
encodedMaster: null,
encodedDeviceWithExtensionPassword: null,
encodedDevice: null,
encodedRevocation: null
},
loading: false
};
Expand All @@ -18,10 +19,10 @@ const initKeysStore = () => {
isInitialState: () =>
subscribe((state) => JSON.stringify(state) === JSON.stringify(initialState)),
subscribe,
generate: async (passphrase: string) => {
generate: async (passphrase: string, extensionPassword: string) => {
update((state) => ({ ...state, loading: true }));
try {
const keys = await generateKeys(passphrase);
const keys = await generateKeys(passphrase, extensionPassword);
set({ keys, loading: false });
} catch (error) {
set(initialState);
Expand Down
5 changes: 4 additions & 1 deletion src/lib/stores/passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { writable } from 'svelte/store';
const createPassphraseStore = () => {
const { subscribe, set } = writable('');

const clean = () => set('');

return {
subscribe,
set
set,
clean
};
};

Expand Down
17 changes: 17 additions & 0 deletions src/lib/stores/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { writable } from 'svelte/store';

const createPasswordStore = () => {
const { subscribe, set } = writable('');

const clean = () => set('');

return {
subscribe,
set,
clean
};
};

const passwordStore = createPasswordStore();

export { passwordStore };
14 changes: 6 additions & 8 deletions src/lib/types/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { z } from 'zod';
export type SetSecret = 'set' | 'confirm';

export type GeneratedKeys = {
master: Uint8Array | null;
device: Uint8Array | null;
revocation: Uint8Array | null;
encodedMaster: Uint8Array | null;
encodedDeviceWithExtensionPassword: string | null;
encodedDevice: Uint8Array | null;
encodedRevocation: Uint8Array | null;
};

export type KeysState = {
Expand All @@ -20,9 +21,6 @@ export const HashSaltSchema = z.object({

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

export const SecureDataSchema = z.object({
encryptedData: z.string(),
iv: z.string()
});
export const EncryptedDeviceKeySchema = z.string();

export type SecureData = z.infer<typeof SecureDataSchema>;
export type EncryptedDeviceKey = z.infer<typeof EncryptedDeviceKeySchema>;
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 { HashSalt, SecureData } from './keys';
import type { HashSalt } from './keys';

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

Expand All @@ -24,7 +24,7 @@ type GetPassword = { key: typeof PASSWORD; area: typeof LOCAL };

type SetDeviceKey = {
key: typeof DEVICE_KEY;
value: SecureData;
value: string;
area: typeof LOCAL;
};
type GetDeviceKey = { key: typeof DEVICE_KEY; area: typeof LOCAL };
Expand Down
Loading

0 comments on commit 210880f

Please sign in to comment.