Created
August 29, 2024 14:26
-
-
Save dishmaker/9209804ba0950a3a4da537222ff1e006 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [package] | |
// name = "cert_installer_rs" | |
// version = "0.1.0" | |
// edition = "2021" | |
// [dependencies] | |
// eyre = "0.6.12" | |
// schannel = "0.1.23" | |
// serde = { version = "1.0.209", features = ["derive"] } | |
// serde_json = "1.0.127" | |
// windows-sys = "0.52.0" | |
// cert_list.json: | |
// [ | |
// { | |
// "P12File": [ | |
// "../test/key.p12", | |
// "123456" | |
// ] | |
// }, | |
// { | |
// "CerFile": "../test/ca.cer" | |
// } | |
// ] | |
use eyre::Context; | |
use schannel::{ | |
cert_context::{CertContext, HashAlgorithm}, | |
cert_store::{CertAdd, CertStore}, | |
}; | |
use serde::Deserialize; | |
use std::{ | |
borrow::Cow, | |
env, fs, | |
io::{self}, | |
}; | |
fn main() { | |
let result = run(); | |
if let Err(err) = result { | |
println!("err: {err:?}"); | |
} | |
} | |
fn run() -> eyre::Result<()> { | |
let delete = env::args().any(|arg| arg == "delete"); | |
let list_path = "cert_list.json"; | |
let config = | |
fs::read_to_string(list_path).wrap_err_with(|| format!("read config {list_path}"))?; | |
let certs: Vec<InstallCert<'_>> = | |
serde_json::from_str(&config).wrap_err_with(|| format!("parse {list_path}"))?; | |
if delete { | |
delete_installer_certs(&certs)?; | |
} else { | |
install_installer_certs(&certs)?; | |
} | |
Ok(()) | |
} | |
#[derive(Clone, Deserialize)] | |
pub enum InstallCert<'a> { | |
CerFile(Cow<'a, str>), | |
P12File(Cow<'a, str>, Option<Cow<'a, str>>), | |
} | |
fn install_installer_certs<'a>(certs: &[InstallCert<'a>]) -> eyre::Result<()> { | |
for cert in certs { | |
install_to_store(&cert).wrap_err("install_to_store")?; | |
} | |
Ok(()) | |
} | |
fn delete_installer_certs<'a>(certs: &[InstallCert<'a>]) -> eyre::Result<()> { | |
for cert in certs { | |
delete_from_store(&cert).wrap_err("delete_from_store")?; | |
} | |
Ok(()) | |
} | |
fn install_to_store(icert: &InstallCert<'_>) -> eyre::Result<()> { | |
match icert { | |
InstallCert::CerFile(cer_path) => install_certificate(cer_path) | |
.wrap_err_with(|| format!("install_certificate {cer_path:?}")), | |
InstallCert::P12File(p12_path, pass) => install_p12_key(p12_path, pass.as_deref()) | |
.wrap_err_with(|| format!("install_p12_key {p12_path:?}")), | |
} | |
} | |
fn delete_from_store(icert: &InstallCert<'_>) -> eyre::Result<()> { | |
match icert { | |
InstallCert::CerFile(cer_path) => delete_certificate(cer_path) | |
.wrap_err_with(|| format!("install_certificate {cer_path:?}")), | |
InstallCert::P12File(p12_path, pass) => delete_p12_key(p12_path, pass.as_deref()) | |
.wrap_err_with(|| format!("install_p12_key {p12_path:?}")), | |
} | |
} | |
fn delete_p12_key(p12_path_str: &str, password: Option<&str>) -> eyre::Result<()> { | |
let p12_bytes = read_cert(p12_path_str)?; | |
let imported_store = | |
CertStore::import_pkcs12(&p12_bytes, password).wrap_err("import_pkcs12")?; | |
let personal_cert_store = CertStore::open_current_user("My")?; | |
for cert in imported_store.certs() { | |
delete_by_similar_cert(&personal_cert_store, &cert, || HashAlgorithm::sha256())?; | |
} | |
Ok(()) | |
} | |
fn install_p12_key(p12_path_str: &str, password: Option<&str>) -> eyre::Result<()> { | |
let p12_bytes = read_cert(p12_path_str)?; | |
let imported_store = | |
CertStore::import_pkcs12(&p12_bytes, password).wrap_err("import_pkcs12")?; | |
let mut personal_cert_store = CertStore::open_current_user("My")?; | |
for cert in imported_store.certs() { | |
let desc = cert.description().unwrap_or_default(); | |
let friendly_name = cert.friendly_name()?; | |
println!( | |
"Installing .p12 personal desc:{:?} friendly_name:{friendly_name:?}", | |
String::from_utf8_lossy(&desc) | |
); | |
set_installer_friendly_name(&cert, p12_path_str)?; | |
personal_cert_store.add_cert(&cert, CertAdd::ReplaceExisting)?; | |
} | |
Ok(()) | |
} | |
fn delete_certificate(cer_path_str: &str) -> eyre::Result<()> { | |
let der_cert = read_cert(cer_path_str)?; | |
let cert_context = CertContext::new(&der_cert).wrap_err("CertContext::new")?; | |
set_installer_friendly_name(&cert_context, cer_path_str)?; | |
let root_cert_store = CertStore::open_current_user("Root")?; | |
delete_by_similar_cert(&root_cert_store, &cert_context, || HashAlgorithm::sha256())?; | |
Ok(()) | |
} | |
fn set_installer_friendly_name(cert: &CertContext, cer_path_str: &str) -> eyre::Result<()> { | |
let last_friendly_name = cert.friendly_name().unwrap_or_default(); | |
let friendly_name = cer_path_str; | |
let friendly_name = friendly_name.strip_suffix(".cer").unwrap_or(&friendly_name); | |
let friendly_name = friendly_name.replace("../", ""); | |
let friendly_name = friendly_name.replace('/', " "); | |
cert.set_friendly_name(&format!("testing {friendly_name} {last_friendly_name}"))?; | |
Ok(()) | |
} | |
fn install_certificate(cer_path_str: &str) -> eyre::Result<()> { | |
let der_cert = read_cert(cer_path_str)?; | |
let cert_context = CertContext::new(&der_cert).wrap_err("CertContext::new")?; | |
set_installer_friendly_name(&cert_context, cer_path_str)?; | |
let mut root_cert_store = CertStore::open_current_user("Root")?; | |
// delete_by_cert(&cert_store, &cert_context, || HashAlgorithm::sha256())?; | |
{ | |
let desc = cert_context.description().unwrap_or_default(); | |
let friendly_name = cert_context.friendly_name()?; | |
println!( | |
"Installing .cer root desc:{:?} friendly_name:{friendly_name:?}", | |
String::from_utf8_lossy(&desc) | |
); | |
} | |
let mut result = root_cert_store | |
.add_cert(&cert_context, CertAdd::Newer) | |
.map(|_| ()); | |
// The object or property already exists. (os error -2146885627) | |
ignore_err_exists(&mut result); | |
result?; | |
Ok(()) | |
} | |
fn ignore_err_exists<T: Default>(result: &mut io::Result<T>) { | |
if result.as_ref().is_err_and(|err| is_err_exists(err)) { | |
*result = Ok(T::default()); | |
} | |
} | |
/// The object or property already exists. (os error -2146885627) | |
fn is_err_exists(err: &io::Error) -> bool { | |
err.raw_os_error() == Some(-2146885627) | |
} | |
fn delete_by_similar_cert( | |
cert_store: &CertStore, | |
cert_context: &CertContext, | |
// no Copy, Clone on HashAlgorithm | |
alg: fn() -> HashAlgorithm, | |
) -> io::Result<()> { | |
let friendly_name = cert_context.friendly_name().unwrap_or_default(); | |
println!("trying to delete friendly_name:{friendly_name:?}"); | |
let remove_fingerprint = cert_context.fingerprint(alg())?; | |
delete_by_fingerprint(&cert_store, &remove_fingerprint, alg)?; | |
Ok(()) | |
} | |
fn delete_by_fingerprint( | |
cert_store: &CertStore, | |
remove_fingerprint: &[u8], | |
// no Copy, Clone on HashAlgorithm | |
alg: fn() -> HashAlgorithm, | |
) -> io::Result<()> { | |
for cert in cert_store.certs() { | |
let fingerprint = cert.fingerprint(alg())?; | |
if fingerprint == remove_fingerprint { | |
let friendly_name = cert.friendly_name().unwrap_or_default(); | |
println!("deleting friendly_name:{friendly_name:?}"); | |
cert.delete()?; | |
} | |
} | |
Ok(()) | |
} | |
fn read_cert(cert_path_str: &str) -> eyre::Result<Vec<u8>> { | |
let cert_bytes = | |
std::fs::read(cert_path_str).wrap_err_with(|| format!("fs::read {cert_path_str:?}"))?; | |
Ok(cert_bytes) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment