ssh-cert-dist/src/certs.rs
2022-12-01 13:58:56 +00:00

104 lines
3.2 KiB
Rust

use anyhow::Context;
use ssh_key::{Certificate, PublicKey};
use std::{path::{Path, PathBuf}, fmt::Debug};
use tokio::fs;
use tracing::{trace, instrument};
pub async fn read_certs(
ca: &PublicKey,
path: impl AsRef<Path>,
) -> anyhow::Result<Vec<Certificate>> {
if !path.as_ref().exists() {
return Ok(Vec::new());
}
read_dir(path.as_ref().join(ca_dir(ca))).await
}
#[instrument]
pub async fn read_dir(path: impl AsRef<Path> + Debug) -> anyhow::Result<Vec<Certificate>> {
let mut dir = fs::read_dir(path.as_ref())
.await
.with_context(|| format!("read certs dir '{:?}'", path.as_ref()))?;
let mut certs = Vec::new();
while let Some(entry) = dir.next_entry().await? {
//TODO: investigate why path().ends_with doesn't work
if !entry
.file_name()
.into_string()
.unwrap()
.ends_with("-cert.pub")
{
trace!(
"skipped {:?} due to missing '-cert.pub' extension",
entry.path()
);
continue;
}
let contents = fs::read(&entry.path())
.await
.with_context(|| format!("read {:?}", entry.path()))?;
let string_repr = parse_utf8(contents)?;
let cert = Certificate::from_openssh(&string_repr)
.with_context(|| format!("parse {:?} as openssh certificate", entry.path()))?;
certs.push(cert);
}
Ok(certs)
}
fn parse_utf8(bytes: Vec<u8>) -> anyhow::Result<String> {
String::from_utf8(bytes).context("invalid utf-8")
}
pub async fn read_pubkey(path: impl AsRef<Path>) -> anyhow::Result<PublicKey> {
let contents = fs::read(&path)
.await
.with_context(|| format!("read {:?}", path.as_ref()))?;
let string_repr = parse_utf8(contents)?;
PublicKey::from_openssh(&string_repr)
.with_context(|| format!("parse '{}' as public key", string_repr))
}
fn ca_dir(ca: &PublicKey) -> String {
ca.comment().to_string()
}
#[instrument]
fn cert_path(ca: &PublicKey, identifier: &str) -> String {
let _ca_fingerprint = ca.fingerprint(Default::default());
format!("{}/{}-cert.pub", ca_dir(ca), identifier)
}
#[instrument]
pub async fn store_cert(
cert_dir: impl AsRef<Path> + Debug,
ca: &PublicKey,
cert: &Certificate,
) -> anyhow::Result<PathBuf> {
// TODO: proper store
let path = cert_dir.as_ref().join(cert_path(&ca, cert.key_id()));
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
}
fs::write(&path, cert.to_openssh().context("encode cert")?).await?;
Ok(path)
}
pub async fn load_cert(
cert_dir: impl AsRef<Path>,
ca: &PublicKey,
identifier: &str,
) -> anyhow::Result<Option<Certificate>> {
let path = cert_dir.as_ref().join(cert_path(ca, identifier));
if !path.exists() {
trace!("no certificate at {:?}", path);
return Ok(None);
}
let contents = fs::read(&path)
.await
.with_context(|| format!("read {:?}", &path))?;
let string_repr = parse_utf8(contents)?;
Ok(Some(Certificate::from_openssh(&string_repr).with_context(
|| format!("parse {:?} as openssh certificate", &path),
)?))
}