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}