100 lines
4.0 KiB
Rust
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())
|
|
} |