micro-tss 0.1.0

A simple implementation of a Tatsu Signing Server.
use anyhow::Context;
use image4::{
    manifest::{CertChain, UnsignedManifest},
    property::Value,
    Manifest, Tag,
};
use image4_pki::{PrivateKey, SigningKey};
use std::{collections::BTreeMap, path::Path};
use tokio::{fs::File, io::AsyncReadExt};
use x509_cert::Certificate;

use crate::config::{SignerConfig, SignersConfig};

#[derive(Clone, Debug)]
pub struct Signer {
    chain: CertChain,
    key: SigningKey,
}

async fn load_pem_cert_chain(path: impl AsRef<Path>) -> anyhow::Result<CertChain> {
    let mut v = Vec::new();
    File::open(path).await?.read_to_end(&mut v).await?;

    let certs = Certificate::load_pem_chain(&v)?;
    Ok(CertChain::from_certs(&certs)?)
}

async fn load_pem_private_key(path: impl AsRef<Path>) -> anyhow::Result<PrivateKey> {
    let mut s = String::new();
    File::open(path).await?.read_to_string(&mut s).await?;

    Ok(PrivateKey::from_pem(&s)?)
}

impl Signer {
    pub async fn load(config: &SignerConfig) -> anyhow::Result<Self> {
        let chain = load_pem_cert_chain(&config.certificate_chain_path)
            .await
            .context("Failed to load certificate chain.")?;

        let private_key = load_pem_private_key(&config.private_key_path)
            .await
            .context("Failed to load private key.")?;

        let key = SigningKey::from_key_and_algo(private_key, config.digest_algorithm)?;

        Ok(Self { chain, key })
    }

    pub fn encode_and_sign(&self, body: &BTreeMap<Tag, Value>) -> anyhow::Result<Manifest> {
        let encoded = UnsignedManifest::encode_from(body)?;
        Ok(encoded.sign(&self.key, self.chain.clone())?)
    }
}

#[derive(Clone)]
pub struct Signers {
    pub ticket: Signer,
    pub local_policy: Signer,
}

impl Signers {
    pub async fn load(config: &SignersConfig) -> anyhow::Result<Self> {
        let ticket = Signer::load(&config.ap_ticket_signer)
            .await
            .context("Failed to load AP ticket signer.")?;

        let local_policy = Signer::load(&config.local_policy_signer)
            .await
            .context("Failed to load local policy signer at {}.")?;

        Ok(Self {
            ticket,
            local_policy,
        })
    }
}