haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Continuations sealed with a key only this service holds.
//!
//! A sealed token is one format byte, a 24-byte nonce drawn for that token,
//! and the token's bytes sealed with XChaCha20-Poly1305 under the format byte,
//! written as lowercase hex. The caller who holds it can neither read nor
//! alter what is inside. The key is drawn from the operating system when the
//! service is made and lives only in its memory, so a token from another
//! service, or from before a restart, does not open.

use chacha20poly1305::aead::rand_core::RngCore;
use chacha20poly1305::aead::{Aead, KeyInit, OsRng, Payload};
use chacha20poly1305::{XChaCha20Poly1305, XNonce};

use super::codec;
use crate::serve::storage::Failure;

/// The first byte of every sealed token, and the data it is sealed under.
const FORMAT: u8 = 2;
const NONCE_BYTES: usize = 24;

/// This service's key for its continuations; never written, logged or shown.
pub struct Seal {
    cipher: XChaCha20Poly1305,
}

/// A token that does not open, for every reason alike.
fn unopened() -> Failure {
    Failure::new(
        "query_continuation_invalid",
        "the continuation was not made by this service as it runs now; the service may have \
         restarted, so ask again from the first page",
    )
}

impl Seal {
    /// A key drawn from the operating system's randomness.
    ///
    /// # Errors
    ///
    /// When the operating system gives no randomness.
    pub fn new() -> Result<Self, Failure> {
        let mut key = [0_u8; 32];
        OsRng.try_fill_bytes(&mut key).map_err(|error| {
            Failure::new(
                "query_seal_unavailable",
                format!("no randomness for the continuation key: {error}"),
            )
        })?;
        let cipher = XChaCha20Poly1305::new(&key.into());
        key.fill(0);
        Ok(Self { cipher })
    }

    /// `plain` sealed under a nonce drawn for it, as hex.
    ///
    /// # Errors
    ///
    /// When no nonce can be drawn or the bytes cannot be sealed.
    pub fn seal(&self, plain: &[u8]) -> Result<String, Failure> {
        let mut nonce = [0_u8; NONCE_BYTES];
        OsRng
            .try_fill_bytes(&mut nonce)
            .map_err(|error| Failure::new("query_seal_unavailable", error.to_string()))?;
        let payload = Payload {
            msg: plain,
            aad: &[FORMAT],
        };
        let sealed = self
            .cipher
            .encrypt(&XNonce::from(nonce), payload)
            .map_err(|_| Failure::new("query_seal_unavailable", "the token could not be sealed"))?;
        let mut token = Vec::with_capacity(1 + NONCE_BYTES + sealed.len());
        token.push(FORMAT);
        token.extend_from_slice(&nonce);
        token.extend_from_slice(&sealed);
        Ok(codec::encode(&token))
    }

    /// The bytes `token` was sealed from.
    ///
    /// # Errors
    ///
    /// `query_continuation_invalid` for a token that is not canonical hex, is
    /// cut, altered, of another format, or sealed by another key.
    pub fn open(&self, token: &str) -> Result<Vec<u8>, Failure> {
        let bytes = codec::decode(token).map_err(|_| unopened())?;
        let (format, rest) = bytes.split_first().ok_or_else(unopened)?;
        if *format != FORMAT {
            return Err(unopened());
        }
        let (nonce, sealed) = rest.split_at_checked(NONCE_BYTES).ok_or_else(unopened)?;
        let payload = Payload {
            msg: sealed,
            aad: &[FORMAT],
        };
        self.cipher
            .decrypt(XNonce::from_slice(nonce), payload)
            .map_err(|_| unopened())
    }
}