micro-tss 0.1.0

A simple implementation of a Tatsu Signing Server.
use image4_pki::DigestAlgo;
use serde::{Deserialize, Serialize};
use std::{net::SocketAddr, path::PathBuf};

mod digest_serde {
    use super::*;
    /// `serde` handles `Option`'s a bit specially. Visitors have `visit_none`, `visit_some` and
    /// `__private_visit_untagged_option` method with the latter being obviously private API, thus
    /// writing a visitor for an option directly isn't a good idea. We can, however, wrap the type
    /// we want to deserialize, and let the umbrella implementation of `Deserialize` on `Option`'s
    /// do part of the work for us.
    use serde::{
        de::{Unexpected, Visitor},
        Deserializer, Serializer,
    };

    struct DigestAlgoWrapper(DigestAlgo);

    impl Serialize for DigestAlgoWrapper {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            serializer.serialize_some(match self.0 {
                DigestAlgo::Sha1 => "sha1",
                DigestAlgo::Sha256 => "sha256",
                DigestAlgo::Sha384 => "sha384",
                _ => panic!("unhandled digest algorithm"),
            })
        }
    }

    struct DigestAlgoVisitor;

    impl Visitor<'_> for DigestAlgoVisitor {
        type Value = DigestAlgo;

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            match v {
                "sha1" => Ok(DigestAlgo::Sha1),
                "sha256" => Ok(DigestAlgo::Sha256),
                "sha384" => Ok(DigestAlgo::Sha384),
                other => Err(E::invalid_value(Unexpected::Str(other), &self)),
            }
        }

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("\"sha1\", \"sha256\" or \"sha384\"")
        }
    }

    impl<'de> Deserialize<'de> for DigestAlgoWrapper {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            deserializer
                .deserialize_str(DigestAlgoVisitor)
                .map(DigestAlgoWrapper)
        }
    }

    pub(super) fn serialize<S: Serializer>(
        algo: &Option<DigestAlgo>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        (*algo).map(DigestAlgoWrapper).serialize(serializer)
    }

    pub(super) fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Option<DigestAlgo>, D::Error> {
        let deserialized = Option::<DigestAlgoWrapper>::deserialize(deserializer)?;
        Ok(deserialized.map(|wrapper| wrapper.0))
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SignerConfig {
    pub certificate_chain_path: PathBuf,
    pub private_key_path: PathBuf,
    #[serde(with = "digest_serde", default)]
    pub digest_algorithm: Option<DigestAlgo>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SignersConfig {
    pub ap_ticket_signer: SignerConfig,
    pub local_policy_signer: SignerConfig,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SetupConfig {
    pub listen_addr: SocketAddr,
    #[serde(flatten)]
    pub signers: SignersConfig,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct RuntimeConfig {
    #[serde(default)]
    pub forward_local_policy: bool,
    #[serde(default)]
    pub user_agent: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Config {
    #[serde(flatten)]
    pub setup: SetupConfig,
    #[serde(flatten)]
    pub runtime: RuntimeConfig,
}