Skip to content

Instantly share code, notes, and snippets.

@dishmaker
Created August 29, 2024 14:26
Show Gist options
  • Save dishmaker/9209804ba0950a3a4da537222ff1e006 to your computer and use it in GitHub Desktop.
Save dishmaker/9209804ba0950a3a4da537222ff1e006 to your computer and use it in GitHub Desktop.
// [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