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

feat: show if file is invalid

This commit is contained in:
Paul-Christian Volkmer 2025-01-06 18:29:48 +01:00
parent 7905603aad
commit c02474ac37
3 changed files with 216 additions and 209 deletions

View File

@ -57,7 +57,7 @@ pub enum StringValue {
impl Display for StringValue { impl Display for StringValue {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
StringValue::Valid(val) => write!(f, "{}", val), StringValue::Valid(val) => write!(f, "{val}"),
StringValue::Invalid => write!(f, "*Invalid*"), StringValue::Invalid => write!(f, "*Invalid*"),
StringValue::Empty => write!(f, "*Empty*"), StringValue::Empty => write!(f, "*Empty*"),
} }
@ -271,6 +271,10 @@ impl Chain {
return Err("Certificate chain contains invalid certificate".to_string()); return Err("Certificate chain contains invalid certificate".to_string());
} }
if certs.is_empty() {
return Err("No Certificates found".to_string());
}
Ok(Self { Ok(Self {
certs: certs.into_iter().flatten().collect::<Vec<_>>(), certs: certs.into_iter().flatten().collect::<Vec<_>>(),
}) })

View File

@ -72,7 +72,7 @@ fn main() -> Result<(), ()> {
match PrivateKey::read(Path::new(&key)) { match PrivateKey::read(Path::new(&key)) {
Ok(private_key) => { Ok(private_key) => {
if let Some(cert) = chain.certs().first() { if let Some(cert) = chain.certs().first() {
if cert.public_key_matches(private_key) { if cert.public_key_matches(&private_key) {
println!( println!(
"{}", "{}",
style("✓ Private Key matches first Cert Public Key") style("✓ Private Key matches first Cert Public Key")

View File

@ -9,7 +9,7 @@ use iced::widget::{
use iced::{application, clipboard, Background, Color, Element, Font, Length, Size, Task}; use iced::{application, clipboard, Background, Color, Element, Font, Length, Size, Task};
use itertools::Itertools; use itertools::Itertools;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
fn main() -> iced::Result { fn main() -> iced::Result {
@ -20,15 +20,28 @@ fn main() -> iced::Result {
.run_with(Ui::new) .run_with(Ui::new)
} }
enum File {
None,
Invalid(PathBuf),
Certificates(PathBuf, Box<Chain>),
PrivateKey(PathBuf, Box<PrivateKey>),
}
impl File {
fn is_some(&self) -> bool {
!matches!(self, Self::None)
}
}
enum UiMode { enum UiMode {
CertList, CertList,
Output, Output,
} }
struct Ui { struct Ui {
cert_file: Option<PathBuf>, cert_file: File,
ca_file: Option<PathBuf>, ca_file: File,
key_file: Option<PathBuf>, key_file: File,
mode: UiMode, mode: UiMode,
chain: Option<Chain>, chain: Option<Chain>,
@ -40,9 +53,9 @@ impl Ui {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
( (
Self { Self {
cert_file: None, cert_file: File::None,
ca_file: None, ca_file: File::None,
key_file: None, key_file: File::None,
mode: UiMode::CertList, mode: UiMode::CertList,
chain: None, chain: None,
output: Content::default(), output: Content::default(),
@ -63,7 +76,7 @@ impl Ui {
Message::PickCaFile => Task::perform(pick_file(), Message::SetCaFile), Message::PickCaFile => Task::perform(pick_file(), Message::SetCaFile),
Message::PickKeyFile => Task::perform(pick_file(), Message::SetKeyFile), Message::PickKeyFile => Task::perform(pick_file(), Message::SetKeyFile),
Message::ClearCertFile => { Message::ClearCertFile => {
self.cert_file = None; self.cert_file = File::None;
self.chain = match self.load_chain() { self.chain = match self.load_chain() {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
@ -71,7 +84,7 @@ impl Ui {
self.update(Message::Print) self.update(Message::Print)
} }
Message::ClearCaFile => { Message::ClearCaFile => {
self.ca_file = None; self.ca_file = File::None;
self.chain = match self.load_chain() { self.chain = match self.load_chain() {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
@ -79,7 +92,7 @@ impl Ui {
self.update(Message::Print) self.update(Message::Print)
} }
Message::ClearKeyFile => { Message::ClearKeyFile => {
self.key_file = None; self.key_file = File::None;
self.chain = match self.load_chain() { self.chain = match self.load_chain() {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
@ -89,7 +102,10 @@ impl Ui {
Message::SetCertFile(file) => { Message::SetCertFile(file) => {
match file { match file {
Ok(file) => { Ok(file) => {
self.cert_file = Some(file); self.cert_file = match Chain::read(&file) {
Ok(chain) => File::Certificates(file, Box::new(chain)),
Err(_) => File::Invalid(file),
};
self.chain = match self.load_chain() { self.chain = match self.load_chain() {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
@ -97,31 +113,37 @@ impl Ui {
self.output = Content::default(); self.output = Content::default();
self.mode = UiMode::CertList; self.mode = UiMode::CertList;
} }
_ => self.cert_file = None, _ => self.cert_file = File::None,
}; };
self.update(Message::Print) self.update(Message::Print)
} }
Message::SetCaFile(file) => { Message::SetCaFile(file) => {
match file { match file {
Ok(file) => { Ok(file) => {
self.ca_file = Some(file); self.ca_file = match Chain::read(&file) {
Ok(chain) => File::Certificates(file, Box::new(chain)),
Err(_) => File::Invalid(file),
};
self.chain = match self.load_chain() { self.chain = match self.load_chain() {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
}; };
self.output = Content::default(); self.output = Content::default();
} }
_ => self.ca_file = None, _ => self.ca_file = File::None,
}; };
self.update(Message::Print) self.update(Message::Print)
} }
Message::SetKeyFile(file) => { Message::SetKeyFile(file) => {
match file { match file {
Ok(file) => { Ok(file) => {
self.key_file = Some(file); self.key_file = match PrivateKey::read(&file) {
Ok(key) => File::PrivateKey(file, Box::new(key)),
Err(_) => File::Invalid(file),
};
self.output = Content::default(); self.output = Content::default();
} }
_ => self.key_file = None, _ => self.key_file = File::None,
}; };
self.update(Message::Print) self.update(Message::Print)
} }
@ -158,13 +180,15 @@ impl Ui {
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
fn grey_out_style(is_active: bool) -> text::Style { fn grey_style() -> text::Style {
text::Style { text::Style {
color: if is_active { color: Some(Color::parse("#888888").unwrap()),
Some(Color::BLACK) }
} else { }
Some(Color::parse("#888888").unwrap())
}, fn red_style() -> text::Style {
text::Style {
color: Some(Color::parse("#aa0000").unwrap()),
} }
} }
@ -172,10 +196,15 @@ impl Ui {
row![ row![
text("Certificate: ").width(100), text("Certificate: ").width(100),
text(match self.cert_file { text(match self.cert_file {
Some(ref file) => file.display().to_string(), File::Invalid(ref file) | File::Certificates(ref file, _) =>
file.display().to_string(),
_ => "No certificate file".to_string(), _ => "No certificate file".to_string(),
}) })
.style(|_| grey_out_style(self.cert_file.is_some())), .style(|_| match self.cert_file {
File::Certificates(_, _) => text::Style::default(),
File::Invalid(_) => red_style(),
_ => grey_style(),
}),
horizontal_space(), horizontal_space(),
if self.cert_file.is_some() { if self.cert_file.is_some() {
button("x") button("x")
@ -195,10 +224,15 @@ impl Ui {
row![ row![
text("CA: ").width(100), text("CA: ").width(100),
text(match self.ca_file { text(match self.ca_file {
Some(ref file) => file.display().to_string(), File::Invalid(ref file) | File::Certificates(ref file, _) =>
file.display().to_string(),
_ => "No CA file".to_string(), _ => "No CA file".to_string(),
}) })
.style(|_| grey_out_style(self.ca_file.is_some())), .style(|_| match self.ca_file {
File::Certificates(_, _) => text::Style::default(),
File::Invalid(_) => red_style(),
_ => grey_style(),
}),
horizontal_space(), horizontal_space(),
if self.ca_file.is_some() { if self.ca_file.is_some() {
button("x") button("x")
@ -222,10 +256,15 @@ impl Ui {
row![ row![
text("Key: ").width(100), text("Key: ").width(100),
text(match self.key_file { text(match self.key_file {
Some(ref file) => file.display().to_string(), File::Invalid(ref file) | File::PrivateKey(ref file, _) =>
file.display().to_string(),
_ => "No key file".to_string(), _ => "No key file".to_string(),
}) })
.style(|_| grey_out_style(self.key_file.is_some())), .style(|_| match self.key_file {
File::PrivateKey(_, _) => text::Style::default(),
File::Invalid(_) => red_style(),
_ => grey_style(),
}),
horizontal_space(), horizontal_space(),
if self.key_file.is_some() { if self.key_file.is_some() {
button("x") button("x")
@ -409,13 +448,10 @@ impl Ui {
column![] column![]
}); });
result = result = result.push(if let File::PrivateKey(_, private_key) = &self.key_file {
result.push(if let Some(key) = &self.key_file {
match PrivateKey::read(Path::new(&key)) {
Ok(private_key) => {
if let Some(chain) = &self.chain { if let Some(chain) = &self.chain {
if let Some(first) = chain.certs().first() { if let Some(first) = chain.certs().first() {
if first.public_key_matches(&private_key) { if first.public_key_matches(private_key) {
column![Container::new(text( column![Container::new(text(
"Private Key matches first Cert Public Key" "Private Key matches first Cert Public Key"
)) ))
@ -448,9 +484,6 @@ impl Ui {
} else { } else {
column![] column![]
} }
}
_ => column![],
}
} else { } else {
column![] column![]
}); });
@ -502,17 +535,15 @@ impl Ui {
fn print_output(&self) -> Result<String, String> { fn print_output(&self) -> Result<String, String> {
let mut output = vec![]; let mut output = vec![];
if let Some(cert_file) = &self.cert_file { if let File::Certificates(_, chain) = &self.cert_file {
let chain = Chain::read(cert_file); let mut certs = vec![];
for cert in chain.certs() {
if let Ok(mut chain) = chain { certs.push(cert);
if let Some(ca_file) = &self.ca_file {
if let Ok(ca_chain) = Chain::read(ca_file) {
for ca_cert in ca_chain.into_vec() {
chain.push(ca_cert);
} }
} else {
return Err("Cannot read CA file".to_string()); if let File::Certificates(_, ca_chain) = &self.ca_file {
for ca_cert in ca_chain.certs() {
certs.push(ca_cert);
} }
} }
@ -544,7 +575,10 @@ Authority-Key-Id: {}
} }
if chain.has_missing_tail() { if chain.has_missing_tail() {
output.push("! Last Certificate points to another one that should be contained in chain.".to_string()); output.push(
"! Last Certificate points to another one that should be contained in chain."
.to_string(),
);
output.push(" Self signed (CA-) Certificate? It might be required to import a self signed Root-CA manually for applications to use it.".to_string()); output.push(" Self signed (CA-) Certificate? It might be required to import a self signed Root-CA manually for applications to use it.".to_string());
} }
@ -554,28 +588,17 @@ Authority-Key-Id: {}
output.push("! Chain or some of its parts is not valid (anymore)".to_string()); output.push("! Chain or some of its parts is not valid (anymore)".to_string());
} }
if let Some(key) = &self.key_file { if let File::PrivateKey(_, private_key) = &self.key_file {
match PrivateKey::read(Path::new(&key)) {
Ok(private_key) => {
if let Some(cert) = chain.certs().first() { if let Some(cert) = chain.certs().first() {
if cert.public_key_matches(&private_key) { if cert.public_key_matches(private_key) {
output.push( output.push("✓ Private Key matches first Cert Public Key".to_string());
"✓ Private Key matches first Cert Public Key".to_string(),
);
} else { } else {
output.push( output.push(
"! Private Key does not match the first Cert Public Key" "! Private Key does not match the first Cert Public Key".to_string(),
.to_string(),
); );
} }
} }
} }
_ => return Err("Could not read Private Key".to_string()),
}
}
} else {
return Err("Cannot read Certificate file".to_string());
}
} }
Ok(output.join("\n")) Ok(output.join("\n"))
@ -583,20 +606,18 @@ Authority-Key-Id: {}
fn merge_output(&self) -> Result<String, String> { fn merge_output(&self) -> Result<String, String> {
let mut result = String::new(); let mut result = String::new();
if let Some(cert_file) = &self.cert_file { if let File::Certificates(_, chain) = &self.cert_file {
let chain = Chain::read(cert_file); let mut certs = vec![];
for cert in chain.certs() {
certs.push(cert.clone());
}
if let Ok(mut chain) = chain { if let File::Certificates(_, ca_chain) = &self.ca_file {
if let Some(ca_file) = &self.ca_file { for ca_cert in ca_chain.certs() {
if let Ok(ca_chain) = Chain::read(ca_file) { certs.push(ca_cert.clone());
for ca_cert in ca_chain.into_vec() {
chain.push(ca_cert);
}
} else {
return Err("Cannot read CA file".to_string());
} }
} }
let mut certs = chain.into_vec();
certs.sort_by(|cert1, cert2| { certs.sort_by(|cert1, cert2| {
if cert1.subject_key_id() == cert2.authority_key_id() { if cert1.subject_key_id() == cert2.authority_key_id() {
Ordering::Greater Ordering::Greater
@ -612,37 +633,27 @@ Authority-Key-Id: {}
match cert.to_pem() { match cert.to_pem() {
Ok(plain) => result.push_str(&plain), Ok(plain) => result.push_str(&plain),
Err(_) => { Err(_) => {
return Err( return Err("Cannot merge files to valid chain - Cert error!".to_string());
"Cannot merge files to valid chain - Cert error!".to_string()
);
} }
} }
} }
} else {
return Err("Cannot read Certificate file".to_string());
}
} }
Ok(result) Ok(result)
} }
fn load_chain(&self) -> Result<Chain, String> { fn load_chain(&self) -> Result<Chain, String> {
if let Some(cert_file) = &self.cert_file { if let File::Certificates(_, chain) = &self.cert_file {
let chain = Chain::read(cert_file); let mut certs = vec![];
for cert in chain.certs() {
certs.push(cert.clone());
}
if let Ok(mut chain) = chain { if let File::Certificates(_, ca_chain) = &self.ca_file {
if let Some(ca_file) = &self.ca_file { for ca_cert in ca_chain.certs() {
if let Ok(ca_chain) = Chain::read(ca_file) { certs.push(ca_cert.clone());
for ca_cert in ca_chain.into_vec() {
chain.push(ca_cert);
}
} else {
return Err("Cannot read CA file".to_string());
} }
} }
return Ok(chain); return Ok(Chain::from(certs));
} else {
return Err("Cannot read Certificate file".to_string());
}
} }
Ok(Chain::from(vec![])) Ok(Chain::from(vec![]))
} }
@ -650,46 +661,38 @@ Authority-Key-Id: {}
fn indicator_state(&self) -> IndicatorState { fn indicator_state(&self) -> IndicatorState {
let mut result = IndicatorState::Unknown; let mut result = IndicatorState::Unknown;
if let Some(cert_file) = &self.cert_file { if let File::Certificates(_, chain) = &self.cert_file {
let chain = Chain::read(cert_file);
if let Ok(mut chain) = chain {
result = if chain.is_valid() { result = if chain.is_valid() {
IndicatorState::Success IndicatorState::Success
} else { } else {
IndicatorState::Error IndicatorState::Error
}; };
if let Some(ca_file) = &self.ca_file { if let File::Certificates(_, ca_chain) = &self.ca_file {
if let Ok(ca_chain) = Chain::read(ca_file) { let mut certs = vec![];
for ca_cert in ca_chain.into_vec() { for cert in chain.certs() {
chain.push(ca_cert); certs.push(cert.clone());
} }
for ca_cert in ca_chain.certs() {
certs.push(ca_cert.clone());
}
let chain = Chain::from(certs);
result = if chain.is_valid() { result = if chain.is_valid() {
IndicatorState::Success IndicatorState::Success
} else { } else {
IndicatorState::Error IndicatorState::Error
}; };
} else {
result = IndicatorState::Error;
}
} }
if let Some(key) = &self.key_file { if let File::PrivateKey(_, private_key) = &self.key_file {
match PrivateKey::read(Path::new(&key)) {
Ok(private_key) => {
if let Some(cert) = chain.certs().first() { if let Some(cert) = chain.certs().first() {
return if cert.public_key_matches(&private_key) && chain.is_valid() { return if cert.public_key_matches(private_key) && chain.is_valid() {
result result
} else { } else {
IndicatorState::Error IndicatorState::Error
}; };
} }
} }
_ => return IndicatorState::Error,
}
}
}
} }
result result