herodb/crates/libcryptoa/src/lib.rs
2025-08-16 15:10:55 +02:00

100 lines
4.0 KiB
Rust

// In crates/libcryptoa/src/lib.rs
use std::str::FromStr;
use age::{Decryptor, Encryptor, x25519};
use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use secrecy::ExposeSecret;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AsymmetricCryptoError {
#[error("key parsing failed")]
ParseKey,
#[error("age crypto error: {0}")]
Age(String),
#[error("invalid utf-8 in plaintext")]
Utf8,
#[error("invalid signature length")]
SignatureLen,
#[error("signature verification failed")]
Verify,
#[error("base64 decoding failed: {0}")]
Base64(#[from] base64::DecodeError),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}
fn parse_recipient(s: &str) -> Result<x25519::Recipient, AsymmetricCryptoError> {
x25519::Recipient::from_str(s).map_err(|_| AsymmetricCryptoError::ParseKey)
}
fn parse_identity(s: &str) -> Result<x25519::Identity, AsymmetricCryptoError> {
x25519::Identity::from_str(s).map_err(|_| AsymmetricCryptoError::ParseKey)
}
fn parse_ed25519_signing_key(s: &str) -> Result<SigningKey, AsymmetricCryptoError> {
let bytes = B64.decode(s)?;
let key_bytes: [u8; 32] = bytes.try_into().map_err(|_| AsymmetricCryptoError::ParseKey)?;
Ok(SigningKey::from_bytes(&key_bytes))
}
fn parse_ed25519_verifying_key(s: &str) -> Result<VerifyingKey, AsymmetricCryptoError> {
let bytes = B64.decode(s)?;
let key_bytes: [u8; 32] = bytes.try_into().map_err(|_| AsymmetricCryptoError::ParseKey)?;
VerifyingKey::from_bytes(&key_bytes).map_err(|_| AsymmetricCryptoError::ParseKey)
}
pub fn gen_enc_keypair() -> (String, String) {
let id = x25519::Identity::generate();
let pk = id.to_public();
(pk.to_string(), id.to_string().expose_secret().to_string())
}
pub fn gen_sign_keypair() -> (String, String) {
let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
let verifying_key = signing_key.verifying_key();
(B64.encode(verifying_key.to_bytes()), B64.encode(signing_key.to_bytes()))
}
pub fn encrypt_b64(recipient_str: &str, msg: &str) -> Result<String, AsymmetricCryptoError> {
let recipient = parse_recipient(recipient_str)?;
let encryptor = Encryptor::with_recipients(vec![Box::new(recipient)])
.ok_or_else(|| AsymmetricCryptoError::Age("Failed to create encryptor".into()))?;
let mut encrypted = vec![];
let mut writer = encryptor.wrap_output(&mut encrypted)?;
std::io::Write::write_all(&mut writer, msg.as_bytes())?;
writer.finish()?;
Ok(B64.encode(encrypted))
}
pub fn decrypt_b64(identity_str: &str, ct_b64: &str) -> Result<String, AsymmetricCryptoError> {
let identity = parse_identity(identity_str)?;
let ct = B64.decode(ct_b64)?;
let decryptor = Decryptor::new(&ct[..]).map_err(|e| AsymmetricCryptoError::Age(e.to_string()))?;
let mut decrypted = vec![];
if let Decryptor::Recipients(d) = decryptor {
let mut reader = d.decrypt(std::iter::once(&identity as &dyn age::Identity))
.map_err(|e| AsymmetricCryptoError::Age(e.to_string()))?;
std::io::Read::read_to_end(&mut reader, &mut decrypted)?;
String::from_utf8(decrypted).map_err(|_| AsymmetricCryptoError::Utf8)
} else {
Err(AsymmetricCryptoError::Age("Passphrase decryption not supported".into()))
}
}
pub fn sign_b64(signing_secret_str: &str, msg: &str) -> Result<String, AsymmetricCryptoError> {
let signing_key = parse_ed25519_signing_key(signing_secret_str)?;
let signature = signing_key.sign(msg.as_bytes());
Ok(B64.encode(signature.to_bytes()))
}
pub fn verify_b64(verify_pub_str: &str, msg: &str, sig_b64: &str) -> Result<bool, AsymmetricCryptoError> {
let verifying_key = parse_ed25519_verifying_key(verify_pub_str)?;
let sig_bytes = B64.decode(sig_b64)?;
let signature = Signature::from_slice(&sig_bytes).map_err(|_| AsymmetricCryptoError::SignatureLen)?;
Ok(verifying_key.verify(msg.as_bytes(), &signature).is_ok())
}