From 35524473833961b13d1d30cc17fd8631b9035f80 Mon Sep 17 00:00:00 2001 From: artur Date: Fri, 1 Dec 2023 16:38:11 +0300 Subject: [PATCH] Fix Safari doesn't store CryptoKey in IndexedDB --- src/backend.ts | 17 +++++++++++++---- src/db.ts | 3 +-- src/keys.ts | 20 +++++++++++++++++--- src/serviceWorkerRegistration.ts | 4 ++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/backend.ts b/src/backend.ts index 9a4aebb..e32b478 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -201,7 +201,7 @@ export class NoauthBackend { }) if (r.status !== 200 && r.status != 201) { console.log("Fetch error", url, method, r.status) - throw new Error("Failed to fetch"+url) + throw new Error("Failed to fetch" + url) } return await r.json(); @@ -367,6 +367,7 @@ export class NoauthBackend { const npub = nip19.npubEncode(pubkey) const localKey = await this.keysModule.generateLocalKey() const enckey = await this.keysModule.encryptKeyLocal(sk, localKey) + // @ts-ignore const dbKey: DbKey = { npub, enckey, localKey } await dbi.addKey(dbKey) this.enckeys.push(dbKey) @@ -560,7 +561,11 @@ export class NoauthBackend { if (!info) throw new Error(`Key ${npub} not found`) const { type } = nip19.decode(npub) if (type !== "npub") throw new Error(`Invalid npub ${npub}`) - const sk = await this.keysModule.decryptKeyLocal({ enckey: info.enckey, localKey: info.localKey }) + const sk = await this.keysModule.decryptKeyLocal({ + enckey: info.enckey, + // @ts-ignore + localKey: info.localKey + }) await this.startKey({ npub, sk }) } @@ -573,7 +578,11 @@ export class NoauthBackend { private async saveKey(npub: string, passphrase: string) { const info = this.enckeys.find(k => k.npub === npub) if (!info) throw new Error(`Key ${npub} not found`) - const sk = await this.keysModule.decryptKeyLocal({ enckey: info.enckey, localKey: info.localKey }) + const sk = await this.keysModule.decryptKeyLocal({ + enckey: info.enckey, + // @ts-ignore + localKey: info.localKey + }) const { enckey, pwh } = await this.keysModule.encryptKeyPass({ key: sk, passphrase }) await this.sendKeyToServer(npub, enckey, pwh) } @@ -582,7 +591,7 @@ export class NoauthBackend { const { type, data: pubkey } = nip19.decode(npub) if (type !== "npub") throw new Error(`Invalid npub ${npub}`) const { pwh } = await this.keysModule.generatePassKey(pubkey, passphrase) - const { data: enckey } = await this.fetchKeyFromServer(npub, pwh); + const { data: enckey } = await this.fetchKeyFromServer(npub, pwh); // key already exists? const key = this.enckeys.find(k => k.npub === npub) diff --git a/src/db.ts b/src/db.ts index 45ea9d6..d91bc22 100644 --- a/src/db.ts +++ b/src/db.ts @@ -7,7 +7,6 @@ export interface DbKey { avatar?: string relays?: string[] enckey: string - localKey: CryptoKey } export interface DbApp { @@ -57,7 +56,7 @@ export interface DbSchema extends Dexie { export const db = new Dexie('noauthdb') as DbSchema -db.version(5).stores({ +db.version(7).stores({ keys: 'npub', apps: 'appNpub,npub,name,timestamp', perms: 'id,npub,appNpub,perm,value,timestamp', diff --git a/src/keys.ts b/src/keys.ts index 6645896..afec6ac 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -64,7 +64,19 @@ export class Keys { }) } - public async generateLocalKey(): Promise { + private isSafari() { + const chrome = navigator.userAgent.indexOf("Chrome") > -1; + const safari = navigator.userAgent.indexOf("Safari") > -1; + return safari && !chrome + } + + public async generateLocalKey(): Promise { + // https://github.com/dexie/Dexie.js/issues/585 + // Those lazy-asses from Safari still don't allow one + // to store keys in IndexedDB, so for them we have to + // store nsecs in plaintext + if (this.isSafari()) return {} + return await this.subtle.generateKey( { name: ALGO_LOCAL, length: KEY_SIZE_LOCAL }, // NOTE: important to make sure it's not visible in @@ -74,14 +86,16 @@ export class Keys { ) } - public async encryptKeyLocal(key: string, localKey: CryptoKey): Promise { + public async encryptKeyLocal(key: string, localKey: CryptoKey | {}): Promise { + if (this.isSafari()) return key const nsec = nip19.nsecEncode(key) const iv = crypto.randomBytes(IV_SIZE) const encrypted = await this.subtle.encrypt({ name: ALGO_LOCAL, iv }, localKey, Buffer.from(nsec)) return `${PREFIX_LOCAL}:${VERSION_LOCAL}:${iv.toString('hex')}:${Buffer.from(encrypted).toString('hex')}}` } - public async decryptKeyLocal({ enckey, localKey }: { enckey: string, localKey: CryptoKey }): Promise { + public async decryptKeyLocal({ enckey, localKey }: { enckey: string, localKey: CryptoKey | {} }): Promise { + if (this.isSafari()) return enckey const parts = enckey.split(':') if (parts.length !== 4) throw new Error("Bad encrypted key") if (parts[0] !== PREFIX_LOCAL) throw new Error("Bad encrypted key prefix") diff --git a/src/serviceWorkerRegistration.ts b/src/serviceWorkerRegistration.ts index 509d923..cfbb787 100644 --- a/src/serviceWorkerRegistration.ts +++ b/src/serviceWorkerRegistration.ts @@ -58,6 +58,10 @@ export function register(config?: Config) { registerValidSW(swUrl, config); } }); + } else { + if (config && config.onError) { + config.onError(new Error("No service worker")); + } } }