OXYGENHUB

Coverage / crates/spectra-mcp/src/instance.rs 92.7%

51 of 55 lines run, 8 of 22 functions · run #28 on main · run missed

1
//! The instance's word for its TLS certificate (spec §8.6): the
2
//! `instance.tls` event the daemon signs with the instance key, and the
3
//! check a client makes of it.
4
5
use anyhow::{bail, Context, Result};
6
use serde_json::{json, Value as Json};
7
use spectra_core::event::Event;
8
use spectra_core::identity::Identity;
9
use spectra_core::keys::KeyPair;
10
use spectra_core::ledger::Ledger;
11
use spectra_core::proof::Proof;
12
13
/// The latest `instance.tls` event, if any.
1439×
pub async fn latest_tls(ledger: &Ledger) -> Result<Option<Event>> {
1539×
    let mut last = None;
1639×
    let mut from = 1;
17
    loop {
1871×
        let rows = ledger.events_of_kinds(from, &["instance.tls"], 1000).await?;
1971×
        match rows.last() {
2032×
            Some(e) => {
2132×
                from = e.seq + 1;
2232×
                last = Some(e.clone());
2332×
            }
2439×
            None => return Ok(last),
25
        }
26
    }
2739×
}
28
29
/// Record the certificate's pin, signed by the instance key, unless the
30
/// latest record already names it.
319×
pub async fn record_tls(ledger: &Ledger, instance: &KeyPair, spki: &[u8; 32], names: &[String]) -> Result<bool> {
329×
    let hex_spki = hex::encode(spki);
339×
    if let Some(e) = latest_tls(ledger).await? {
342×
        if e.json().get("spki_sha256").and_then(Json::as_str) == Some(hex_spki.as_str()) {
351×
            return Ok(false);
361×
        }
377×
    }
388×
    let payload = json!({ "spki_sha256": hex_spki, "names": names });
398×
    ledger.append_one(spectra_core::principals::signed_event(instance, "system", "instance.tls", payload)).await?;
408×
    Ok(true)
419×
}
42
43
/// What `GET /api/v1/instance` told a client, checked.
44
#[derive(Clone, Debug)]
45
pub struct Checked {
46
    pub name: String,
47
    pub instance: Identity,
48
    pub owner: Option<String>,
49
    /// The certificate the instance key vouches for.
50
    pub spki_sha256: [u8; 32],
51
}
52
53
/// Check `/api/v1/instance`'s answer: the identity is whole, and the
54
/// instance key signed an `instance.tls` naming the certificate `seen`.
5510×
pub fn check(answer: &Json, seen: &[u8; 32]) -> Result<Checked> {
5610×
    let ip = answer.get("instance").context("no instance in the answer")?;
5710×
    let bytes = ip.get("identity").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no instance identity")?;
5810×
    let instance = Identity::decode(&bytes).map_err(|e| anyhow::anyhow!("malformed instance identity: {e}"))?;
5910×
    let tls = answer.get("tls").filter(|t| !t.is_null()).context("the server has not signed a TLS certificate")?;
6010×
    let payload = tls.get("payload").context("no TLS payload")?;
6110×
    let proof = tls.get("proof").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no TLS proof")?;
6210×
    let proof = Proof::decode(&proof).map_err(|e| anyhow::anyhow!(e))?;
6310×
    if proof.key() != instance.hash().0 || proof.method != "cli" {
640×
        bail!("the certificate is not vouched for by the instance key");
6510×
    }
6610×
    proof
6710×
        .verify(&instance, Some(&spectra_core::event::canonical_json(payload)))
6810×
        .map_err(|e| anyhow::anyhow!("the instance's signature on its certificate: {e}"))?;
6910×
    let named = payload.get("spki_sha256").and_then(Json::as_str).context("no pin in the TLS record")?;
7010×
    if named != hex::encode(seen) {
711×
        bail!("the server presented a certificate its instance key did not sign");
729×
    }
739×
    Ok(Checked {
749×
        name: answer.get("name").and_then(Json::as_str).unwrap_or("").to_string(),
759×
        instance,
769×
        owner: answer.get("owner").and_then(Json::as_str).map(String::from),
779×
        spki_sha256: *seen,
789×
    })
7910×
}