checkpoint

This commit is contained in:
2023-09-29 15:52:17 -04:00
parent beed933b15
commit 5936e15184
11 changed files with 188 additions and 93 deletions

View File

@@ -13,6 +13,7 @@
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { deriveKey, encryptData, hashPassword } from '$lib/crypto/crypto';
import { onMount } from 'svelte';
const modalStore = getModalStore();
@@ -26,24 +27,12 @@
value: 'Discreetly',
valueAttr: { type: 'password', minlength: 3, required: true },
response: async (r: string) => {
console.log('Prompt Response: ', r);
const hashedPassword = await hashPassword(r);
$configStore.hashedPwd = hashedPassword;
console.debug(`Hashed Password: ${hashedPassword}`);
$keyStore = await deriveKey(r);
encryptData(
JSON.stringify({
_commitment: $identityStore._commitment,
_nullifier: $identityStore._nullifier,
_trapdoor: $identityStore._trapdoor,
_secret: $identityStore._secret
}),
$keyStore
).then((data) => {
$identityKeyStore.identity = data;
});
$identityStore._commitment = '';
$identityStore._nullifier = '';
$identityStore._trapdoor = '';
$identityStore._secret = '';
console.debug(`Derived Key: ${$keyStore}`);
}
};
modalStore.trigger(modal);
@@ -57,9 +46,15 @@
value: '',
valueAttr: { type: 'password', minlength: 3, required: true },
response: async (r: string) => {
const hashedPassword = await hashPassword(r);
$configStore.hashedPwd = hashedPassword;
$keyStore = await deriveKey(r);
if (r != 'false') {
console.log('Prompt Response: ', r);
const hashedPassword = await hashPassword(r);
if ($configStore.hashedPwd == hashedPassword) {
$keyStore = await deriveKey(r);
} else {
$keyStore = null;
}
}
}
};
modalStore.trigger(modal);
@@ -68,6 +63,13 @@
function lock() {
$keyStore = null;
}
onMount(() => {
console.debug(
'PasswordLock: ',
$passwordSet ? 'password set,' : 'password not set,',
$keyExists ? 'unlocked' : 'locked'
);
});
</script>
<div class={cls} id="lock">

View File

@@ -66,7 +66,8 @@ export async function encryptData(plainText: string, key: CryptoKey): Promise<st
* @param {CryptoKey} key - The cryptographic key for decryption.
* @returns {Promise<string>} - Returns the decrypted text as a Promise.
*/
export async function decryptData(encryptedData: string, key: CryptoKey): Promise<string> {
export async function decryptData(encryptedData: string, key: CryptoKey): Promise<string | null> {
key = key as CryptoKey;
// Decode the base64 encrypted string
const rawData = atob(encryptedData);
@@ -78,11 +79,20 @@ export async function decryptData(encryptedData: string, key: CryptoKey): Promis
const iv = new TextEncoder().encode(rawIv);
const encrypted = new TextEncoder().encode(rawEncryptedData).buffer;
// Decrypt the data
const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted);
try {
if (!window) {
throw new Error('Window not found');
}
// Return the decrypted text
return new TextDecoder().decode(decrypted);
// Decrypt the data
const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted);
// Return the decrypted text
return new TextDecoder().decode(decrypted);
} catch (e) {
console.error(e);
throw new Error('Error decrypting data');
}
}
/**

View File

@@ -1,5 +1,5 @@
import { storable, sessionable, encryptable } from './storeFactory';
import { derived, get, writable } from 'svelte/store';
import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
import { configDefaults } from '$lib/defaults';
import type {
ConfigurationI,
@@ -12,9 +12,7 @@ import type {
selectedRoomStoreI,
serverStoreI,
roomKeyStoreI,
keyStoreI,
DecryptedIdentityStoreI,
CryptedIdentityStoreI
keyStoreI
} from '$lib/types';
import { decryptData } from '$lib/crypto/crypto';
@@ -84,18 +82,6 @@ export const currentRoomMessages = derived(
*/
export const rateLimitStore = sessionable({} as rateLimitStoreI, 'rateLimitStore');
/* ------------------ Identity Stores ------------------*/
/**
* @description Identity store, this is the user's identity UNENCRYPTED
* @deprecated Use identityKeyStore when you can, alert user to set password first
*/
export const identityStore = storable({} as IdentityStoreI, 'identity');
/**
* @description Identity store, this is the user's identity ENCRYPTED
*/
export const identityKeyStore = encryptable({} as CryptedIdentityStoreI, 'identity');
/* ------------------ Configuration / Misc Stores ------------------*/
/**
@@ -142,15 +128,45 @@ export const consoleStore = sessionable(
'consoleStore'
);
export const identityKeyStoreDecrypted = derived(identityKeyStore, ($identityKeyStore) => {
if (keyExists) {
try {
decryptData($identityKeyStore.identity as string, get(keyStore)!).then((decrypted) => {
return { identity: JSON.parse(decrypted) } as DecryptedIdentityStoreI;
});
} catch (e) {
console.error(`Error decrypting identity: ${e}`);
return { identity: {} as IdentityStoreI } as DecryptedIdentityStoreI;
/* ------------------ Identity Stores ------------------*/
/**
* @description Identity store, this is the user's identity UNENCRYPTED
* @deprecated Use identityKeyStore when you can, alert user to set password first
*/
export const identityStore = storable({} as IdentityStoreI, 'identity');
/**
* @description Identity store, this is the user's identity ENCRYPTED
*/
export const identityKeyStore = encryptable('' as string, 'identity');
/**
* @description Derived Store: The user's identity DECRYPTED in memory
*/
export const identityKeyStoreDecrypted: Readable<string | null | undefined> = derived(
[identityKeyStore, keyStore, keyExists],
([$identityKeyStore, $keyStore, $keyExists]) => {
const key = $keyStore;
if ($keyExists && key !== undefined && key !== null) {
try {
if (key !== undefined && key !== null) {
try {
decryptData($identityKeyStore as string, key).then((decrypted) => {
if (decrypted !== null) {
return JSON.parse(decrypted) as IdentityStoreI;
}
});
} catch (e) {
console.error(`Error decrypting identity: ${e}`);
return null;
}
} else {
return null;
}
} catch (e) {
console.error(`Error decrypting identity: ${e}`);
return null;
}
}
}
});
);

