lesavka/client/src/paste.rs

47 lines
1.6 KiB
Rust
Raw Normal View History

// client/src/paste.rs
#![forbid(unsafe_code)]
use anyhow::{Context, Result};
use chacha20poly1305::aead::{Aead, KeyInit, OsRng, rand_core::RngCore};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use lesavka_common::lesavka::PasteRequest;
use lesavka_common::paste::{decode_shared_key, truncate_text};
/// Build an encrypted clipboard request for the server's paste RPC.
///
/// Inputs: the raw clipboard text captured on the desktop client.
/// Outputs: a `PasteRequest` whose payload is truncated to policy, encrypted,
/// and marked as such for the server-side validator.
/// Why: the client owns nonce generation and pre-flight truncation so oversized
/// clipboard content fails predictably before any RPC is attempted.
pub fn build_paste_request(text: &str) -> Result<PasteRequest> {
let max = std::env::var("LESAVKA_PASTE_MAX")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(4096);
let text = truncate_text(text, max);
let key = load_key()?;
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key));
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, text.as_bytes())
.map_err(|e| anyhow::anyhow!("paste encrypt failed: {e}"))?;
Ok(PasteRequest {
nonce: nonce_bytes.to_vec(),
data: ciphertext,
encrypted: true,
})
}
fn load_key() -> Result<[u8; 32]> {
let raw = std::env::var("LESAVKA_PASTE_KEY")
.context("LESAVKA_PASTE_KEY not set (required for PasteText RPC)")?;
decode_shared_key(&raw)
}