@kodama.page/security-tauri
v0.2.4
Published
Tauri SecurityProvider for Kodama — opaque KeyHandles via tauri-plugin-kodama-security.
Downloads
824
Readme
@kodama.page/security-tauri
TypeScript SecurityProvider for Kodama desktop apps. Talks to
tauri-plugin-kodama-security over IPC
using opaque key handles — private keys never enter the WebView.
Composition
import { invoke } from "@tauri-apps/api/core";
import {
createTauriSecurityProvider,
KodamaKeyCustody,
} from "@kodama.page/security-tauri";
const security = createTauriSecurityProvider({ invoke });
const custody = new KodamaKeyCustody(invoke);
await custody.unlock(userPassword);
const key = await custody.createKey({
product: "note",
placeId: "place-1",
deviceId: "device-1",
purpose: "inbox-encryption",
});
// key.keyHandle is opaque — no private key bytesProduct protocols must depend only on @kodama.page/core.
Custody API
| Method | Plugin command |
|--------|----------------|
| unlock / lock | ksc_custody_unlock / lock |
| createKey | ksc_custody_create_key |
| hasKey | ksc_custody_has_key |
| rotateKey | ksc_custody_rotate_key (previous → decrypt-only) |
| listKeyMetadata | ksc_custody_list_key_metadata |
| deleteKey | soft-delete → pending-deletion |
| resetKey | destructive purge (separate capability) |
| exportPublicKey | public key only |
| encryptWithKey / decryptWithKey / signWithKey | use opaque handles |
Not provided: exportPrivateKey.
Namespace: product / placeId / deviceId / keyPurpose / keyVersion.
Grant Tauri capabilities per window (allow-custody, plus delete/reset as needed).
Stronghold unlock
- Host stores an encrypted Stronghold snapshot under the app data directory (or
KODAMA_SECURITY_VAULT). - Call
custody.unlock(password)once per session before custody ops. - Password is Argon2id-stretched on the Rust side; JS never sees derived vault keys or private keys.
custody.lock()clears the in-memory vault client.
See the plugin README for rotation journaling and permission sets.
Archive Master Key
import { KodamaArchiveVault, ARCHIVE_TEST_ARGON2ID_PARAMS } from "@kodama.page/security-tauri";
const archive = new KodamaArchiveVault(invoke);
await archive.createArchiveVault({
product: "note",
placeId: "place-1",
password: userPassword,
});
await archive.enableAutoLock({ idleMs: 15 * 60 * 1000 });
// Password change rewraps only the AMK — historical ciphertext is untouched.
await archive.changeArchivePassword({
product: "note",
placeId: "place-1",
oldPassword,
newPassword,
});
const destroyed = await archive.destroyArchiveVault({
product: "note",
placeId: "place-1",
password: newPassword,
});
// destroyed.warning explains that history may become permanently unreadableHierarchy: password → Argon2id → PKEK → AMK → historical keys / archive records.
The password is never stored and must not encrypt message history directly.
Streaming attachments
import { KodamaAttachmentStreaming } from "@kodama.page/security-tauri";
const attachments = new KodamaAttachmentStreaming(invoke);
const begin = await attachments.beginAttachmentEncryption({
placeId: "place-1",
objectId: "msg-1",
attachmentId: "att-1",
totalBytes: fileSize,
sourcePath: "/path/to/file", // optional; omit to feed plaintext chunks from JS
metadata: { filename: "photo.jpg", mediaType: "image/jpeg" },
});
for (let i = 0; i < begin.totalChunks; i++) {
const { chunk, progress } = await attachments.encryptNextChunk({
operationHandle: begin.operationHandle,
});
await deliveryClient.uploadChunk(chunk); // product-owned — not KSC
void progress; // no private metadata
}
const { manifest, fileKeyHandle } = await attachments.finalizeAttachmentEncryption(
begin.operationHandle,
);Rust crates
crates/kodama-security-core— native crypto + opaque storecrates/tauri-plugin-kodama-security— capability-controlled Tauri 2 plugin + Stronghold custody
See ARCHITECTURE.md.
