1
0
mirror of https://github.com/pcvolkmer/cert-tools.git synced 2025-04-19 09:06:49 +00:00

feat: improve ui

This commit is contained in:
Paul-Christian Volkmer 2025-01-20 03:27:36 +01:00
parent 845c7331a3
commit 01f297d1bf
2 changed files with 110 additions and 98 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -1,17 +1,16 @@
#![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
use cert_tools::{Chain, PrivateKey}; use cert_tools::{Chain, PrivateKey};
use iced::border::Radius;
use iced::widget::text_editor::{default, Content, Status}; use iced::widget::text_editor::{default, Content, Status};
use iced::widget::{ use iced::widget::{
button, column, container, horizontal_rule, horizontal_space, row, text, text_editor, button, column, container, horizontal_rule, horizontal_space, row, text, text_editor,
Container, Scrollable, Container, Scrollable,
}; };
use iced::{ use iced::{
alignment, application, clipboard, Background, Color, Element, Font, Length, Pixels, Settings, alignment, application, clipboard, color, Background, Border, Color, Element, Font, Length,
Size, Task, Pixels, Settings, Size, Task,
}; };
use itertools::Itertools;
use std::cmp::Ordering;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
@ -55,7 +54,8 @@ struct Ui {
fixed_chain: Option<Chain>, fixed_chain: Option<Chain>,
output: Content, output: Content,
status: String, status: String,
indicator_state: IndicatorState, chain_indicator_state: IndicatorState,
key_indicator_state: IndicatorState,
} }
impl Ui { impl Ui {
@ -70,7 +70,8 @@ impl Ui {
fixed_chain: None, fixed_chain: None,
output: Content::default(), output: Content::default(),
status: String::new(), status: String::new(),
indicator_state: IndicatorState::Unknown, chain_indicator_state: IndicatorState::Unknown,
key_indicator_state: IndicatorState::Unknown,
}, },
Task::none(), Task::none(),
) )
@ -88,18 +89,12 @@ impl Ui {
Message::PickKeyFile => Task::perform(pick_file(), Message::SetKeyFile), Message::PickKeyFile => Task::perform(pick_file(), Message::SetKeyFile),
Message::ClearCertFile => { Message::ClearCertFile => {
self.cert_file = File::None; self.cert_file = File::None;
self.chain = match self.load_chain() { self.ca_file = File::None;
Ok(chain) => Some(chain), self.key_file = File::None;
_ => None, self.chain = None;
}; self.fixed_chain = None;
self.fixed_chain = match &self.chain { self.chain_indicator_state = IndicatorState::Unknown;
Some(chain) => match Chain::fixed_from(chain.certs().to_vec()) { self.key_indicator_state = IndicatorState::Unknown;
Ok(chain) => Some(chain),
_ => None,
},
_ => None,
};
self.indicator_state = IndicatorState::Unknown;
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::ClearCaFile => { Message::ClearCaFile => {
@ -115,14 +110,12 @@ impl Ui {
}, },
_ => None, _ => None,
}; };
self.chain_indicator_state = self.chain_indicator_state();
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::ClearKeyFile => { Message::ClearKeyFile => {
self.key_file = File::None; self.key_file = File::None;
self.chain = match self.load_chain() { self.key_indicator_state = IndicatorState::Unknown;
Ok(chain) => Some(chain),
_ => None,
};
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::SetCertFile(file) => { Message::SetCertFile(file) => {
@ -148,6 +141,7 @@ impl Ui {
} }
_ => self.cert_file = File::None, _ => self.cert_file = File::None,
}; };
self.chain_indicator_state = self.chain_indicator_state();
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::SetCaFile(file) => { Message::SetCaFile(file) => {
@ -172,6 +166,7 @@ impl Ui {
} }
_ => self.ca_file = File::None, _ => self.ca_file = File::None,
}; };
self.chain_indicator_state = self.chain_indicator_state();
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::SetKeyFile(file) => { Message::SetKeyFile(file) => {
@ -181,10 +176,10 @@ impl Ui {
Ok(key) => File::PrivateKey(file, Box::new(key)), Ok(key) => File::PrivateKey(file, Box::new(key)),
Err(_) => File::Invalid(file), Err(_) => File::Invalid(file),
}; };
self.output = Content::default();
} }
_ => self.key_file = File::None, _ => self.key_file = File::None,
}; };
self.key_indicator_state = self.key_indicator_state();
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::Print => { Message::Print => {
@ -199,11 +194,10 @@ impl Ui {
self.status = err self.status = err
} }
}; };
self.indicator_state = self.indicator_state();
Task::none() Task::none()
} }
Message::Merge => { Message::PrintPem => {
match self.merge_output() { match self.pem_output() {
Ok(output) => { Ok(output) => {
self.output = Content::with_text(output.as_str()); self.output = Content::with_text(output.as_str());
self.status = String::new(); self.status = String::new();
@ -222,24 +216,18 @@ impl Ui {
self.chain = Some(chain); self.chain = Some(chain);
self.mode = UiMode::CertList; self.mode = UiMode::CertList;
} }
self.indicator_state = IndicatorState::Cleaned; self.chain_indicator_state = IndicatorState::Cleaned;
Task::none() Task::none()
} }
Message::PickExportFile => Task::perform(export_file(), Message::ExportToFile), Message::PickExportFile => Task::perform(export_file(), Message::ExportToFile),
Message::ExportToFile(file) => { Message::ExportToFile(file) => {
match file { match file {
Ok(file) => { Ok(file) => match self.pem_output() {
match self.merge_output() { Ok(output) => match fs::write(&file, output) {
Ok(output) => { Ok(_) => self.status = format!("Exported to {}", file.display()),
match fs::write(&file, output) { Err(err) => self.status = format!("{:?}", err),
Ok(_) => self.status = format!("Exported to {}", file.display()), },
Err(err) => self.status = format!("{:?}", err) Err(err) => self.status = err,
}
}
Err(err) => {
self.status = err
}
}
}, },
Err(err) => { Err(err) => {
self.status = format!("{:?}", err); self.status = format!("{:?}", err);
@ -253,13 +241,13 @@ impl Ui {
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
fn grey_style() -> text::Style { fn grey_style() -> text::Style {
text::Style { text::Style {
color: Some(Color::parse("#888888").unwrap()), color: Some(color!(0x888888)),
} }
} }
fn red_style() -> text::Style { fn red_style() -> text::Style {
text::Style { text::Style {
color: Some(Color::parse("#aa0000").unwrap()), color: Some(color!(0xaa0000)),
} }
} }
@ -358,10 +346,12 @@ impl Ui {
.align_y(alignment::Vertical::Center) .align_y(alignment::Vertical::Center)
}; };
let export_button = if !(self.indicator_state == IndicatorState::Success || self.indicator_state == IndicatorState::Cleaned) { let export_button = if !(self.chain_indicator_state == IndicatorState::Success
button("Export").style(button::primary) || self.chain_indicator_state == IndicatorState::Cleaned)
{
button("Export PEM").style(button::primary)
} else { } else {
button("Export") button("Export PEM")
.on_press(Message::PickExportFile) .on_press(Message::PickExportFile)
.style(button::primary) .style(button::primary)
}; };
@ -372,7 +362,9 @@ impl Ui {
.on_press(Message::CopyValue(self.output.text().trim().to_string())) .on_press(Message::CopyValue(self.output.text().trim().to_string()))
.style(button::secondary) .style(button::secondary)
}; };
let cleanup_button = if self.fixed_chain.is_none() { let cleanup_button = if self.fixed_chain.is_none()
|| self.chain_indicator_state == IndicatorState::Success
{
button("Cleanup").style(button::secondary) button("Cleanup").style(button::secondary)
} else { } else {
button("Cleanup") button("Cleanup")
@ -384,8 +376,8 @@ impl Ui {
button("Print information") button("Print information")
.on_press(Message::Print) .on_press(Message::Print)
.style(button::primary), .style(button::primary),
button("Merge into PEM") button("Print PEM")
.on_press(Message::Merge) .on_press(Message::PrintPem)
.style(button::primary), .style(button::primary),
export_button, export_button,
text(" "), text(" "),
@ -396,7 +388,7 @@ impl Ui {
} else { } else {
row![ row![
button("Print information").style(button::primary), button("Print information").style(button::primary),
button("Merge into PEM").style(button::primary), button("Print PEM").style(button::primary),
export_button, export_button,
text(" "), text(" "),
clip_button, clip_button,
@ -567,9 +559,7 @@ impl Ui {
"Private Key matches first Cert Public Key" "Private Key matches first Cert Public Key"
)) ))
.style(|_| container::Style { .style(|_| container::Style {
background: Some(Background::Color( background: Some(Background::Color(color!(0x00aa00))),
Color::parse("#00aa00").unwrap()
)),
text_color: Some(Color::WHITE), text_color: Some(Color::WHITE),
..container::Style::default() ..container::Style::default()
}) })
@ -580,9 +570,7 @@ impl Ui {
"Private Key does not match the first Cert Public Key" "Private Key does not match the first Cert Public Key"
)) ))
.style(|_| container::Style { .style(|_| container::Style {
background: Some(Background::Color( background: Some(Background::Color(color!(0xaa0000))),
Color::parse("#aa0000").unwrap()
)),
text_color: Some(Color::WHITE), text_color: Some(Color::WHITE),
..container::Style::default() ..container::Style::default()
}) })
@ -603,33 +591,63 @@ impl Ui {
}; };
let indicator = { let indicator = {
let content = match self.indicator_state { let chain_content = match self.chain_indicator_state {
IndicatorState::Unknown => ("?", "#aaaaaa", "#ffffff"), IndicatorState::Unknown => ("No Chain", color!(0xaaaaaa, 0.2), color!(0xaaaaaa)),
IndicatorState::Success => ("OK", "#00aa00", "#ffffff"), IndicatorState::Success => ("Chain OK", color!(0x00aa00, 0.2), color!(0x00aa00)),
IndicatorState::Error => ("Not OK", "#aa0000", "#ffffff"), IndicatorState::Error => ("Chain not OK", color!(0xaa0000, 0.2), color!(0xaa0000)),
IndicatorState::Cleaned => ("Cleaned", "#00aa88", "#ffffff"), IndicatorState::Cleaned => {
("Chain cleaned", color!(0x00aa88, 0.2), color!(0x00aa88))
}
};
let key_content = match self.key_indicator_state {
IndicatorState::Success => ("Key OK", color!(0x00aa00, 0.2), color!(0x00aa00)),
IndicatorState::Error => ("Key not OK", color!(0xaa0000, 0.2), color!(0xaa0000)),
_ => ("No Key", color!(0xaaaaaa, 0.2), color!(0xaaaaaa)),
}; };
container( container(
container(text(content.0)) column![
.style(|_| container::Style { container(text(chain_content.0))
background: Some(Background::Color(Color::parse(content.1).unwrap())), .style(move |_| container::Style {
text_color: Some(Color::parse(content.2).unwrap()), background: Some(Background::Color(chain_content.1)),
..container::Style::default() text_color: Some(chain_content.2),
}) border: Border {
.center_x(72) color: chain_content.2,
.center_y(72), width: 1.0,
radius: Radius::from(4)
},
..container::Style::default()
})
.center_x(160)
.center_y(40),
container(text(key_content.0))
.style(move |_| container::Style {
background: Some(Background::Color(key_content.1)),
text_color: Some(key_content.2),
border: Border {
color: key_content.2,
width: 1.0,
radius: Radius::from(4)
},
..container::Style::default()
})
.center_x(160)
.center_y(40),
]
.spacing(4),
) )
.center_x(80) .center_x(160)
.center_y(80) .center_y(80)
}; };
column![ column![
row![ row![
column![cert_file_input, ca_file_input, key_file_input].spacing(2), container(column![cert_file_input, ca_file_input, key_file_input].spacing(2))
indicator, .center_y(96),
indicator.center_y(96),
] ]
.spacing(96), .spacing(40),
horizontal_rule(1), horizontal_rule(1),
buttons, buttons,
horizontal_rule(1), horizontal_rule(1),
@ -716,31 +734,10 @@ Authority-Key-Id: {}
Ok(output.join("\n")) Ok(output.join("\n"))
} }
fn merge_output(&self) -> Result<String, String> { fn pem_output(&self) -> Result<String, String> {
let mut result = String::new(); let mut result = String::new();
if let File::Certificates(_, chain) = &self.cert_file { let chain = self.chain.as_ref();
let mut certs = vec![]; if let Some(chain) = chain {
for cert in chain.certs() {
certs.push(cert.clone());
}
if let File::Certificates(_, ca_chain) = &self.ca_file {
for ca_cert in ca_chain.certs() {
certs.push(ca_cert.clone());
}
}
certs.sort_by(|cert1, cert2| {
if cert1.subject_key_id() == cert2.authority_key_id() {
Ordering::Greater
} else {
Ordering::Less
}
});
let chain = Chain::from(certs.into_iter().unique().collect::<Vec<_>>());
if !chain.is_valid() {
return Err("Cannot merge files to valid chain - giving up!".to_string());
}
for cert in chain.certs() { for cert in chain.certs() {
match cert.to_pem() { match cert.to_pem() {
Ok(plain) => result.push_str(&plain), Ok(plain) => result.push_str(&plain),
@ -770,7 +767,7 @@ Authority-Key-Id: {}
Ok(Chain::from(vec![])) Ok(Chain::from(vec![]))
} }
fn indicator_state(&self) -> IndicatorState { fn chain_indicator_state(&self) -> IndicatorState {
if let Some(chain) = &self.chain { if let Some(chain) = &self.chain {
if chain.is_valid() { if chain.is_valid() {
IndicatorState::Success IndicatorState::Success
@ -781,6 +778,21 @@ Authority-Key-Id: {}
IndicatorState::Unknown IndicatorState::Unknown
} }
} }
fn key_indicator_state(&self) -> IndicatorState {
if let Some(chain) = &self.chain {
if let File::PrivateKey(_, private_key) = &self.key_file {
if let Some(cert) = chain.certs().first() {
return if cert.public_key_matches(private_key) {
IndicatorState::Success
} else {
IndicatorState::Error
};
}
}
}
IndicatorState::Unknown
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -795,11 +807,11 @@ enum Message {
SetCaFile(Result<PathBuf, Error>), SetCaFile(Result<PathBuf, Error>),
SetKeyFile(Result<PathBuf, Error>), SetKeyFile(Result<PathBuf, Error>),
Print, Print,
Merge, PrintPem,
CopyValue(String), CopyValue(String),
Cleanup, Cleanup,
PickExportFile, PickExportFile,
ExportToFile(Result<PathBuf, Error>) ExportToFile(Result<PathBuf, Error>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]