104 lines
3.2 KiB
Rust
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),
|
|
)?))
|
|
}
|