use clap::ArgMatches;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use super::Error;
const CERT_COMMON_NAME: &str = "UNSAFE Proxide Root Certificate";
pub fn run(matches: &ArgMatches) -> Result<(), Error>
{
match matches.subcommand() {
Some(("ca", matches)) => run_ca(matches),
Some((cmd, _)) => unreachable!("Unknown command: {}", cmd),
_ => unreachable!("No subcommand specified"),
}
}
pub fn run_ca(matches: &ArgMatches) -> Result<(), Error>
{
if matches.is_present("revoke") || matches.is_present("trust") {
os::revoke_ca(matches)?;
}
if !(matches.is_present("create") || matches.is_present("trust")) {
return Ok(());
}
let cert_file = matches
.value_of("ca-certificate")
.unwrap_or("proxide_ca.crt");
let key_file = matches.value_of("ca-key").unwrap_or("proxide_ca.key");
if matches.is_present("create") {
create_ca(matches, cert_file, key_file)?;
}
if matches.is_present("trust") {
trust_ca(matches, cert_file, key_file)?;
}
Ok(())
}
fn trust_ca(matches: &ArgMatches, cert_file: &str, key_file: &str) -> Result<(), Error>
{
for file in &[cert_file, key_file] {
if !Path::new(file).is_file() {
return Err(Error::ArgumentError {
msg: format!(
"Could not open '{}', use --create if you need to create a new CA certificate",
file
),
});
}
}
os::trust_ca(cert_file, matches)?;
Ok(())
}
fn create_ca(matches: &ArgMatches, cert_file: &str, key_file: &str) -> Result<(), Error>
{
if !matches.is_present("force") {
for file in &[cert_file, key_file] {
if Path::new(file).is_file() {
return Err(Error::ArgumentError {
msg: format!(
"File '{}' already exists. Use --force to overwrite it.",
file
),
});
}
}
}
let mut ca_params = rcgen::CertificateParams::new(vec![]);
ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let mut key_usage = rcgen::CustomExtension::from_oid_content(
&[2, 5, 29, 15],
vec![
0x03, 0x02, 0x01, 0x86, ],
);
key_usage.set_criticality(true);
ca_params.custom_extensions = vec![key_usage];
ca_params.distinguished_name = rcgen::DistinguishedName::new();
ca_params
.distinguished_name
.push(rcgen::DnType::OrganizationName, "UNSAFE");
ca_params
.distinguished_name
.push(rcgen::DnType::CommonName, CERT_COMMON_NAME); let ca_cert = rcgen::Certificate::from_params(ca_params).unwrap();
File::create(cert_file)
.map_err(|_| Error::ArgumentError {
msg: format!(
"Could not open the certificate file '{}' for writing",
cert_file
),
})?
.write_all(
ca_cert
.serialize_pem()
.map_err(|e| Error::RuntimeError { msg: e.to_string() })?
.as_bytes(),
)
.map_err(|_| Error::ArgumentError {
msg: format!("Could not write certificate to '{}'", cert_file),
})?;
File::create(key_file)
.map_err(|_| Error::ArgumentError {
msg: format!(
"Could not open the private key file '{}' for writing",
key_file
),
})?
.write_all(ca_cert.serialize_private_key_pem().as_bytes())
.map_err(|_| Error::ArgumentError {
msg: format!("Could not write private key to '{}'", key_file),
})?;
Ok(())
}
#[cfg(not(windows))]
mod os
{
use super::*;
pub fn revoke_ca(_matches: &ArgMatches) -> Result<(), Error>
{
Err(Error::RuntimeError {
msg: "--revoke is not supported on this platform".to_string(),
})
}
pub fn trust_ca(_cert_file: &str, _matches: &ArgMatches) -> Result<(), Error>
{
Err(Error::RuntimeError {
msg: "--trust is not supported on this platform".to_string(),
})
}
}
#[cfg(windows)]
mod os
{
use super::*;
pub fn revoke_ca(matches: &ArgMatches) -> Result<(), Error>
{
let store = matches
.value_of("revoke")
.or_else(|| matches.value_of("trust"))
.unwrap_or("user");
match store {
"user" | "system" | "all" => (),
v => {
return Err(Error::ArgumentError {
msg: format!("Invalid certificate store '{}'", v),
})
}
}
if store == "all" || store == "system" {
println!("Removing the previous Proxide CA certificates from the System store");
std::process::Command::new("certutil")
.arg("-delstore")
.arg("Root")
.arg(CERT_COMMON_NAME)
.spawn()
.and_then(|mut process| process.wait())
.map(|_| ())
.map_err(|e| Error::RuntimeError {
msg: format!("Failed to revoke the certificates with certutil: {}", e),
})?;
}
if store == "all" || store == "user" {
println!("Removing the previous Proxide CA certificates from the User store");
std::process::Command::new("certutil")
.arg("-delstore")
.arg("-user")
.arg("Root")
.arg(CERT_COMMON_NAME)
.spawn()
.and_then(|mut process| process.wait())
.map(|_| ())
.map_err(|e| Error::RuntimeError {
msg: format!("Failed to revoke the certificates with certutil: {}", e),
})?;
}
Ok(())
}
pub fn trust_ca(cert_file: &str, matches: &ArgMatches) -> Result<(), Error>
{
let trust = matches.value_of("trust").unwrap_or("user");
match trust {
"user" | "system" | "all" => (),
v => {
return Err(Error::ArgumentError {
msg: format!("Invalid --trust value '{}'", v),
})
}
}
if trust == "all" || trust == "system" {
println!("Add system");
std::process::Command::new("certutil")
.arg("-addstore")
.arg("-v")
.arg("Root")
.arg(cert_file)
.spawn()
.and_then(|mut process| process.wait())
.map_err(|e| Error::RuntimeError {
msg: format!("Failed to import the certificate with certutil: {}", e),
})?;
}
if trust == "all" || trust == "user" {
println!("Add user");
std::process::Command::new("certutil")
.arg("-addstore")
.arg("-user")
.arg("Root")
.arg(cert_file)
.spawn()
.and_then(|mut process| process.wait())
.map_err(|e| Error::RuntimeError {
msg: format!("Failed to import the certificate with certutil: {}", e),
})?;
}
Ok(())
}
}