CH-J Toolsservera pārvaldība virs SSH
Izvēlne
Publicētais avots

CH-J Tools

Pārlūkot mapes un failus konkrētai lietojumprogrammas izdošanai.

Lejupielādēt avota ZIP
CH-J / Server Manager 1704 faili
0.1.2-beta Pievienots: · Būvēts:
keychain.rs Pievienots: Datums nav reģistrēts 14,4 KB · 200 rindas
Kods Lejupielādēt failu
1//! Main-window-only Keychain boundary. Native dialogs choose paths. Private key text and stored
2//! passwords cross IPC only through `showPrivate` / `passwordReveal`, each after `require_sensitive_action`.
3use super::{main_core, text, CommandResult};
4use crate::core::{app::CoreSlot, error::CoreError, keychain as keys};
5use serde_json::{json, Value};
6use tauri::{State, Webview};
7use zeroize::Zeroizing;
9#[tauri::command]
10pub async fn core_keychain(webview: Webview, slot: State<'_, CoreSlot>, payload: Option<Value>) -> CommandResult {
11 let core = main_core(&webview, &slot)?;
12 let p = super::payload(payload);
13 if text(&p,"action") == "openGenerator" {
14 let entry = if !text(&p,"id").is_empty() {{let generation=core.vault.unlocked_generation()?;Some((keys::get_at_generation(&core.vault,text(&p,"id"),generation)?,generation))}}else{None};
15 let mode = core.services.keys.select_mode(text(&p,"kind"))?;
16 let opened = core.services.modules.open(&core,"chj.key-generator")?;
17 if let Ok(label) = core.services.modules.label_of("chj.key-generator") {
18 if let Some((entry,generation))=entry {core.vault.assert_generation(generation)?;core.services.keys.select_edit(&format!("chj.key-generator:{label}"),&entry,generation);}else{core.services.keys.clear_edit(&format!("chj.key-generator:{label}"));}
19 core.emit_window(&label,"module:keys:modeChanged",mode);
20 }
21 return Ok(opened);
22 }
23 let generation = core.vault.unlocked_generation()?;
24 let check = || core.vault.assert_generation(generation);
25 let parent = crate::dialogs::main_window(&core.app);
26 match text(&p, "action") {
27 "list" => {
28 if let Ok(data) = core.vault.get_data_at_generation(generation) {
29 let entries = data.pointer("/secrets/keychain").and_then(Value::as_array).cloned().unwrap_or_default();
30 let _ = core.services.keysync.mirror(&entries);
31 }
32 keys::list_at_generation(&core.vault, generation)
33 }
34 "syncStatus" => Ok(core.services.keysync.summary()),
35 "syncRun" => { core.services.keysync.notify.notify_one(); Ok(json!({"queued": true})) }
36 "syncRetry" => { core.services.keysync.retry(text(&p, "jobId"))?; Ok(json!({"queued": true})) }
37 "syncDismiss" => { core.services.keysync.dismiss(text(&p, "jobId"))?; Ok(json!({"dismissed": true})) }
38 "remove" => keys::remove_at_generation(&core.vault, text(&p, "id"), generation),
39 "import" => {
40 let Some(path) = crate::dialogs::pick_file(&core.app, parent.as_ref(), Some("Keychain — Import"), &[]).await else { return Ok(json!({"canceled":true})); };
41 check()?;
42 if std::fs::metadata(&path)?.len() > 4 * 1024 * 1024 { return Err(CoreError::plain("Key file is too large.")); }
43 let bytes = Zeroizing::new(keys::bounded_file(&path, 4 * 1024 * 1024)?);
44 let label=text(&p,"label").to_string();let password=Zeroizing::new(text(&p,"passphrase").to_string());
45 let entry=tokio::task::spawn_blocking(move||keys::parse(&bytes,&label,&password)).await.map_err(|_|CoreError::plain("Key import failed."))??;
46 check()?; keys::store_at_generation(&core.vault, entry, generation)
47 }
48 "publish" => {
49 if p["confirmed"] != true {return Err(CoreError::plain("Explicit public-key publishing confirmation is required."));}
50 core.services.biometrics.require_sensitive_action().await?;check()?;
51 // Queued: KeySync uploads, fetches the certificate back and only then reports success.
52 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
53 let kind = if entry["revoked"] == true { "revoke" } else { "publish" };
54 core.services.keysync.enqueue(kind, &entry).map(|job| json!({"queued": true, "job": job}))
55 }
56 "checkServer" => {
57 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
58 core.services.keysync.enqueue("check", &entry).map(|job| json!({"queued": true, "job": job}))
59 }
60 "revoke" | "revokeAndPublish" => {
61 if p["confirmed"] != true { return Err(CoreError::plain("Explicit revocation confirmation is required.")); }
62 core.services.biometrics.require_sensitive_action().await?;
63 check()?;
64 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
65 let password = Zeroizing::new(text(&p, "passphrase").to_string());
66 let before=entry.clone();
67 let next = tokio::task::spawn_blocking(move || keys::revoke(&entry, &password)).await.map_err(|_| CoreError::plain("Revocation failed."))??;
68 check()?;
69 let revoked = keys::commit_revision(&core.vault,generation,&before,next)?;
70 if text(&p,"action") == "revokeAndPublish" {
71 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
72 let job = core.services.keysync.enqueue("revoke", &entry)?;
73 Ok(json!({"revoked": true, "queued": true, "publicationPending": true, "job": job}))
74 } else {Ok(revoked)}
75 }
76 "encryptFile" | "decryptFile" | "signInline" | "verifyInline" => {
77 let operation=text(&p,"action").to_string();
78 if ["decryptFile","signInline"].contains(&operation.as_str()){core.services.biometrics.require_sensitive_action().await?;check()?;}
79 let before=keys::get_at_generation(&core.vault,text(&p,"id"),generation)?;
80 let Some(path)=crate::dialogs::pick_file(&core.app,parent.as_ref(),Some("Keychain — OpenPGP"),&[]).await else{return Ok(json!({"canceled":true}))};check()?;
81 let entry=before.clone();let password=Zeroizing::new(text(&p,"passphrase").to_string());let op=operation.clone();
82 let output=tokio::task::spawn_blocking(move || {
83 let bytes=Zeroizing::new(keys::bounded_file(&path,crate::core::openpgp_data::MAX_DATA)?);
84 use crate::core::openpgp_data as data;
85 match op.as_str(){
86 "encryptFile"=>data::encrypt(&entry,&bytes).map(Zeroizing::new),
87 "decryptFile"=>data::decrypt(&entry,&bytes,&password),
88 "signInline"=>data::sign_inline(&entry,&bytes,&password).map(Zeroizing::new),
89 _=>data::verify_inline(&entry,&bytes),
90 }
91 }).await.map_err(|_|CoreError::plain("OpenPGP operation failed."))??;
92 keys::with_revision(&core.vault,generation,&before,||Ok(()))?;
93 let name=if ["encryptFile","signInline"].contains(&operation.as_str()){"message.pgp"}else{"message"};
94 let Some(destination)=crate::dialogs::save_file(&core.app,parent.as_ref(),Some("Keychain — OpenPGP"),name,&[]).await else{return Ok(json!({"canceled":true}))};
95 keys::with_revision(&core.vault,generation,&before,||crate::core::storage::atomic_write_file(&destination,&output,0o600))?;
96 Ok(json!({"completed":true,"verified":operation=="verifyInline"}))
97 }
98 "sign" | "verify" => {
99 let signing = text(&p, "action") == "sign";
100 let Some(path) = crate::dialogs::pick_file(&core.app, parent.as_ref(), Some("Keychain — File"), &[]).await else { return Ok(json!({"canceled":true})); };
101 check()?;
102 if signing {
103 core.services.biometrics.require_sensitive_action().await?;
104 check()?;
105 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
106 let password = Zeroizing::new(text(&p, "passphrase").to_string());
107 let signature = tokio::task::spawn_blocking(move || keys::sign(&entry, &path, &password)).await.map_err(|_| CoreError::plain("Signing failed."))??;
108 check()?;
109 let Some(destination) = crate::dialogs::save_file(&core.app, parent.as_ref(), Some("Keychain — Signature"), "signature.sig", &[]).await else { return Ok(json!({"canceled":true})); };
110 check()?;
111 core.vault.with_generation(generation, || crate::core::storage::atomic_write_file(&destination, signature.as_bytes(), 0o600))?;
112 Ok(json!({"signed":true}))
113 } else {
114 let Some(signature) = crate::dialogs::pick_file(&core.app, parent.as_ref(), Some("Keychain — Signature"), &[]).await else { return Ok(json!({"canceled":true})); };
115 check()?;
116 let bytes = keys::bounded_file(&signature, 1024 * 1024)?;
117 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
118 let result = tokio::task::spawn_blocking(move || keys::verify(&entry, &path, &bytes)).await.map_err(|_| CoreError::plain("Verification failed."))??;
119 check()?; Ok(result)
120 }
121 }
122 "exportPublic" | "exportPrivate" => {
123 let private = text(&p, "action") == "exportPrivate";
124 if private { core.services.biometrics.require_sensitive_action().await?; }
125 check()?;
126 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
127 let base = crate::core::openpgp::file_base(entry["label"].as_str().unwrap_or(""),entry["fingerprint"].as_str().unwrap_or(""));
128 let suggested = if entry["kind"] == "openpgp" { format!("{base} – {}.asc",if private {"Soukromé"} else {"Veřejné"}) } else { format!("{base}.{}", if private {if entry["privateKeyFormat"] == "ppk" {"ppk"} else {"key"}} else {"pub"}) };
129 let Some(path) = crate::dialogs::save_file(&core.app, parent.as_ref(), Some("Keychain — Export"), &suggested, &[]).await else { return Ok(json!({"canceled":true})); };
130 check()?;
131 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
132 let material = Zeroizing::new(if private { keys::private_export_text(&entry) } else { entry["publicExport"].as_str().or_else(|| entry["public"].as_str()).map(str::to_string) }.ok_or_else(|| CoreError::plain("Private key is not available."))?);
133 core.vault.with_generation(generation, || crate::core::storage::atomic_write_file(&path, material.as_bytes(), 0o600))?;
134 Ok(json!({"exported":true}))
135 }
136 "showPublic" | "showPrivate" => {
137 let private = text(&p, "action") == "showPrivate";
138 if private { reveal_guard(&core, &p).await?; }
139 check()?;
140 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
141 let material = if private { entry["private"].as_str() } else { entry["publicExport"].as_str().or_else(|| entry["public"].as_str()) }
142 .ok_or_else(|| CoreError::plain("Private key is not available."))?;
143 Ok(json!({"text": material}))
144 }
145 "edit" => {
146 // Same operations and revision guard as the Key Generator editor, from the Keychain view.
147 core.services.biometrics.require_sensitive_action().await?;
148 check()?;
149 let before = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
150 let entry = before.clone(); let payload = p.clone();
151 let next = tokio::task::spawn_blocking(move || crate::core::openpgp::edit(&entry, &payload, &|_| Ok(()))).await.map_err(|_| CoreError::plain("Key editing failed."))??;
152 let updated = keys::commit_revision(&core.vault, generation, &before, next)?;
153 core.emit_main("keychain:changed", json!({}));
154 Ok(json!({"stored": true, "identity": updated}))
155 }
156 "exportRevocation" => {
157 core.services.biometrics.require_sensitive_action().await?;
158 check()?;
159 let entry = keys::get_at_generation(&core.vault, text(&p, "id"), generation)?;
160 let passphrase = Zeroizing::new(text(&p, "passphrase").to_string());
161 let for_entry = entry.clone();
162 let certificate = Zeroizing::new(tokio::task::spawn_blocking(move || crate::core::openpgp::revocation_certificate_for(&for_entry, &passphrase)).await.map_err(|_| CoreError::plain("Revocation export failed."))??);
163 let base = crate::core::openpgp::file_base(entry["label"].as_str().unwrap_or(""), entry["fingerprint"].as_str().unwrap_or(""));
164 let Some(path) = crate::dialogs::save_file(&core.app, parent.as_ref(), Some("Keychain — Export"), &format!("{base} – Revokace.rev"), &[]).await else { return Ok(json!({"canceled": true})); };
165 check()?;
166 core.vault.with_generation(generation, || crate::core::storage::atomic_write_file(&path, certificate.as_bytes(), 0o600))?;
167 Ok(json!({"exported": true}))
168 }
169 "algorithms" => Ok(crate::core::keys::openpgp_algorithm_catalog()),
170 "passwordList" => crate::core::passwords::list_at_generation(&core.vault, generation),
171 "passwordSave" => crate::core::passwords::save_at_generation(&core.vault, &p, generation),
172 "passwordRemove" => crate::core::passwords::remove_at_generation(&core.vault, text(&p, "id"), generation),
173 "passwordCopy" => {
174 use tauri_plugin_clipboard_manager::ClipboardExt;
175 reveal_guard(&core, &p).await?;
176 check()?;
177 let password = Zeroizing::new(crate::core::passwords::reveal_at_generation(&core.vault, text(&p, "id"), generation)?);
178 core.app.clipboard().write_text(password.as_str()).map_err(|_| CoreError::plain("Clipboard is unavailable."))?;
179 Ok(json!({"copied": true}))
180 }
181 "passwordReveal" => {
182 reveal_guard(&core, &p).await?;
183 check()?;
184 Ok(json!({"password": crate::core::passwords::reveal_at_generation(&core.vault, text(&p, "id"), generation)?}))
185 }
186 _ => Err(CoreError::plain("Unknown Keychain action."))
187 }
190/// Showing secret text always needs a fresh proof of the owner, independent of the optional
191/// "require authentication" setting: the vault password, or system biometrics/password.
192async fn reveal_guard(core: &crate::core::app::Core, p: &Value) -> Result<(), CoreError> {
193 let password = Zeroizing::new(text(p, "vaultPassword").to_string());
194 if !password.is_empty() {
195 return core.vault.verify_password(&password).await.map(|_| ())
196 .map_err(|_| CoreError::new("REVEAL_PASSWORD_INVALID", "The vault password is incorrect."));
197 }
198 core.services.biometrics.authenticate_sensitive_action().await.map(|_| ())
199 .map_err(|_| CoreError::new("REVEAL_PASSWORD_REQUIRED", "Enter the vault password to show this secret."))
SHA-2567f7775e1f008b2b03d687e1ff76bcd567074fc43435cee280263143c50bc5744

CH-J Proprietary Software License 1.14 · Avots ir sniegts saskaņā ar CH-J Proprietary Software License 1.14. Tā pieejamība nemaina licences noteikumus un nepiešķir papildu tiesības.

Arhīvs SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Faili