mirror of
https://github.com/pcvolkmer/cert-tools.git
synced 2025-09-13 11:12:52 +00:00
Merge pull request #8 from pcvolkmer/pkcs12_import
feat: pkcs #12 file import
This commit is contained in:
@@ -63,4 +63,7 @@ cert-tools merge cert.pem ca.pem > chain.pem
|
|||||||
In addition to the console-based application, a simple [iced](https://github.com/iced-rs/iced)-based graphical user
|
In addition to the console-based application, a simple [iced](https://github.com/iced-rs/iced)-based graphical user
|
||||||
interface is available in (sub-)package `ui`.
|
interface is available in (sub-)package `ui`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
The GUI also provides import from and export to a password protected PKCS #12 file,
|
||||||
|
including certificates and private keys.
|
160
ui/src/main.rs
160
ui/src/main.rs
@@ -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 {
|
||||||
@@ -114,15 +112,12 @@ impl Ui {
|
|||||||
fn update(&mut self, message: Message) -> Task<Message> {
|
fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
fn fixed_chain(chain: &Option<Chain>) -> Option<Chain> {
|
fn fixed_chain(chain: &Option<Chain>) -> Option<Chain> {
|
||||||
match chain {
|
match chain {
|
||||||
Some(chain) => match Chain::create_fixed(chain.certs()) {
|
Some(chain) => Chain::create_fixed(chain.certs()).ok(),
|
||||||
Ok(chain) => Some(chain),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
@@ -139,10 +134,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
Message::ClearCaFile => {
|
Message::ClearCaFile => {
|
||||||
self.ca_file = File::None;
|
self.ca_file = File::None;
|
||||||
self.chain = match self.load_chain() {
|
self.chain = self.load_chain().ok();
|
||||||
Ok(chain) => Some(chain),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
self.fixed_chain = fixed_chain(&self.chain);
|
self.fixed_chain = fixed_chain(&self.chain);
|
||||||
self.chain_indicator_state = self.chain_indicator_state();
|
self.chain_indicator_state = self.chain_indicator_state();
|
||||||
Task::done(Message::Print)
|
Task::done(Message::Print)
|
||||||
@@ -155,14 +147,15 @@ 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),
|
||||||
};
|
};
|
||||||
self.chain = match self.load_chain() {
|
self.chain = self.load_chain().ok();
|
||||||
Ok(chain) => Some(chain),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
self.fixed_chain = fixed_chain(&self.chain);
|
self.fixed_chain = fixed_chain(&self.chain);
|
||||||
self.output = Content::default();
|
self.output = Content::default();
|
||||||
self.mode = UiMode::CertList;
|
self.mode = UiMode::CertList;
|
||||||
@@ -179,10 +172,7 @@ impl Ui {
|
|||||||
Ok(chain) => File::Certificates(file, Box::new(chain)),
|
Ok(chain) => File::Certificates(file, Box::new(chain)),
|
||||||
Err(_) => File::Invalid(file),
|
Err(_) => File::Invalid(file),
|
||||||
};
|
};
|
||||||
self.chain = match self.load_chain() {
|
self.chain = self.load_chain().ok();
|
||||||
Ok(chain) => Some(chain),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
self.fixed_chain = fixed_chain(&self.chain);
|
self.fixed_chain = fixed_chain(&self.chain);
|
||||||
self.output = Content::default();
|
self.output = Content::default();
|
||||||
}
|
}
|
||||||
@@ -204,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();
|
||||||
@@ -219,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());
|
||||||
@@ -234,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;
|
||||||
@@ -257,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,
|
||||||
@@ -291,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()
|
||||||
}
|
}
|
||||||
@@ -417,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)
|
||||||
@@ -759,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(|t| Message::SetPw1(t)),
|
text_input("", &self.password_1)
|
||||||
text_input("", &self.password_2)
|
.secure(true)
|
||||||
.secure(true)
|
.on_input(Message::SetPw1),
|
||||||
.on_input(|t| Message::SetPw2(t)),
|
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)
|
||||||
};
|
};
|
||||||
@@ -798,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![
|
||||||
@@ -1009,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),
|
||||||
@@ -1017,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,
|
||||||
|
Reference in New Issue
Block a user