diff --git a/src/lib.rs b/src/lib.rs
index a4a68fb..fb5cf20 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+use std::cmp::Ordering;
use openssl::asn1::Asn1Time;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
@@ -27,6 +27,7 @@ use std::fs;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
+use itertools::Itertools;
pub fn hex_encode>(s: T) -> String {
s.as_ref()
@@ -284,6 +285,22 @@ impl Chain {
Self { certs }
}
+ pub fn fixed_from(certs: Vec) -> Result {
+ let mut certs = certs.iter().collect::>();
+ 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::>());
+ if !chain.is_valid() {
+ return Err("Cannot merge files to valid chain - giving up!".to_string());
+ }
+ Ok(chain)
+ }
+
pub fn certs(&self) -> &Vec {
&self.certs
}
diff --git a/ui/src/main.rs b/ui/src/main.rs
index a5f6636..1514667 100644
--- a/ui/src/main.rs
+++ b/ui/src/main.rs
@@ -12,6 +12,7 @@ use iced::{
};
use itertools::Itertools;
use std::cmp::Ordering;
+use std::fs;
use std::path::PathBuf;
use std::time::SystemTime;
@@ -51,8 +52,10 @@ struct Ui {
mode: UiMode,
chain: Option,
+ fixed_chain: Option,
output: Content,
status: String,
+ indicator_state: IndicatorState,
}
impl Ui {
@@ -64,8 +67,10 @@ impl Ui {
key_file: File::None,
mode: UiMode::CertList,
chain: None,
+ fixed_chain: None,
output: Content::default(),
status: String::new(),
+ indicator_state: IndicatorState::Unknown,
},
Task::none(),
)
@@ -87,6 +92,14 @@ impl Ui {
Ok(chain) => Some(chain),
_ => 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)
}
Message::ClearCaFile => {
@@ -95,6 +108,13 @@ impl Ui {
Ok(chain) => Some(chain),
_ => 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)
}
Message::ClearKeyFile => {
@@ -116,6 +136,13 @@ impl Ui {
Ok(chain) => Some(chain),
_ => 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.mode = UiMode::CertList;
}
@@ -134,6 +161,13 @@ impl Ui {
Ok(chain) => Some(chain),
_ => 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.ca_file = File::None,
@@ -165,6 +199,7 @@ impl Ui {
self.status = err
}
};
+ self.indicator_state = self.indicator_state();
Task::none()
}
Message::Merge => {
@@ -182,6 +217,36 @@ impl Ui {
Task::none()
}
Message::CopyValue(value) => clipboard::write::(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)
};
+ 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() {
button("Copy to Clipboard").style(button::secondary)
} else {
@@ -300,6 +372,13 @@ impl Ui {
.on_press(Message::CopyValue(self.output.text().trim().to_string()))
.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() {
row![
button("Print information")
@@ -308,16 +387,20 @@ impl Ui {
button("Merge into PEM")
.on_press(Message::Merge)
.style(button::primary),
+ export_button,
text(" "),
clip_button,
+ cleanup_button,
horizontal_space(),
]
} else {
row![
button("Print information").style(button::primary),
button("Merge into PEM").style(button::primary),
+ export_button,
text(" "),
clip_button,
+ cleanup_button,
horizontal_space(),
]
}
@@ -520,10 +603,11 @@ impl Ui {
};
let indicator = {
- let content = match self.indicator_state() {
+ let content = match self.indicator_state {
IndicatorState::Unknown => ("?", "#aaaaaa", "#ffffff"),
IndicatorState::Success => ("OK", "#00aa00", "#ffffff"),
IndicatorState::Error => ("Not OK", "#aa0000", "#ffffff"),
+ IndicatorState::Cleaned => ("Cleaned", "#00aa88", "#ffffff"),
};
container(
@@ -687,43 +771,15 @@ Authority-Key-Id: {}
}
fn indicator_state(&self) -> IndicatorState {
- let mut result = IndicatorState::Unknown;
-
- if let File::Certificates(_, chain) = &self.cert_file {
- result = if chain.is_valid() {
+ if let Some(chain) = &self.chain {
+ if chain.is_valid() {
IndicatorState::Success
} else {
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 {
- IndicatorState::Error
- };
- }
-
- 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
- };
- }
}
+ } else {
+ IndicatorState::Unknown
}
-
- result
}
}
@@ -741,6 +797,9 @@ enum Message {
Print,
Merge,
CopyValue(String),
+ Cleanup,
+ PickExportFile,
+ ExportToFile(Result)
}
#[derive(Debug, Clone)]
@@ -748,10 +807,12 @@ enum Error {
Undefined,
}
+#[derive(PartialEq)]
enum IndicatorState {
Unknown,
Success,
Error,
+ Cleaned,
}
async fn pick_file() -> Result {
@@ -763,3 +824,14 @@ async fn pick_file() -> Result {
Ok(path.into())
}
+
+async fn export_file() -> Result {
+ 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())
+}