OXYGENHUB

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

51 of 55 lines run, 8 of 22 functions · run #29 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.
1440×
pub async fn latest_tls(ledger: &Ledger) -> Result<Option<Event>> {
1540×
    let mut last = None;
1640×
    let mut from = 1;
17
    loop {
1873×
        let rows = ledger.events_of_kinds(from, &["instance.tls"], 1000).await?;
1973×
        match rows.last() {
2033×
            Some(e) => {
2133×
                from = e.seq + 1;
2233×
                last = Some(e.clone());
2333×
            }
2440×
            None => return Ok(last),
25
        }
26
    }
2740×
}
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`.
5511×
pub fn check(answer: &Json, seen: &[u8; 32]) -> Result<Checked> {
5611×
    let ip = answer.get("instance").context("no instance in the answer")?;
5711×
    let bytes = ip.get("identity").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no instance identity")?;
5811×
    let instance = Identity::decode(&bytes).map_err(|e| anyhow::anyhow!("malformed instance identity: {e}"))?;
5911×
    let tls = answer.get("tls").filter(|t| !t.is_null()).context("the server has not signed a TLS certificate")?;
6011×
    let payload = tls.get("payload").context("no TLS payload")?;
6111×
    let proof = tls.get("proof").and_then(Json::as_str).and_then(|h| hex::decode(h).ok()).context("no TLS proof")?;
6211×
    let proof = Proof::decode(&proof).map_err(|e| anyhow::anyhow!(e))?;
6311×
    if proof.key() != instance.hash().0 || proof.method != "cli" {
640×
        bail!("the certificate is not vouched for by the instance key");
6511×
    }
6611×
    proof
6711×
        .verify(&instance, Some(&spectra_core::event::canonical_json(payload)))
6811×
        .map_err(|e| anyhow::anyhow!("the instance's signature on its certificate: {e}"))?;
6911×
    let named = payload.get("spki_sha256").and_then(Json::as_str).context("no pin in the TLS record")?;
7011×
    if named != hex::encode(seen) {
711×
        bail!("the server presented a certificate its instance key did not sign");
7210×
    }
7310×
    Ok(Checked {
7410×
        name: answer.get("name").and_then(Json::as_str).unwrap_or("").to_string(),
7510×
        instance,
7610×
        owner: answer.get("owner").and_then(Json::as_str).map(String::from),
7710×
        spki_sha256: *seen,
7810×
    })
7911×
}