1
0
mirror of https://github.com/pcvolkmer/cert-tools.git synced 2025-09-13 03:02:51 +00:00

feat: add import of PKCS #12 files

This commit is contained in:
2025-09-08 12:36:56 +02:00
parent 8ec26ff6bf
commit 3e6a48d7e7
2 changed files with 110 additions and 33 deletions

View File

@@ -65,4 +65,5 @@ interface is available in (sub-)package `ui`.
![](ui-image.jpeg) ![](ui-image.jpeg)
The GUI also provides export to a password protected PKCS #12 file, including certificates and private keys. The GUI also provides import from and export to a password protected PKCS #12 file,
including certificates and private keys.

View File

@@ -19,17 +19,14 @@
#![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
use cert_tools::{save_p12_file, Chain, PrivateKey}; use cert_tools::{read_p12_file, save_p12_file, Chain, PrivateKey};
use iced::border::Radius; 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::{
self, button, column, container, horizontal_rule, horizontal_space, row, text, text_editor, self, button, column, container, horizontal_rule, horizontal_space, row, text, text_editor,
text_input, Container, Scrollable, text_input, Container, Scrollable,
}; };
use iced::{ use iced::{alignment, application, clipboard, color, window, Background, Border, Color, Element, Font, Length, Padding, Pixels, Settings, Size, Task};
alignment, application, clipboard, color, window, Background, Border, Color, Element, Font,
Length, Pixels, Settings, Size, Task,
};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
@@ -66,7 +63,8 @@ impl File {
enum UiMode { enum UiMode {
CertList, CertList,
Output, Output,
Passphrase, ImportPassphrase,
ExportPassphrase,
} }
struct Ui { struct Ui {
@@ -119,7 +117,7 @@ impl Ui {
} }
} }
self.mode = UiMode::CertList; //self.mode = UiMode::CertList;
match message { match message {
Message::PickCertFile => Task::perform(pick_file(), Message::SetCertFile), Message::PickCertFile => Task::perform(pick_file(), Message::SetCertFile),
Message::PickCaFile => Task::perform(pick_file(), Message::SetCaFile), Message::PickCaFile => Task::perform(pick_file(), Message::SetCaFile),
@@ -149,6 +147,10 @@ impl Ui {
Message::SetCertFile(file) => { Message::SetCertFile(file) => {
match file { match file {
Ok(file) => { Ok(file) => {
if file.to_str().unwrap_or_default().to_lowercase().ends_with(".p12") {
self.cert_file = File::Certificates(file, Box::new(Chain::from(vec![])));
return Task::done(Message::AskForImportPassword)
}
self.cert_file = match Chain::read(&file) { self.cert_file = match Chain::read(&file) {
Ok(chain) => File::Certificates(file, Box::new(chain)), Ok(chain) => File::Certificates(file, Box::new(chain)),
Err(_) => File::Invalid(file), Err(_) => File::Invalid(file),
@@ -192,12 +194,40 @@ impl Ui {
self.key_indicator_state = self.key_indicator_state(); self.key_indicator_state = self.key_indicator_state();
Task::done(Message::Print) Task::done(Message::Print)
} }
Message::SetPkcs12File(file) => {
match file {
Ok(file) => {
let (cert_file, key_file) = match read_p12_file(&file, &self.password_1) {
Ok((chain, key)) => (
File::Certificates(file.clone(), Box::new(chain)),
File::PrivateKey(file, Box::new(key))
),
Err(_) => (
File::Invalid(file.clone()),
File::Invalid(file)
)
};
self.cert_file = cert_file;
self.key_file = key_file;
self.chain = self.load_chain().ok();
self.fixed_chain = fixed_chain(&self.chain);
self.output = Content::default();
self.mode = UiMode::CertList;
self.password_1 = String::new();
self.password_2 = String::new();
}
_ => self.cert_file = File::None
}
self.chain_indicator_state = self.chain_indicator_state();
self.key_indicator_state = self.key_indicator_state();
Task::done(Message::Print)
}
Message::Print => { Message::Print => {
self.mode = UiMode::CertList;
match self.print_output() { match self.print_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();
self.mode = UiMode::CertList;
} }
Err(err) => { Err(err) => {
self.output = Content::default(); self.output = Content::default();
@@ -207,6 +237,7 @@ impl Ui {
Task::none() Task::none()
} }
Message::PrintPem => { Message::PrintPem => {
self.mode = UiMode::CertList;
match self.pem_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());
@@ -222,6 +253,7 @@ impl Ui {
} }
Message::CopyValue(value) => clipboard::write::<Message>(value), Message::CopyValue(value) => clipboard::write::<Message>(value),
Message::Cleanup => { Message::Cleanup => {
self.mode = UiMode::CertList;
if let Some(chain) = self.fixed_chain.take() { if let Some(chain) = self.fixed_chain.take() {
self.chain = Some(chain); self.chain = Some(chain);
self.mode = UiMode::CertList; self.mode = UiMode::CertList;
@@ -245,14 +277,19 @@ impl Ui {
} }
Task::none() Task::none()
} }
Message::AskForPassword => { Message::AskForImportPassword => {
self.mode = UiMode::Passphrase; self.mode = UiMode::ImportPassphrase;
Task::none()
}
Message::AskForExportPassword => {
self.mode = UiMode::ExportPassphrase;
Task::none() Task::none()
} }
Message::PickExportP12File => { Message::PickExportP12File => {
Task::perform(export_p12_file(), Message::ExportToP12File) Task::perform(export_p12_file(), Message::ExportToP12File)
} }
Message::ExportToP12File(file) => { Message::ExportToP12File(file) => {
self.mode = UiMode::CertList;
let private_key = match &self.key_file { let private_key = match &self.key_file {
File::PrivateKey(_, key) => Some(key.as_ref().clone()), File::PrivateKey(_, key) => Some(key.as_ref().clone()),
_ => None, _ => None,
@@ -279,12 +316,12 @@ impl Ui {
Task::none() Task::none()
} }
Message::SetPw1(pw) => { Message::SetPw1(pw) => {
self.mode = UiMode::Passphrase; //self.mode = UiMode::ExportPassphrase;
self.password_1 = pw.clone(); self.password_1 = pw.clone();
Task::none() Task::none()
} }
Message::SetPw2(pw) => { Message::SetPw2(pw) => {
self.mode = UiMode::Passphrase; //self.mode = UiMode::ExportPassphrase;
self.password_2 = pw.clone(); self.password_2 = pw.clone();
Task::none() Task::none()
} }
@@ -405,7 +442,7 @@ impl Ui {
|| self.chain_indicator_state == IndicatorState::Cleaned) && self.key_indicator_state == IndicatorState::Success || self.chain_indicator_state == IndicatorState::Cleaned) && self.key_indicator_state == IndicatorState::Success
{ {
button("Export PKCS #12") button("Export PKCS #12")
.on_press(Message::AskForPassword) .on_press(Message::AskForExportPassword)
.style(button::primary) .style(button::primary)
} else { } else {
button("Export PKCS #12").style(button::primary) button("Export PKCS #12").style(button::primary)
@@ -747,28 +784,64 @@ impl Ui {
.center_y(80) .center_y(80)
}; };
let ask_for_password = { let ask_for_import_password = {
let file = match &self.cert_file {
File::Certificates(file, _) => Ok(file.clone()),
_ => Err(Error::Undefined),
};
row![
column![].width(Length::Fill),
container(
column![
text("Bitte Passwort für den Import eingeben"),
text_input("", &self.password_1)
.secure(true)
.on_input(Message::SetPw1)
.on_submit(Message::SetPkcs12File(file.clone())),
row![
button("OK").on_press(Message::SetPkcs12File(file)),
button("Cancel").on_press(Message::Abort)
].spacing(4),
]
.spacing(4)
.height(Length::Fill)
.width(320),
)
.center_x(320),
column![].width(Length::Fill),
]
.padding(Padding::from(64))
.height(Length::Fill)
.width(Length::Fill)
};
let ask_for_export_password = {
let ok_button = if !self.password_1.is_empty() && self.password_1 == self.password_2 { let ok_button = if !self.password_1.is_empty() && self.password_1 == self.password_2 {
button("OK").on_press(Message::PickExportP12File) button("OK").on_press(Message::PickExportP12File)
} else { } else {
button("OK") button("OK")
}; };
row![container( row![
column![ column![].width(Length::Fill),
text("Bitte Passwort für den Export eingeben"), container(
text_input("", &self.password_1) column![
.secure(true) text("Bitte Passwort für den Export eingeben"),
.on_input(Message::SetPw1), text_input("", &self.password_1)
text_input("", &self.password_2) .secure(true)
.secure(true) .on_input(Message::SetPw1),
.on_input(Message::SetPw2), text_input("", &self.password_2)
row![ok_button, button("Cancel").on_press(Message::Abort)].spacing(4), .secure(true)
] .on_input(Message::SetPw2),
.spacing(4) row![ok_button, button("Cancel").on_press(Message::Abort)].spacing(4),
.height(Length::Fill) ]
.width(320), .spacing(4)
) .height(Length::Fill)
.center_x(320)] .width(320),
)
.center_x(320),
column![].width(Length::Fill),
]
.padding(Padding::from(64))
.height(Length::Fill) .height(Length::Fill)
.width(Length::Fill) .width(Length::Fill)
}; };
@@ -786,7 +859,8 @@ impl Ui {
match self.mode { match self.mode {
UiMode::CertList => column![certs, chain_info], UiMode::CertList => column![certs, chain_info],
UiMode::Output => column![output], UiMode::Output => column![output],
UiMode::Passphrase => column![ask_for_password], UiMode::ImportPassphrase => column![ask_for_import_password],
UiMode::ExportPassphrase => column![ask_for_export_password],
}, },
horizontal_rule(1), horizontal_rule(1),
row![ row![
@@ -997,6 +1071,7 @@ enum Message {
SetCertFile(Result<PathBuf, Error>), SetCertFile(Result<PathBuf, Error>),
SetCaFile(Result<PathBuf, Error>), SetCaFile(Result<PathBuf, Error>),
SetKeyFile(Result<PathBuf, Error>), SetKeyFile(Result<PathBuf, Error>),
SetPkcs12File(Result<PathBuf, Error>),
Print, Print,
PrintPem, PrintPem,
CopyValue(String), CopyValue(String),
@@ -1005,7 +1080,8 @@ enum Message {
ExportToFile(Result<PathBuf, Error>), ExportToFile(Result<PathBuf, Error>),
PickExportP12File, PickExportP12File,
ExportToP12File(Result<PathBuf, Error>), ExportToP12File(Result<PathBuf, Error>),
AskForPassword, AskForImportPassword,
AskForExportPassword,
SetPw1(String), SetPw1(String),
SetPw2(String), SetPw2(String),
Abort, Abort,