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

feat: cleanup loaded chain

This commit is contained in:
Paul-Christian Volkmer 2025-01-20 01:59:32 +01:00
parent a380a2ac96
commit f5a60d82d7
2 changed files with 123 additions and 34 deletions

View File

@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::cmp::Ordering;
use openssl::asn1::Asn1Time; use openssl::asn1::Asn1Time;
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
use openssl::nid::Nid; use openssl::nid::Nid;
@ -27,6 +27,7 @@ use std::fs;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::Path; use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use itertools::Itertools;
pub fn hex_encode<T: AsRef<[u8]>>(s: T) -> String { pub fn hex_encode<T: AsRef<[u8]>>(s: T) -> String {
s.as_ref() s.as_ref()
@ -284,6 +285,22 @@ impl Chain {
Self { certs } Self { certs }
} }
pub fn fixed_from(certs: Vec<Certificate>) -> Result<Chain, String> {
let mut certs = certs.iter().collect::<Vec<_>>();
certs.sort_by(|cert1, cert2| {
if cert1.subject_key_id() == cert2.authority_key_id() {
Ordering::Greater
} else {
Ordering::Less
}
});
let chain = Chain::from(certs.iter().unique().map(|&c| c.clone()).collect::<Vec<_>>());
if !chain.is_valid() {
return Err("Cannot merge files to valid chain - giving up!".to_string());
}
Ok(chain)
}
pub fn certs(&self) -> &Vec<Certificate> { pub fn certs(&self) -> &Vec<Certificate> {
&self.certs &self.certs
} }

View File

@ -12,6 +12,7 @@ use iced::{
}; };
use itertools::Itertools; use itertools::Itertools;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
@ -51,8 +52,10 @@ struct Ui {
mode: UiMode, mode: UiMode,
chain: Option<Chain>, chain: Option<Chain>,
fixed_chain: Option<Chain>,
output: Content, output: Content,
status: String, status: String,
indicator_state: IndicatorState,
} }
impl Ui { impl Ui {
@ -64,8 +67,10 @@ impl Ui {
key_file: File::None, key_file: File::None,
mode: UiMode::CertList, mode: UiMode::CertList,
chain: None, chain: None,
fixed_chain: None,
output: Content::default(), output: Content::default(),
status: String::new(), status: String::new(),
indicator_state: IndicatorState::Unknown,
}, },
Task::none(), Task::none(),
) )
@ -87,6 +92,14 @@ impl Ui {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
}; };
self.fixed_chain = match &self.chain {
Some(chain) => match Chain::fixed_from(chain.certs().to_vec()) {
Ok(chain) => Some(chain),
_ => None,
},
_ => None,
};
self.indicator_state = IndicatorState::Unknown;
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::ClearCaFile => { Message::ClearCaFile => {
@ -95,6 +108,13 @@ impl Ui {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
}; };
self.fixed_chain = match &self.chain {
Some(chain) => match Chain::fixed_from(chain.certs().to_vec()) {
Ok(chain) => Some(chain),
_ => None,
},
_ => None,
};
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::ClearKeyFile => { Message::ClearKeyFile => {
@ -116,6 +136,13 @@ impl Ui {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
}; };
self.fixed_chain = match &self.chain {
Some(chain) => match Chain::fixed_from(chain.certs().to_vec()) {
Ok(chain) => Some(chain),
_ => None,
},
_ => None,
};
self.output = Content::default(); self.output = Content::default();
self.mode = UiMode::CertList; self.mode = UiMode::CertList;
} }
@ -134,6 +161,13 @@ impl Ui {
Ok(chain) => Some(chain), Ok(chain) => Some(chain),
_ => None, _ => None,
}; };
self.fixed_chain = match &self.chain {
Some(chain) => match Chain::fixed_from(chain.certs().to_vec()) {
Ok(chain) => Some(chain),
_ => None,
},
_ => None,
};
self.output = Content::default(); self.output = Content::default();
} }
_ => self.ca_file = File::None, _ => self.ca_file = File::None,
@ -165,6 +199,7 @@ impl Ui {
self.status = err self.status = err
} }
}; };
self.indicator_state = self.indicator_state();
Task::none() Task::none()
} }
Message::Merge => { Message::Merge => {
@ -182,6 +217,36 @@ impl Ui {
Task::none() Task::none()
} }
Message::CopyValue(value) => clipboard::write::<Message>(value), Message::CopyValue(value) => clipboard::write::<Message>(value),
Message::Cleanup => {
if let Some(chain) = self.fixed_chain.take() {
self.chain = Some(chain);
self.mode = UiMode::CertList;
}
self.indicator_state = IndicatorState::Cleaned;
Task::none()
}
Message::PickExportFile => Task::perform(export_file(), Message::ExportToFile),
Message::ExportToFile(file) => {
match file {
Ok(file) => {
match self.merge_output() {
Ok(output) => {
match fs::write(&file, output) {
Ok(_) => self.status = format!("Exported to {}", file.display()),
Err(err) => self.status = format!("{:?}", err)
}
}
Err(err) => {
self.status = err
}
}
},
Err(err) => {
self.status = format!("{:?}", err);
}
}
Task::none()
}
} }
} }
@ -293,6 +358,13 @@ 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) {
button("Export").style(button::primary)
} else {
button("Export")
.on_press(Message::PickExportFile)
.style(button::primary)
};
let clip_button = if self.output.text().trim().is_empty() { let clip_button = if self.output.text().trim().is_empty() {
button("Copy to Clipboard").style(button::secondary) button("Copy to Clipboard").style(button::secondary)
} else { } else {
@ -300,6 +372,13 @@ 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() {
button("Cleanup").style(button::secondary)
} else {
button("Cleanup")
.on_press(Message::Cleanup)
.style(button::secondary)
};
let buttons = if self.cert_file.is_some() { let buttons = if self.cert_file.is_some() {
row![ row![
button("Print information") button("Print information")
@ -308,16 +387,20 @@ impl Ui {
button("Merge into PEM") button("Merge into PEM")
.on_press(Message::Merge) .on_press(Message::Merge)
.style(button::primary), .style(button::primary),
export_button,
text(" "), text(" "),
clip_button, clip_button,
cleanup_button,
horizontal_space(), horizontal_space(),
] ]
} else { } else {
row![ row![
button("Print information").style(button::primary), button("Print information").style(button::primary),
button("Merge into PEM").style(button::primary), button("Merge into PEM").style(button::primary),
export_button,
text(" "), text(" "),
clip_button, clip_button,
cleanup_button,
horizontal_space(), horizontal_space(),
] ]
} }
@ -520,10 +603,11 @@ impl Ui {
}; };
let indicator = { let indicator = {
let content = match self.indicator_state() { let content = match self.indicator_state {
IndicatorState::Unknown => ("?", "#aaaaaa", "#ffffff"), IndicatorState::Unknown => ("?", "#aaaaaa", "#ffffff"),
IndicatorState::Success => ("OK", "#00aa00", "#ffffff"), IndicatorState::Success => ("OK", "#00aa00", "#ffffff"),
IndicatorState::Error => ("Not OK", "#aa0000", "#ffffff"), IndicatorState::Error => ("Not OK", "#aa0000", "#ffffff"),
IndicatorState::Cleaned => ("Cleaned", "#00aa88", "#ffffff"),
}; };
container( container(
@ -687,43 +771,15 @@ Authority-Key-Id: {}
} }
fn indicator_state(&self) -> IndicatorState { fn indicator_state(&self) -> IndicatorState {
let mut result = IndicatorState::Unknown; if let Some(chain) = &self.chain {
if chain.is_valid() {
if let File::Certificates(_, chain) = &self.cert_file {
result = if chain.is_valid() {
IndicatorState::Success IndicatorState::Success
} else { } else {
IndicatorState::Error IndicatorState::Error
};
if let File::Certificates(_, ca_chain) = &self.ca_file {
let mut certs = vec![];
for cert in chain.certs() {
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() {
IndicatorState::Success
} else { } else {
IndicatorState::Error IndicatorState::Unknown
};
} }
if let File::PrivateKey(_, private_key) = &self.key_file {
if let Some(cert) = chain.certs().first() {
return if cert.public_key_matches(private_key) && chain.is_valid() {
result
} else {
IndicatorState::Error
};
}
}
}
result
} }
} }
@ -741,6 +797,9 @@ enum Message {
Print, Print,
Merge, Merge,
CopyValue(String), CopyValue(String),
Cleanup,
PickExportFile,
ExportToFile(Result<PathBuf, Error>)
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -748,10 +807,12 @@ enum Error {
Undefined, Undefined,
} }
#[derive(PartialEq)]
enum IndicatorState { enum IndicatorState {
Unknown, Unknown,
Success, Success,
Error, Error,
Cleaned,
} }
async fn pick_file() -> Result<PathBuf, Error> { async fn pick_file() -> Result<PathBuf, Error> {
@ -763,3 +824,14 @@ async fn pick_file() -> Result<PathBuf, Error> {
Ok(path.into()) Ok(path.into())
} }
async fn export_file() -> Result<PathBuf, Error> {
let path = rfd::AsyncFileDialog::new()
.set_title("Export file...")
.add_filter("PEM-File", &["pem", "crt"])
.save_file()
.await
.ok_or(Error::Undefined)?;
Ok(path.into())
}