Skip to main content

tauri_plugin_keyring_store/
models.rs

1//! Request and response types shared between Rust, IPC, and TypeScript (camelCase where noted).
2//!
3//! Use [`BytesDto`] for Stronghold-compatible `string | bytes` payloads and [`ProcedureDto`] for
4//! optional `crypto` feature procedures.
5
6use std::{fmt, time::Duration};
7
8use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
9
10#[derive(Debug, Deserialize, Serialize, Clone)]
11#[serde(rename_all = "camelCase")]
12/// IPC ping request (health / round-trip).
13pub struct PingRequest {
14    /// Echo payload from the client, if any.
15    pub value: Option<String>,
16}
17
18#[derive(Debug, Clone, Default, Deserialize, Serialize)]
19#[serde(rename_all = "camelCase")]
20/// IPC ping response.
21pub struct PingResponse {
22    /// Echo payload returned to the client.
23    pub value: Option<String>,
24}
25
26/// Bytes payload compatible with Stronghold's JS API (`string | bytes`).
27///
28/// # Example
29///
30/// ```
31/// use tauri_plugin_keyring_store::BytesDto;
32///
33/// let text = BytesDto::Text("client-id".into());
34/// let raw = BytesDto::Raw(vec![0x01, 0xff]);
35/// assert_eq!(text.as_ref(), b"client-id");
36/// assert_eq!(raw.as_ref(), [1, 255]);
37/// ```
38#[derive(Debug, Clone, Deserialize, Serialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
39#[serde(untagged)]
40pub enum BytesDto {
41    /// UTF-8 string (stored/compared as bytes via [`AsRef`]).
42    Text(String),
43    /// Raw binary (e.g. client id bytes).
44    Raw(Vec<u8>),
45}
46
47impl AsRef<[u8]> for BytesDto {
48    fn as_ref(&self) -> &[u8] {
49        match self {
50            Self::Text(t) => t.as_bytes(),
51            Self::Raw(b) => b.as_ref(),
52        }
53    }
54}
55
56impl From<BytesDto> for Vec<u8> {
57    fn from(v: BytesDto) -> Self {
58        match v {
59            BytesDto::Text(t) => t.into_bytes(),
60            BytesDto::Raw(b) => b,
61        }
62    }
63}
64
65/// Stronghold-compatible duration (ignored for OS keyring persistence).
66///
67/// # Example
68///
69/// ```
70/// use tauri_plugin_keyring_store::DurationDto;
71/// use std::time::Duration;
72///
73/// let d = DurationDto { secs: 1, nanos: 500 };
74/// let std_d: Duration = d.into();
75/// assert_eq!(std_d.as_secs(), 1);
76/// ```
77#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct DurationDto {
80    /// Whole seconds.
81    pub secs: u64,
82    /// Sub-second nanoseconds (`0..1_000_000_000`).
83    pub nanos: u32,
84}
85
86impl From<DurationDto> for Duration {
87    fn from(d: DurationDto) -> Self {
88        Duration::new(d.secs, d.nanos)
89    }
90}
91
92#[derive(Debug, Clone, Deserialize, Serialize)]
93#[serde(tag = "type", content = "payload")]
94/// Logical storage location inside a snapshot (vault + record or counter row).
95pub enum LocationDto {
96    /// Arbitrary record under a named vault.
97    Generic {
98        /// Vault id (often a string name).
99        vault: BytesDto,
100        /// Record path inside the vault.
101        record: BytesDto,
102    },
103    /// Counter-style record (`counter` serialized as text for hashing).
104    Counter {
105        /// Vault id.
106        vault: BytesDto,
107        /// Monotonic row index.
108        counter: usize,
109    },
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
113#[serde(tag = "type", content = "payload")]
114/// SLIP10 derive input: either a seed blob or an extended parent key.
115pub enum Slip10DeriveInputDto {
116    /// Read SLIP10 seed bytes from this location.
117    Seed(LocationDto),
118    /// Read 65-byte extended SLIP10 key from this location.
119    Key(LocationDto),
120}
121
122#[derive(Debug, Clone, Copy)]
123/// Asymmetric key kind for `PublicKey` procedure (string JSON: `ed25519` / `x25519`).
124pub enum KeyType {
125    /// Ed25519 curve (supported for derive/sign/public key).
126    Ed25519,
127    /// X25519 (public key extraction not implemented in this plugin).
128    X25519,
129}
130
131impl<'de> Deserialize<'de> for KeyType {
132    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
133    where
134        D: Deserializer<'de>,
135    {
136        struct KeyTypeVisitor;
137
138        impl<'de> Visitor<'de> for KeyTypeVisitor {
139            type Value = KeyType;
140
141            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
142                formatter.write_str("ed25519 or x25519")
143            }
144
145            fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
146            where
147                E: serde::de::Error,
148            {
149                match value.to_lowercase().as_str() {
150                    "ed25519" => Ok(KeyType::Ed25519),
151                    "x25519" => Ok(KeyType::X25519),
152                    _ => Err(serde::de::Error::custom("unknown key type")),
153                }
154            }
155        }
156
157        deserializer.deserialize_str(KeyTypeVisitor)
158    }
159}
160
161impl Serialize for KeyType {
162    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
163    where
164        S: Serializer,
165    {
166        let s = match self {
167            KeyType::Ed25519 => "ed25519",
168            KeyType::X25519 => "x25519",
169        };
170        serializer.serialize_str(s)
171    }
172}
173
174/// Stronghold-shaped crypto or signing operation (requires the **`crypto`** crate feature).
175#[derive(Debug, Clone, Deserialize, Serialize)]
176#[serde(tag = "type", content = "payload")]
177pub enum ProcedureDto {
178    /// Generate a new SLIP10 master and write the extended key to `output`.
179    SLIP10Generate {
180        /// Target vault location for the new master key material.
181        output: LocationDto,
182        /// Seed length in bytes (default 32, clamped to at least 16).
183        #[serde(rename = "sizeBytes")]
184        size_bytes: Option<usize>,
185    },
186    /// Derive a child SLIP10 key along `chain` and write to `output`.
187    SLIP10Derive {
188        /// Hardened segment indices (same semantics as Stronghold).
189        chain: Vec<u32>,
190        /// Parent seed or extended key read from `input`.
191        input: Slip10DeriveInputDto,
192        /// Where to write derived extended key bytes.
193        output: LocationDto,
194    },
195    /// Restore BIP39 mnemonic to SLIP10 master at `output`.
196    BIP39Recover {
197        /// Space-separated mnemonic (English wordlist).
198        mnemonic: String,
199        /// Optional BIP39 passphrase.
200        passphrase: Option<String>,
201        /// Where to write extended master key bytes.
202        output: LocationDto,
203    },
204    /// Generate entropy, build an English mnemonic, derive the BIP39 seed → SLIP10 master, and write extended key bytes to `output` (return value matches written bytes).
205    BIP39Generate {
206        /// Optional BIP39 passphrase for seed derivation.
207        passphrase: Option<String>,
208        /// Where to write extended master key bytes.
209        output: LocationDto,
210    },
211    /// Extract public key bytes (32-byte Ed25519 pk) from a private or extended key at `private_key`.
212    PublicKey {
213        /// Curve / algorithm selector.
214        #[serde(rename = "type")]
215        ty: KeyType,
216        /// Location of 32-byte secret key or 65-byte extended SLIP10 key.
217        #[serde(rename = "privateKey")]
218        private_key: LocationDto,
219    },
220    /// Sign `msg` (UTF-8 interpreted as bytes) with Ed25519 private key at `private_key`.
221    Ed25519Sign {
222        /// Location of signing key material.
223        #[serde(rename = "privateKey")]
224        private_key: LocationDto,
225        /// Message to sign (UTF-8 string, not hex).
226        msg: String,
227    },
228}
229
230/// One row for the bulk **set passwords** IPC command (`account` + plaintext `secret`).
231#[derive(Debug, Clone, Deserialize, Serialize)]
232#[serde(rename_all = "camelCase")]
233pub struct PasswordEntryDto {
234    /// Logical account id (often `prefix.name` from [`crate::join_prefix`]).
235    pub account: String,
236    /// Secret value to store (plaintext over IPC — see README security notes).
237    pub secret: String,
238}
239
240/// Single entry in a plaintext password backup (export/import).
241#[derive(Debug, Clone, Deserialize, Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct PasswordBackupEntryDto {
244    /// Account id included in the backup.
245    pub account: String,
246    /// Stored secret, or [`None`] if the credential was missing at export time.
247    pub secret: Option<String>,
248}
249
250/// Plaintext backup blob shared by export/import JSON over IPC.
251#[derive(Debug, Clone, Deserialize, Serialize)]
252#[serde(rename_all = "camelCase")]
253pub struct PasswordBackupPlainDto {
254    /// Schema version for forward compatibility.
255    pub format_version: u32,
256    /// All exported rows.
257    pub entries: Vec<PasswordBackupEntryDto>,
258}
259
260/// Argon2id + ChaCha20-Poly1305 backup envelope (always built).
261#[derive(Debug, Clone, Deserialize, Serialize)]
262#[serde(rename_all = "camelCase")]
263pub struct PasswordBackupEncryptedDto {
264    /// Schema version for forward compatibility.
265    pub format_version: u32,
266    /// Salt for Argon2 (Base64).
267    pub salt: String,
268    /// Nonce for ChaCha20-Poly1305 (Base64).
269    pub nonce: String,
270    /// Ciphertext including Poly1305 tag (Base64).
271    pub ciphertext: String,
272}