已发布的源代码
下载源代码 ZIP
CH-J Tools
浏览特定应用版本的目录和文件。
CH-J / Server Manager
1704 个文件
0.1.2-beta
添加时间: · 构建时间:
1
//! Main-window-only Keychain boundary. Native dialogs choose paths. Private key text and stored2
//! passwords cross IPC only through `showPrivate` / `passwordReveal`, each after `require_sensitive_action`.3
use super::{main_core, text, CommandResult};4
use crate::core::{app::CoreSlot, error::CoreError, keychain as keys};5
use serde_json::{json, Value};6
use tauri::{State, Webview};7
use zeroize::Zeroizing;9
#[tauri::command]10
pub 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
}188
}190
/// Showing secret text always needs a fresh proof of the owner, independent of the optional191
/// "require authentication" setting: the vault password, or system biometrics/password.192
async 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."))200
}SHA-256
7f7775e1f008b2b03d687e1ff76bcd567074fc43435cee280263143c50bc5744CH-J Proprietary Software License 1.14 · 源代码依据 CH-J Proprietary Software License 1.14 提供。公开源代码不会改变许可条款,也不会授予额外权利。
压缩包 SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f