View File

@@ -83,11 +83,14 @@ export function encryptable<Type>(data: Type, localStorageKey: string): Writable
if (storedValue) {
try {
const key = get(keyStore);
if (!key) {
if (!key || typeof key !== CryptoKey) {
throw new Error('Key store not initialized, cannot encrypt data');
}
console.debug('Encryptable State: ', storedValue, key);
decryptData(storedValue, key).then((decryptedData) => {
set(JSON.parse(decryptedData) as Type);
if (decryptedData !== null) {
set(JSON.parse(decryptedData) as Type);
}
});
} catch (e) {
console.warn(`Error reading local storage for key: ${localStorageKey}; ${e}`);

View File

@@ -10,7 +10,7 @@ export interface ConfigurationI {
experience?: Experiences;
beta?: boolean;
numMessagesToSave: number;
hashedPwd: string;
hashedPwd: string | null | undefined;
}
export interface RoomI extends RI {

View File

@@ -33,19 +33,6 @@ export interface IdentityStoreI {
_secret: string;
}
export type EncryptedIdentityStoreI = string;
export interface DecryptedIdentityStoreI {
identity: {
_commitment: string;
_trapdoor: string;
_nullifier: string;
_secret: string;
};
}
export type CryptedIdentityStoreI = EncryptedIdentityStoreI | DecryptedIdentityStoreI;
export interface rateLimitStoreI {
[key: string]: {
lastEpoch: number;

View File

@@ -4,7 +4,7 @@ import type { consoleStoreI, consoleMessageI } from '$lib/types/';
export const addConsoleMessage = (
message: string,
type: 'info' | 'userinput' | 'error' | 'warning' | 'space'
type: 'info' | 'userinput' | 'error' | 'warning' | 'space' = 'info'
) => {
let idx: number | undefined;
consoleStore.update((state) => {

View File

@@ -1,12 +1,14 @@
import { get } from 'svelte/store';
import { identityStore } from '../stores';
import { identityKeyStore, identityKeyStoreDecrypted, identityStore, keyStore } from '../stores';
import { Identity } from '@semaphore-protocol/identity';
import type { IdentityStoreI } from '$lib/types';
import { encryptData } from '$lib/crypto/crypto';
export function createIdentity(regenerate = false) {
if (!get(identityStore)._commitment || regenerate) {
console.debug('Creating identity');
const id = new Identity() as unknown as IdentityStoreI;
identityStore.set(id);
console.log('Identity Created! Congrats on your new journey');
return 'created';
@@ -16,8 +18,20 @@ export function createIdentity(regenerate = false) {
}
}
export function getIdentity(): IdentityStoreI {
return get(identityStore) as unknown as IdentityStoreI;
export function getIdentity(): IdentityStoreI | null {
const decryptedIdentity = get(identityKeyStoreDecrypted) as unknown as IdentityStoreI;
if (decryptedIdentity !== null) {
return decryptedIdentity;
} else {
const identity = get(identityStore);
if (identity !== null) {
console.warn('Identity not decrypted, please set a password!');
return identity;
} else {
console.warn('Identity not created, please create an identity!');
}
}
return null;
}
export function getCommitment() {
@@ -31,3 +45,26 @@ export function getIdentityBackup() {
export function doesIdentityExist(): boolean {
return !!get(identityStore)._commitment;
}
export function encryptIdentity(identity: Identity) {
const key = get(keyStore);
if (key !== undefined && key !== null) {
encryptData(
JSON.stringify({
_commitment: identity['_commitment'],
_nullifier: identity['_nullifier'],
_trapdoor: identity['_trapdoor'],
_secret: identity['_secret']
}),
key
).then((data) => {
identityKeyStore.set(data);
});
identityStore.set({
_commitment: '',
_trapdoor: '',
_nullifier: '',
_secret: ''
});
}
}

View File

@@ -45,6 +45,10 @@
{:else}
<a class="btn btn-sm variant-ringed-secondary" href="/signup">Sign Up</a>
{/if}
<a
class="btn btn-sm variant-ringed-secondary font-medium text-sm hidden sm:inline me-2"
href="/console">Console</a
>
<svelte:fragment slot="trail">
<span class="inline sm:hidden me-2 text-primary-500">Alpha Version!</span>
<a href="/about" class="hidden sm:inline me-2"><Information size="1.2em" /></a>

View File

@@ -1,22 +1,44 @@
<script lang="ts">
import { deriveKey, hashPassword } from '$lib/crypto/crypto';
import { configStore, keyStore, passwordSet } from '$lib/stores';
import { decryptData, deriveKey, hashPassword } from '$lib/crypto/crypto';
import {
configStore,
identityKeyStore,
identityKeyStoreDecrypted,
keyExists,
keyStore,
passwordSet
} from '$lib/stores';
import { addConsoleMessage, clearConsoleMessages } from '$lib/utils/';
import { inviteCode } from '$lib/utils/inviteCode';
function help() {
addConsoleMessage('/clear Clears the console', 'info');
addConsoleMessage(' ', 'space');
addConsoleMessage('/join Joins a room via invite code, Example:', 'info');
addConsoleMessage('/join apple-banana-candy-donut', 'info');
addConsoleMessage('Commands: /clear, /join, /help, /unlock, /export');
addConsoleMessage(' ', 'space');
addConsoleMessage('Commands: /clear, /join, /help', 'info');
addConsoleMessage('`/clear` Clears the console');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/join invite-code` Joins a room via invite code, Example:');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/password Password`');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/clearPassword`');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/unlock Password`');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/lock`');
addConsoleMessage(' ', 'space');
addConsoleMessage('`/backup`');
}
async function processCommand(command: string) {
const [cmd, ...args] = command.split(' ');
if (['/unlock', '/password'].includes(cmd)) {
addConsoleMessage(cmd, 'userinput');
} else {
addConsoleMessage(command, 'userinput');
}
switch (cmd) {
case '/help':
case 'help' || '/help':
help();
break;
case '/clear':
@@ -30,7 +52,7 @@
addConsoleMessage(err, 'error');
console.log(err.toString());
} else {
addConsoleMessage(`Added to: ${acceptedRoomNames.join(', ')}`, 'info');
addConsoleMessage(`Added to: ${acceptedRoomNames.join(', ')}`);
}
})
.catch((err) => {
@@ -43,16 +65,24 @@
const hashedNewPwd = await hashPassword(args[1]);
if (hashedOldPwd === $configStore.hashedPwd || $configStore.hashedPwd === undefined) {
$configStore.hashedPwd = hashedNewPwd;
addConsoleMessage('New Password Set', 'info');
addConsoleMessage('New Password Set');
} else {
addConsoleMessage('Invalid Old Password', 'error');
addConsoleMessage('/password OLDPASSWORD NEWPASSWORD', 'warning');
}
break;
case '/clearPassword':
$configStore.hashedPwd = null;
addConsoleMessage('Password Cleared');
break;
case '/lock':
$keyStore = null;
addConsoleMessage('Locked!');
break;
case '/unlock':
const hashedPwd = await hashPassword(args[0]);
if (hashedPwd === $configStore.hashedPwd) {
if ($keyStore && !(args[1] === 'force')) {
if ($keyExists && $keyStore && !(args[1] === 'force')) {
addConsoleMessage(
'Already Unlocked! use `/unlock PASSWORD force` to override this and derive the key again',
'warning'
@@ -62,7 +92,7 @@
deriveKey(args[0])
.then((key) => {
$keyStore = key;
addConsoleMessage('Unlocked!', 'info');
addConsoleMessage('Unlocked!');
})
.catch((err) => {
addConsoleMessage(`Could NOT derive key from password: ${err}`, 'error');
@@ -72,15 +102,22 @@
}
break;
case '/export' || '/backup':
addConsoleMessage('Exporting Identity', 'info');
addConsoleMessage('Exporting Identity');
if ($passwordSet) {
const identity = await decryptData($identityKeyStore, $keyStore);
const blob = new Blob([identity], { type: 'text/plain;charset=utf-8' });
saveAs(blob, 'identity.json');
addConsoleMessage('Identity Exported', 'info');
if ($keyExists && $keyStore) {
addConsoleMessage('Decrypting Data');
console.log($identityKeyStore, $keyStore);
const identity = $identityKeyStoreDecrypted;
if (identity) {
addConsoleMessage(identity);
}
} else {
addConsoleMessage('Please Unlock Keystore!', 'error');
}
} else {
addConsoleMessage('Password not set!', 'error');
}
addConsoleMessage('Exporting Identity Complete');
break;
default:
help();
@@ -92,7 +129,6 @@
if (['Enter'].includes(event.key)) {
if ((event.currentTarget as HTMLInputElement).value) {
const value = (event.currentTarget as HTMLInputElement).value;
addConsoleMessage(value, 'userinput');
if (value.startsWith('/')) {
processCommand(value);
}

View File

@@ -4,7 +4,7 @@
import { onMount } from 'svelte';
import ArrowRight from 'svelte-material-icons/ArrowRightBold.svelte';
import Text from 'svelte-material-icons/TextBox.svelte';
import Account from 'svelte-material-icons/AccountHardhat.svelte';
import Account from 'svelte-material-icons/AccountHardHat.svelte';
import { inviteCode } from '$lib/utils/inviteCode';
import { addConsoleMessage } from '$lib/utils/console';
import Console from '../console/ConsoleInputPrompt.svelte';
@@ -13,9 +13,9 @@
function identity() {
const idStatus = createIdentity();
if (idStatus == 'created') {
addConsoleMessage('Identity Generated 🎉', 'info');
addConsoleMessage('Identity Generated 🎉');
} else if (idStatus == 'exists') {
addConsoleMessage('Identity Exists Already ✅', 'info');
addConsoleMessage('Identity Exists Already ✅');
} else {
addConsoleMessage('Error Creating Identity ❌', 'error');
}
@@ -30,9 +30,9 @@
} else {
let { acceptedRoomNames, err } = await inviteCode($configStore.signUpStatus.inviteCode);
if (acceptedRoomNames) {
addConsoleMessage('Invite Code Accepted 🎉', 'info');
addConsoleMessage('Invite Code Accepted 🎉');
acceptedRoomNames.forEach((roomName) => {
addConsoleMessage(`Joined Room: ${roomName}`, 'info');
addConsoleMessage(`Joined Room: ${roomName}`);
});
}
if (err) {
@@ -43,9 +43,9 @@
onMount(() => {
if (identityExists) {
addConsoleMessage('Identity Exists Already ✅', 'info');
addConsoleMessage('Identity Exists Already ✅');
} else {
addConsoleMessage('Creating Identity...', 'info');
addConsoleMessage('Creating Identity...');
identity();
}