47 lines
1.6 KiB
Rust
47 lines
1.6 KiB
Rust
// 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)
|
|
}
|