lesavka/client/src/paste.rs

74 lines
2.2 KiB
Rust
Raw Normal View History

// client/src/paste.rs
#![forbid(unsafe_code)]
use anyhow::{Context, Result};
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD;
use chacha20poly1305::aead::{Aead, KeyInit, OsRng, rand_core::RngCore};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use lesavka_common::lesavka::PasteRequest;
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 = if text.chars().count() > max {
text.chars().take(max).collect::<String>()
} else {
text.to_string()
};
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_key(&raw)
}
fn decode_key(raw: &str) -> Result<[u8; 32]> {
let s = raw.trim();
let s = s.strip_prefix("hex:").unwrap_or(s);
let bytes = if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
hex_to_bytes(s)?
} else {
STANDARD
.decode(s.as_bytes())
.context("LESAVKA_PASTE_KEY must be 32-byte base64 or 64-char hex")?
};
if bytes.len() != 32 {
anyhow::bail!("LESAVKA_PASTE_KEY must decode to 32 bytes");
}
let mut out = [0u8; 32];
out.copy_from_slice(&bytes);
Ok(out)
}
fn hex_to_bytes(s: &str) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(s.len() / 2);
let chars: Vec<char> = s.chars().collect();
for i in (0..chars.len()).step_by(2) {
let hi = chars[i].to_digit(16).context("hex decode failed")?;
let lo = chars[i + 1].to_digit(16).context("hex decode failed")?;
out.push(((hi << 4) | lo) as u8);
}
Ok(out)
}