diff --git a/Cargo.lock b/Cargo.lock index f06cbed1d..725147b81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,6 @@ dependencies = [ "tauri-plugin-shell", "tauri-plugin-sql", "tauri-plugin-store", - "tauri-plugin-stronghold", "tauri-plugin-updater", "tauri-plugin-upload", "tauri-plugin-websocket", diff --git a/Cargo.toml b/Cargo.toml index 23e38cbac..d297e4f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,23 +32,6 @@ license = "Apache-2.0 OR MIT" rust-version = "1.77.2" repository = "https://github.com/tauri-apps/plugins-workspace" -# Stronghold encrypts its snapshots with scrypt: unoptimized, saving or loading one -# takes ~45s instead of ~1s, which stalls the (debug) example app and its e2e suite. -[profile.dev.package.scrypt] -opt-level = 3 -[profile.dev.package.salsa20] -opt-level = 3 -[profile.dev.package.pbkdf2] -opt-level = 3 -[profile.dev.package.sha2] -opt-level = 3 -[profile.dev.package.hmac] -opt-level = 3 -[profile.dev.package.iota-crypto] -opt-level = 3 -[profile.dev.package.rust-argon2] -opt-level = 3 - # default to small, optimized release binaries [profile.release] panic = "abort" diff --git a/examples/api/package.json b/examples/api/package.json index c788209c6..193e1f152 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -32,7 +32,6 @@ "@tauri-apps/plugin-shell": "workspace:*", "@tauri-apps/plugin-sql": "workspace:*", "@tauri-apps/plugin-store": "workspace:*", - "@tauri-apps/plugin-stronghold": "workspace:*", "@tauri-apps/plugin-updater": "workspace:*", "@tauri-apps/plugin-upload": "workspace:*", "@tauri-apps/plugin-websocket": "workspace:*", diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index db762fce2..2f0b84bb1 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -43,7 +43,6 @@ tauri-plugin-deep-link = { path = "../../../plugins/deep-link", version = "2.4.1 tauri-plugin-sql = { path = "../../../plugins/sql", version = "2.4.1", features = [ "sqlite", ] } -tauri-plugin-stronghold = { path = "../../../plugins/stronghold", version = "2.3.2" } tauri-plugin-websocket = { path = "../../../plugins/websocket", version = "2.4.3" } # WebDriver automation bridge, used by the plugins e2e suite (packages/api-e2e). diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json index 90c6d976a..51bf9fd02 100644 --- a/examples/api/src-tauri/capabilities/base.json +++ b/examples/api/src-tauri/capabilities/base.json @@ -139,10 +139,6 @@ "deep-link:allow-is-registered", "sql:default", "sql:allow-execute", - "stronghold:default", - "stronghold:allow-destroy", - "stronghold:allow-remove-secret", - "stronghold:allow-remove-store-record", "websocket:default" ] } diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 9a20067df..cb858201a 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -9,7 +9,7 @@ mod tray; use serde::Serialize; use tauri::{ webview::{PageLoadEvent, WebviewWindowBuilder}, - App, AppHandle, Emitter, Listener, Manager, RunEvent, WebviewUrl, + App, AppHandle, Emitter, Listener, RunEvent, WebviewUrl, }; #[derive(Clone, Serialize)] @@ -71,14 +71,6 @@ pub fn run() { .build(), ) .setup(move |app| { - // the argon2 salt lives next to the snapshots the frontend creates - let local_data_dir = app.path().app_local_data_dir()?; - std::fs::create_dir_all(&local_data_dir)?; - app.handle().plugin( - tauri_plugin_stronghold::Builder::with_argon2(&local_data_dir.join("salt.txt")) - .build(), - )?; - #[cfg(desktop)] { // registered before the tray, whose events it tracks diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index 573ff7e19..205b46f07 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -37,7 +37,6 @@ import DeepLink from './views/DeepLink.svelte' import Positioner from './views/Positioner.svelte' import Sql from './views/Sql.svelte' - import Stronghold from './views/Stronghold.svelte' import WebSocket from './views/WebSocket.svelte' import TitleBar from './lib/TitleBar.svelte' @@ -147,11 +146,6 @@ component: Sql, icon: 'i-ph-database' }, - { - label: 'Stronghold', - component: Stronghold, - icon: 'i-ph-lock-key' - }, { label: 'Deep link', component: DeepLink, diff --git a/examples/api/src/views/Stronghold.svelte b/examples/api/src/views/Stronghold.svelte deleted file mode 100644 index 900b2bbba..000000000 --- a/examples/api/src/views/Stronghold.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - -
- {#if stronghold} -
- - - -
- {:else} -
- - -
- {/if} -
diff --git a/packages/api-e2e/README.md b/packages/api-e2e/README.md index e39fc1781..07e97920c 100644 --- a/packages/api-e2e/README.md +++ b/packages/api-e2e/README.md @@ -83,7 +83,6 @@ skipped on desktop, and the rest run everywhere with the odd test gated. | `shell` | `execute`, `spawn` with stdout/stderr/close events, stdin, `kill`, scope enforcement. | Scope only on iOS, which cannot spawn a process at all. | | `sql` | SQLite `load`/`execute`/`select`/`close`, bound values, column types, app-registered migrations, error paths. | Same. | | `store` | CRUD, persistence, auto-save, defaults/reset, reload, `getStore`, `LazyStore`, change events. | Same. | -| `stronghold` | Store records (persisted across reloads), wrong-password and unknown-client errors, vault secrets, BIP39/SLIP10 derivation and Ed25519 signing. | Same. | | `updater` | `check` against the fixture manifest (update / 204 / older release). Installing is never exercised. | Skipped — desktop-only plugin. | | `upload` | `download` and `upload` with progress, methods, headers and error paths. | Same (through `adb reverse` on Android). | | `websocket` | Text/binary echo, ping/pong, handshake headers, listener removal, server and client close, connection and argument errors, against the fixture server. | Same (through `adb reverse` on Android). | diff --git a/packages/api-e2e/package.json b/packages/api-e2e/package.json index b8226b538..b4ce4b2e7 100644 --- a/packages/api-e2e/package.json +++ b/packages/api-e2e/package.json @@ -38,7 +38,6 @@ "@tauri-apps/plugin-shell": "workspace:*", "@tauri-apps/plugin-sql": "workspace:*", "@tauri-apps/plugin-store": "workspace:*", - "@tauri-apps/plugin-stronghold": "workspace:*", "@tauri-apps/plugin-updater": "workspace:*", "@tauri-apps/plugin-upload": "workspace:*", "@tauri-apps/plugin-websocket": "workspace:*", diff --git a/packages/api-e2e/test/helpers/index.ts b/packages/api-e2e/test/helpers/index.ts index 6da724c30..f22992ab7 100644 --- a/packages/api-e2e/test/helpers/index.ts +++ b/packages/api-e2e/test/helpers/index.ts @@ -26,7 +26,6 @@ import type * as Process from '@tauri-apps/plugin-process' import type * as Shell from '@tauri-apps/plugin-shell' import type * as Sql from '@tauri-apps/plugin-sql' import type * as Store from '@tauri-apps/plugin-store' -import type * as Stronghold from '@tauri-apps/plugin-stronghold' import type * as Updater from '@tauri-apps/plugin-updater' import type * as Upload from '@tauri-apps/plugin-upload' import type * as WebSocket from '@tauri-apps/plugin-websocket' @@ -52,7 +51,6 @@ export interface CommonPluginApi { shell: typeof Shell sql: typeof Sql.default store: typeof Store - stronghold: typeof Stronghold upload: typeof Upload websocket: typeof WebSocket.default } diff --git a/packages/api-e2e/test/specs/plugins.spec.ts b/packages/api-e2e/test/specs/plugins.spec.ts index 7fdc00997..70b7bc0bc 100644 --- a/packages/api-e2e/test/specs/plugins.spec.ts +++ b/packages/api-e2e/test/specs/plugins.spec.ts @@ -108,7 +108,6 @@ const commonSurface: Surface = { // the `Database` class itself sql: ['load', 'get'], store: ['load', 'getStore', 'LazyStore', 'Store'], - stronghold: ['Location', 'Client', 'Store', 'Vault', 'Stronghold'], upload: ['download', 'upload', 'HttpMethod'], // the `WebSocket` class itself websocket: ['connect'] diff --git a/packages/api-e2e/test/specs/stronghold.spec.ts b/packages/api-e2e/test/specs/stronghold.spec.ts deleted file mode 100644 index 4a4960125..000000000 --- a/packages/api-e2e/test/specs/stronghold.spec.ts +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -import { expect } from '@wdio/globals' -import { - tauri, - tauriError, - describePlugin, - scratchDir -} from '../helpers/index.js' - -// The example derives the snapshot key from the password with argon2. Each -// test works on its own snapshot file under the spec's scratch directory, -// which the fs plugin cleans up; the snapshot path is resolved inside the page. - -const dir = scratchDir('stronghold') -const password = 'e2e-password' -const clientName = 'e2e-client' - -/** An arbitrary, valid BIP39 mnemonic, so derived keys are deterministic. */ -const mnemonic = - 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' - -describePlugin('stronghold', () => { - before(async () => { - await tauri(async (api, dir) => { - const baseDir = api.fs.BaseDirectory.AppData - if (await api.fs.exists(dir, { baseDir })) { - await api.fs.remove(dir, { baseDir, recursive: true }) - } - await api.fs.mkdir(dir, { baseDir, recursive: true }) - }, dir) - }) - - after(async () => { - await tauri(async (api, dir) => { - const baseDir = api.fs.BaseDirectory.AppData - if (await api.fs.exists(dir, { baseDir })) { - await api.fs.remove(dir, { baseDir, recursive: true }) - } - }, dir) - }) - - it('store records round-trip and persist in the snapshot', async () => { - const result = await tauri( - async (api, dir, password, clientName) => { - const path = await api.path.join( - await api.path.appDataDir(), - dir, - 'store.stronghold' - ) - const value = Array.from(new TextEncoder().encode('top secret')) - - const stronghold = await api.stronghold.Stronghold.load(path, password) - const client = await stronghold.createClient(clientName) - await client.getStore().insert('key', value) - const sameSession = await client.getStore().get('key') - await stronghold.save() - await stronghold.unload() - - const reopened = await api.stronghold.Stronghold.load(path, password) - const store = (await reopened.loadClient(clientName)).getStore() - const persisted = await store.get('key') - const missing = await store.get('missing') - await reopened.unload() - - return { - sameSession: sameSession && Array.from(sameSession), - persisted: persisted && new TextDecoder().decode(persisted), - missing, - fileExists: await api.fs.exists(path) - } - }, - dir, - password, - clientName - ) - expect(result.sameSession).toEqual( - Array.from(new TextEncoder().encode('top secret')) - ) - expect(result.persisted).toBe('top secret') - expect(result.missing).toBeNull() - expect(result.fileExists).toBe(true) - }) - - it('store.remove returns the removed value', async () => { - const result = await tauri( - async (api, dir, password, clientName) => { - const path = await api.path.join( - await api.path.appDataDir(), - dir, - 'remove.stronghold' - ) - const stronghold = await api.stronghold.Stronghold.load(path, password) - const store = (await stronghold.createClient(clientName)).getStore() - await store.insert('key', [1, 2, 3]) - const removed = await store.remove('key') - const after = await store.get('key') - await stronghold.unload() - return { removed: removed && Array.from(removed), after } - }, - dir, - password, - clientName - ) - expect(result).toEqual({ removed: [1, 2, 3], after: null }) - }) - - it('a snapshot cannot be opened with the wrong password', async () => { - const path = await tauri( - async (api, dir, password, clientName) => { - const path = await api.path.join( - await api.path.appDataDir(), - dir, - 'password.stronghold' - ) - const stronghold = await api.stronghold.Stronghold.load(path, password) - await stronghold.createClient(clientName) - await stronghold.unload() - return path - }, - dir, - password, - clientName - ) - const error = await tauriError( - (api, path) => api.stronghold.Stronghold.load(path, 'wrong-password'), - path - ) - expect(error).toMatch(/failed to decode\/decrypt/) - }) - - it('loading a client that was never created is rejected', async () => { - const error = await tauriError( - async (api, dir, password) => { - const path = await api.path.join( - await api.path.appDataDir(), - dir, - 'no-client.stronghold' - ) - const stronghold = await api.stronghold.Stronghold.load(path, password) - try { - await stronghold.loadClient('never-created') - } finally { - await stronghold.unload() - } - }, - dir, - password - ) - expect(error).toMatch(/error loading client data/) - }) - - it('vault procedures derive keys and sign without exposing secrets', async () => { - const result = await tauri( - async (api, dir, password, clientName, mnemonic) => { - const { Location } = api.stronghold - const path = await api.path.join( - await api.path.appDataDir(), - dir, - 'vault.stronghold' - ) - const stronghold = await api.stronghold.Stronghold.load(path, password) - const vault = (await stronghold.createClient(clientName)).getVault( - 'vault' - ) - - // the same mnemonic recovered twice derives the same key - const seedA = Location.generic('vault', 'seed-a') - const seedB = Location.generic('vault', 'seed-b') - await vault.recoverBIP39(mnemonic, seedA) - await vault.recoverBIP39(mnemonic, seedB) - // Ed25519 SLIP-10 only derives hardened indices (the high bit set). - // (No named helper: the transpiler would wrap it in a `__name` call - // that does not exist in the page.) - const chain = [44, 4218, 0, 0, 0].map((i) => (i | 0x80000000) >>> 0) - const otherChain = [44, 4218, 0, 0, 1].map( - (i) => (i | 0x80000000) >>> 0 - ) - const keyA = Location.generic('vault', 'key-a') - const keyB = Location.generic('vault', 'key-b') - const keyOther = Location.generic('vault', 'key-other') - await vault.deriveSLIP10(chain, 'Seed', seedA, keyA) - await vault.deriveSLIP10(chain, 'Seed', seedB, keyB) - await vault.deriveSLIP10(otherChain, 'Seed', seedA, keyOther) - - const publicA = Array.from(await vault.getEd25519PublicKey(keyA)) - const publicB = Array.from(await vault.getEd25519PublicKey(keyB)) - const publicOther = Array.from( - await vault.getEd25519PublicKey(keyOther) - ) - const signature = Array.from(await vault.signEd25519(keyA, 'message')) - const signatureAgain = Array.from( - await vault.signEd25519(keyA, 'message') - ) - - // a random seed and a raw secret can be stored and removed - const random = Location.generic('vault', 'random') - await vault.generateSLIP10Seed(random) - await vault.insert('raw', [9, 9, 9]) - await vault.remove(Location.generic('vault', 'raw')) - - // a generated mnemonic recovers the seed generateBIP39 stored - const generatedSeed = Location.generic('vault', 'generated') - const mnemonicBytes = await vault.generateBIP39(generatedSeed) - const recoveredSeed = Location.generic('vault', 'recovered') - await vault.recoverBIP39( - new TextDecoder().decode(mnemonicBytes), - recoveredSeed - ) - const generatedKey = Location.generic('vault', 'generated-key') - const recoveredKey = Location.generic('vault', 'recovered-key') - await vault.deriveSLIP10(chain, 'Seed', generatedSeed, generatedKey) - await vault.deriveSLIP10(chain, 'Seed', recoveredSeed, recoveredKey) - const generatedPublic = Array.from( - await vault.getEd25519PublicKey(generatedKey) - ) - const recoveredPublic = Array.from( - await vault.getEd25519PublicKey(recoveredKey) - ) - - await stronghold.unload() - return { - publicA, - publicB, - publicOther, - signature, - signatureAgain, - generatedPublic, - recoveredPublic - } - }, - dir, - password, - clientName, - mnemonic - ) - expect(result.publicA).toHaveLength(32) - expect(result.publicB).toEqual(result.publicA) - expect(result.publicOther).not.toEqual(result.publicA) - // Ed25519 signatures are 64 bytes and deterministic - expect(result.signature).toHaveLength(64) - expect(result.signatureAgain).toEqual(result.signature) - expect(result.recoveredPublic).toEqual(result.generatedPublic) - expect(result.generatedPublic).not.toEqual(result.publicA) - }) -}) diff --git a/packages/api-e2e/wdio.mobile.ts b/packages/api-e2e/wdio.mobile.ts index 815802ac8..4622ccfa6 100644 --- a/packages/api-e2e/wdio.mobile.ts +++ b/packages/api-e2e/wdio.mobile.ts @@ -303,33 +303,12 @@ function tauriCli(args: string[], env: NodeJS.ProcessEnv = {}): void { * Environment for the Android build. * * - No debug info: the suite needs a debug build (webview debugging follows - * `debug_assertions`), but with the stronghold, sql and websocket plugins the + * `debug_assertions`), but with the sql and websocket plugins the * debug info alone grows the APK past what a default emulator can install * ("not enough space"). - * - On a macOS host, `AR`/`RANLIB` point at the NDK's LLVM tools. Autotools-built - * C dependencies (libsodium, through the stronghold plugin) otherwise fall back - * to Apple's `ar`/`ranlib`, which silently produce an empty archive from the - * Android (ELF) objects, and the app then fails to load its library with an - * unresolved symbol. Linux hosts' GNU `ar` is fine. */ function androidBuildEnv(): NodeJS.ProcessEnv { - const env: NodeJS.ProcessEnv = { CARGO_PROFILE_DEV_DEBUG: '0' } - const ndk = process.env.NDK_HOME ?? process.env.ANDROID_NDK_HOME - if (process.platform !== 'darwin' || !ndk) return env - // the NDK only ships an x86_64 (Rosetta-compatible) macOS toolchain - const bin = path.join( - ndk, - 'toolchains', - 'llvm', - 'prebuilt', - 'darwin-x86_64', - 'bin' - ) - return { - ...env, - AR: path.join(bin, 'llvm-ar'), - RANLIB: path.join(bin, 'llvm-ranlib') - } + return { CARGO_PROFILE_DEV_DEBUG: '0' } } /** `adb` from the Android SDK, else whatever is on `PATH`. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f445e0f2c..224f427b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,9 +219,6 @@ importers: '@tauri-apps/plugin-store': specifier: workspace:* version: link:../../plugins/store - '@tauri-apps/plugin-stronghold': - specifier: workspace:* - version: link:../../plugins/stronghold '@tauri-apps/plugin-updater': specifier: workspace:* version: link:../../plugins/updater @@ -337,9 +334,6 @@ importers: '@tauri-apps/plugin-store': specifier: workspace:* version: link:../../plugins/store - '@tauri-apps/plugin-stronghold': - specifier: workspace:* - version: link:../../plugins/stronghold '@tauri-apps/plugin-updater': specifier: workspace:* version: link:../../plugins/updater