cinchcli-core 0.1.0

Shared client-side primitives for Cinch (cinchcli.com): generated wire DTOs, REST/WebSocket clients, AES-256-GCM + X25519 crypto, credential storage, local SQLite store, and sync helpers.
Documentation
//! Shared key-exchange responder logic. Both desktop (`ws.rs`) and
//! CLI (`pull --watch`, `pair`) implement key-bearer behavior by
//! invoking `respond` when the relay broadcasts
//! `key_exchange_requested` for a peer device that has registered a
//! public key but lacks an encrypted bundle.

use crate::crypto;
use crate::http::RestClient;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};

#[derive(Debug, thiserror::Error)]
pub enum RespondError {
    #[error("derive shared key: {0}")]
    DeriveShared(String),
    #[error("encrypt user key: {0}")]
    Encrypt(String),
    #[error("post bundle: {0}")]
    Post(#[from] crate::http::HttpError),
}

/// Build and post an encrypted key bundle for `target_device_id`.
///
/// `user_master_key_b64` is the local device's stored encryption key
/// (`base64url(32-byte AES-256 secret)`). `peer_pub_b64` comes from
/// the WS event payload; the relay vouches for its origin.
pub async fn respond(
    client: &RestClient,
    target_device_id: &str,
    peer_pub_b64: &str,
    user_master_key_b64: &str,
) -> Result<(), RespondError> {
    let (eph_priv_b64, eph_pub_b64) = crypto::generate_ephemeral_keypair();

    let shared = crypto::derive_shared_key(&eph_priv_b64, peer_pub_b64)
        .map_err(RespondError::DeriveShared)?;

    let raw_master = URL_SAFE_NO_PAD
        .decode(user_master_key_b64)
        .map_err(|e| RespondError::Encrypt(format!("master key decode: {}", e)))?;

    let encrypted = crypto::encrypt(&shared, &raw_master).map_err(RespondError::Encrypt)?;

    client
        .post_key_bundle(target_device_id, &eph_pub_b64, &encrypted)
        .await?;
    Ok(())
}