mirror of
https://github.com/pcvolkmer/cert-tools.git
synced 2025-04-19 17:06:49 +00:00
Merge pull request #1 from pcvolkmer/feat_cleanup
feat: cleanup loaded chain
This commit is contained in:
commit
845c7331a3
19
src/lib.rs
19
src/lib.rs
@ -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
|
||||||
}
|
}
|
||||||
|
138
ui/src/main.rs
138
ui/src/main.rs
@ -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 {
|
|
||||||
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,
|
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())
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user