fix: wipe long-lived credential copies from memory
Session-only credentials (kept for a whole session so reconnects don't re-prompt), credentials pending until a connection succeeds, and the credential prompt's text buffer are now Zeroizing and wiped when dropped, including when a prompt is cancelled or abandoned (SEC-20261012-007, partial). Still open: the values handed to the protocol clients are plain Strings for the duration of authentication, egui keeps its own copies of typed text, and no mlock/PR_SET_DUMPABLE is applied.
This commit is contained in:
@@ -13,6 +13,7 @@ nexus-core = { path = "../core" }
|
||||
eframe = { version = "0.36", default-features = false, features = ["default_fonts", "glow", "wayland", "x11", "accesskit", "persistence"] }
|
||||
egui = "0.36"
|
||||
parking_lot = "0.12"
|
||||
zeroize = "1"
|
||||
egui-phosphor = "0.14"
|
||||
rfd = "0.17"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
|
||||
+16
-3
@@ -50,8 +50,21 @@ pub fn post_error_nonblocking(e: String) {
|
||||
|
||||
/// A credential or host-key question from a connecting session.
|
||||
pub enum Prompt {
|
||||
Secret { session: String, title: String, message: String, value: String, reply: mpsc::Sender<String> },
|
||||
HostKey { host: String, key_type: String, fingerprint: String, stored: Option<String>, reply: mpsc::Sender<HostKeyDecision> },
|
||||
/// `value` is wiped when the prompt is dropped (answered, cancelled or abandoned).
|
||||
Secret {
|
||||
session: String,
|
||||
title: String,
|
||||
message: String,
|
||||
value: zeroize::Zeroizing<String>,
|
||||
reply: mpsc::Sender<String>,
|
||||
},
|
||||
HostKey {
|
||||
host: String,
|
||||
key_type: String,
|
||||
fingerprint: String,
|
||||
stored: Option<String>,
|
||||
reply: mpsc::Sender<HostKeyDecision>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Modal dialogs owned by the main window.
|
||||
@@ -507,7 +520,7 @@ impl App {
|
||||
let _ = (session, title, message, reply.send(self.answers.remove(0)));
|
||||
}
|
||||
Event::SecretPrompt { session, title, message, reply } => {
|
||||
self.prompts.push_back(Prompt::Secret { session, title, message, value: String::new(), reply });
|
||||
self.prompts.push_back(Prompt::Secret { session, title, message, value: Default::default(), reply });
|
||||
}
|
||||
Event::HostKeyVerify { host, key_type, fingerprint, stored, reply, .. } => {
|
||||
self.prompts.push_back(Prompt::HostKey { host, key_type, fingerprint, stored, reply });
|
||||
|
||||
@@ -695,12 +695,12 @@ pub fn prompts(app: &mut App, ctx: &egui::Context) {
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
ui.label(message.as_str());
|
||||
let r = ui.add(egui::TextEdit::singleline(value).password(true).desired_width(f32::INFINITY));
|
||||
let r = ui.add(egui::TextEdit::singleline(&mut **value).password(true).desired_width(f32::INFINITY));
|
||||
r.request_focus();
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
if widgets::primary(ui, icon::CHECK, "OK").clicked() || enter {
|
||||
let _ = reply.send(std::mem::take(value));
|
||||
let _ = reply.send(std::mem::take(&mut **value));
|
||||
done = true;
|
||||
}
|
||||
if ui.button("Cancel").clicked() || esc {
|
||||
|
||||
Reference in New Issue
Block a user