OXYGENHUB

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

50 of 55 lines run, 8 of 22 functions · run #19 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 oxy_core::identity::Identity;
7
use serde_json::{json, Value as Json};
8
use spectra_core::event::Event;
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.
149×
pub async fn latest_tls(ledger: &Ledger) -> Result<Option<Event>> {
159×
    let mut last = None;
169×
    let mut from = 1;
17
    loop {
1815×
        let rows = ledger.events_of_kinds(from, &["instance.tls"], 1000).await?;
1915×
        match rows.last() {
206×
            Some(e) => {
216×
                from = e.seq + 1;
226×
                last = Some(e.clone());
236×
            }
249×
            None => return Ok(last),
25
        }
26
    }
279×
}
28
29
/// Record the certificate's pin, signed by the instance key, unless the
30
/// latest record already names it.
314×
pub async fn record_tls(ledger: &Ledger, instance: &KeyPair, spki: &[u8; 32], names: &[String]) -> Result<bool> {
324×
    let hex_spki = hex::encode(spki);
334×
    if let Some(e) = latest_tls(ledger).await? {
341×
        if e.json().get("spki_sha256").and_then(Json::as_str) == Some(hex_spki.as_str()) {
351×
            return Ok(false);
360×
        }
373×
    }
383×
    let payload = json!({ "spki_sha256": hex_spki, "names": names });
393×
    ledger.append_one(spectra_core::principals::signed_event(instance, "system", "instance.tls", payload)).await?;
403×
    Ok(true)
414×
}
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`.
552×
pub fn check(answer: &Json, seen: &[u8; 32]) -> Result<Checked> {
562×
    let ip = answer.get("instance").context("no instance in the answer")?;
572×
    let bytes = ip.get("identity").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no instance identity")?;
582×
    let instance = Identity::decode(&bytes).map_err(|e| anyhow::anyhow!("malformed instance identity: {e}"))?;
592×
    let tls = answer.get("tls").filter(|t| !t.is_null()).context("the server has not signed a TLS certificate")?;
602×
    let payload = tls.get("payload").context("no TLS payload")?;
612×
    let proof = tls.get("proof").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no TLS proof")?;
622×
    let proof = Proof::decode(&proof).map_err(|e| anyhow::anyhow!(e))?;
632×
    if proof.key() != instance.hash().0 || proof.method != "cli" {
640×
        bail!("the certificate is not vouched for by the instance key");
652×
    }
662×
    proof
672×
        .verify(&instance, Some(&spectra_core::event::canonical_json(payload)))
682×
        .map_err(|e| anyhow::anyhow!("the instance's signature on its certificate: {e}"))?;
692×
    let named = payload.get("spki_sha256").and_then(Json::as_str).context("no pin in the TLS record")?;
702×
    if named != hex::encode(seen) {
711×
        bail!("the server presented a certificate its instance key did not sign");
721×
    }
731×
    Ok(Checked {
741×
        name: answer.get("name").and_then(Json::as_str).unwrap_or("").to_string(),
751×
        instance,
761×
        owner: answer.get("owner").and_then(Json::as_str).map(String::from),
771×
        spki_sha256: *seen,
781×
    })
792×
}