74 lines
2.2 KiB
Rust
74 lines
2.2 KiB
Rust
|
|
// 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)
|
||
|
|
}
|