micro-tss 0.1.0

A simple implementation of a Tatsu Signing Server.
use crate::{
    maps::{IGNORED_PROPERTIES, MANIFEST_PROPERTY_TAG_MAP, OBJECT_TAG_MAP},
    signers::Signers,
};
use anyhow::anyhow;
use image4::{der::Encode, property::Value as ManifestValue, Tag};
use plist::Value as PlistValue;
use std::collections::BTreeMap;

fn plist_to_manifest(prop: &PlistValue) -> anyhow::Result<ManifestValue> {
    match prop {
        PlistValue::Array(_) | PlistValue::Dictionary(_) => {
            Err(anyhow!("unexpected constructed property"))
        }
        &PlistValue::Boolean(v) => Ok(ManifestValue::Boolean(v)),
        PlistValue::Data(v) => Ok(ManifestValue::Data(v.clone())),
        PlistValue::Date(_) => Err(anyhow!("manifests do not support dates")),
        PlistValue::Real(_) => Err(anyhow!("manifests do not support real values")),
        PlistValue::Integer(v) => v
            .as_unsigned()
            .ok_or_else(|| anyhow!("bad integer"))
            .map(ManifestValue::Integer),
        // This is used for Ap,OSLongVersion
        PlistValue::String(v) => Ok(ManifestValue::Data(v.clone().into_bytes())),
        PlistValue::Uid(_) => Err(anyhow!("manifests do not support uids")),
        _ => Err(anyhow!("unsupported manifest value")),
    }
}

fn handle_req_key_value(
    key: &str,
    value: &PlistValue,
    manifest_props: &mut BTreeMap<Tag, ManifestValue>,
    manifest: &mut BTreeMap<Tag, ManifestValue>,
) -> anyhow::Result<()> {
    if let Some(tag) = MANIFEST_PROPERTY_TAG_MAP.get(key) {
        let value = plist_to_manifest(value)?;

        debug!("Adding property \"{key}\" with tag '{tag}' and value {value:?}.");

        manifest_props.insert(*tag, value);
    } else if let Some(tag) = OBJECT_TAG_MAP.get(key) {
        let mut object_props = BTreeMap::new();

        let PlistValue::Dictionary(dict) = value else {
            return Err(anyhow!("unexpected type for object properties dict"));
        };

        debug!("Adding object \"{key}\" with tag '{tag}'.");

        for (obj_k, obj_v) in dict {
            match obj_k.as_str() {
                "Digest" => object_props.insert(Tag::from(b"DGST"), plist_to_manifest(obj_v)?),
                "Trusted" => object_props.insert(Tag::from(b"EKEY"), plist_to_manifest(obj_v)?),
                "EPRO" => object_props.insert(Tag::from(b"EPRO"), plist_to_manifest(obj_v)?),
                "ESEC" => object_props.insert(Tag::from(b"ESEC"), plist_to_manifest(obj_v)?),
                "BuildString" => None,
                other => return Err(anyhow!("unknown object property {other}")),
            };
        }

        manifest.insert(*tag, ManifestValue::Dict(object_props));
    } else if IGNORED_PROPERTIES.contains(key) {
        trace!("Skipping ignored property {key} = {value:?}")
    } else if key.starts_with('@') {
        trace!("Skipping optional property {key} = {value:?}.");
    } else {
        return Err(anyhow!("unknown entry \"{key}\""));
    }

    Ok(())
}

pub fn append_ap_img4_ticket(
    req: &BTreeMap<String, PlistValue>,
    resp: &mut BTreeMap<String, PlistValue>,
    signers: &Signers,
    ticket_name: &str,
) -> anyhow::Result<()> {
    let mut manifest: BTreeMap<Tag, ManifestValue> = BTreeMap::new();
    let mut manifest_props: BTreeMap<Tag, ManifestValue> = BTreeMap::new();

    manifest_props.insert(Tag::from(b"CEPO"), 1u32.into());
    manifest_props.insert(Tag::from(b"srvn"), ManifestValue::Data([0; 20].to_vec()));
    manifest_props.insert(
        Tag::from(b"love"),
        ManifestValue::Data(b"22.6.82.0.0,0".to_vec()),
    );

    for (key, value) in req {
        handle_req_key_value(key, value, &mut manifest_props, &mut manifest)?;
    }

    manifest.insert(Tag::from(b"MANP"), ManifestValue::Dict(manifest_props));
    let mut root = BTreeMap::new();
    root.insert(Tag::from(b"MANB"), ManifestValue::Dict(manifest));

    let signed = if req.contains_key("Ap,LocalPolicy") {
        &signers.local_policy
    } else {
        &signers.ticket
    }
    .encode_and_sign(&root)?;

    let mut encoded = Vec::new();
    signed.encode_to_vec(&mut encoded)?;

    resp.insert(ticket_name.to_string(), PlistValue::Data(encoded));

    Ok(())
}