mirror of
https://github.com/sbrow/envr.git
synced 2026-06-28 02:58:33 -04:00
feat: Added age-ffi.
This commit is contained in:
299
zig-vendor/age-ffi/src/decrypt.rs
Normal file
299
zig-vendor/age-ffi/src/decrypt.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
//! In-memory decryption functions.
|
||||
|
||||
use crate::helpers::cstr_to_str;
|
||||
use crate::types::{AgeBuffer, AgeResult};
|
||||
use age::ssh;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::os::raw::c_char;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Decrypt data in memory using a single x25519 identity.
|
||||
/// This is a simple API for common use cases.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ciphertext` - Pointer to the encrypted data
|
||||
/// * `ciphertext_len` - Length of the ciphertext
|
||||
/// * `identity` - The private key string (AGE-SECRET-KEY-1...)
|
||||
/// * `output` - Pointer to receive the decrypted buffer
|
||||
///
|
||||
/// # Returns
|
||||
/// AgeResult indicating success or failure
|
||||
#[no_mangle]
|
||||
pub extern "C" fn age_decrypt(
|
||||
ciphertext: *const u8,
|
||||
ciphertext_len: usize,
|
||||
identity: *const c_char,
|
||||
output: *mut AgeBuffer,
|
||||
) -> AgeResult {
|
||||
if ciphertext.is_null() || output.is_null() {
|
||||
return AgeResult::InvalidInput;
|
||||
}
|
||||
|
||||
let ciphertext = unsafe { std::slice::from_raw_parts(ciphertext, ciphertext_len) };
|
||||
|
||||
let identity_str = match unsafe { cstr_to_str(identity) } {
|
||||
Ok(s) => s,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let identity = match age::x25519::Identity::from_str(identity_str) {
|
||||
Ok(i) => i,
|
||||
Err(_) => return AgeResult::InvalidIdentity,
|
||||
};
|
||||
|
||||
let decrypted = match age::decrypt(&identity, ciphertext) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*output = AgeBuffer::from_vec(decrypted);
|
||||
}
|
||||
|
||||
AgeResult::Success
|
||||
}
|
||||
|
||||
/// Decrypt data in memory using multiple identities.
|
||||
/// The library will try each identity until one succeeds.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ciphertext` - Pointer to the encrypted data
|
||||
/// * `ciphertext_len` - Length of the ciphertext
|
||||
/// * `identities` - Array of identity C strings
|
||||
/// * `identity_count` - Number of identities
|
||||
/// * `output` - Pointer to receive the decrypted buffer
|
||||
///
|
||||
/// # Returns
|
||||
/// AgeResult indicating success or failure
|
||||
#[no_mangle]
|
||||
pub extern "C" fn age_decrypt_multi(
|
||||
ciphertext: *const u8,
|
||||
ciphertext_len: usize,
|
||||
identities: *const *const c_char,
|
||||
identity_count: usize,
|
||||
output: *mut AgeBuffer,
|
||||
) -> AgeResult {
|
||||
if ciphertext.is_null() || identities.is_null() || output.is_null() || identity_count == 0 {
|
||||
return AgeResult::InvalidInput;
|
||||
}
|
||||
|
||||
let ciphertext = unsafe { std::slice::from_raw_parts(ciphertext, ciphertext_len) };
|
||||
let identity_ptrs = unsafe { std::slice::from_raw_parts(identities, identity_count) };
|
||||
|
||||
let mut parsed_identities: Vec<Box<dyn age::Identity>> = Vec::new();
|
||||
|
||||
for &ptr in identity_ptrs {
|
||||
let identity_str = match unsafe { cstr_to_str(ptr) } {
|
||||
Ok(s) => s.trim(),
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// Try x25519 first
|
||||
if let Ok(i) = age::x25519::Identity::from_str(identity_str) {
|
||||
parsed_identities.push(Box::new(i));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comments and empty lines
|
||||
if identity_str.is_empty() || identity_str.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return AgeResult::InvalidIdentity;
|
||||
}
|
||||
|
||||
if parsed_identities.is_empty() {
|
||||
return AgeResult::NoIdentities;
|
||||
}
|
||||
|
||||
let decryptor = match age::Decryptor::new(ciphertext) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
let mut decrypted = Vec::new();
|
||||
let mut reader = match decryptor.decrypt(parsed_identities.iter().map(|i| i.as_ref())) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
if reader.read_to_end(&mut decrypted).is_err() {
|
||||
return AgeResult::DecryptionFailed;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*output = AgeBuffer::from_vec(decrypted);
|
||||
}
|
||||
|
||||
AgeResult::Success
|
||||
}
|
||||
|
||||
/// Decrypt data using an SSH private key.
|
||||
/// Supports both Ed25519 and RSA SSH keys.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ciphertext` - Pointer to the encrypted data
|
||||
/// * `ciphertext_len` - Length of the ciphertext
|
||||
/// * `ssh_key` - The SSH private key in PEM or OpenSSH format
|
||||
/// * `passphrase` - Optional passphrase for encrypted SSH keys (can be null)
|
||||
/// * `output` - Pointer to receive the decrypted buffer
|
||||
///
|
||||
/// # Returns
|
||||
/// AgeResult indicating success or failure
|
||||
#[no_mangle]
|
||||
pub extern "C" fn age_decrypt_ssh(
|
||||
ciphertext: *const u8,
|
||||
ciphertext_len: usize,
|
||||
ssh_key: *const c_char,
|
||||
passphrase: *const c_char,
|
||||
output: *mut AgeBuffer,
|
||||
) -> AgeResult {
|
||||
if ciphertext.is_null() || output.is_null() {
|
||||
return AgeResult::InvalidInput;
|
||||
}
|
||||
|
||||
let ciphertext = unsafe { std::slice::from_raw_parts(ciphertext, ciphertext_len) };
|
||||
|
||||
let ssh_key_str = match unsafe { cstr_to_str(ssh_key) } {
|
||||
Ok(s) => s,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// Parse SSH identity from buffer
|
||||
let buf_reader = BufReader::new(ssh_key_str.as_bytes());
|
||||
let identity = match ssh::Identity::from_buffer(buf_reader, None) {
|
||||
Ok(id) => id,
|
||||
Err(_) => return AgeResult::SshKeyError,
|
||||
};
|
||||
|
||||
// Handle encrypted SSH keys - keep as ssh::Identity since it implements age::Identity
|
||||
let identity: ssh::Identity = match identity {
|
||||
ssh::Identity::Unencrypted(_) => identity,
|
||||
ssh::Identity::Encrypted(enc) => {
|
||||
let passphrase_str = if passphrase.is_null() {
|
||||
return AgeResult::PassphraseRequired;
|
||||
} else {
|
||||
match unsafe { cstr_to_str(passphrase) } {
|
||||
Ok(s) if !s.is_empty() => s,
|
||||
_ => return AgeResult::PassphraseRequired,
|
||||
}
|
||||
};
|
||||
|
||||
match enc.decrypt(age::secrecy::SecretString::from(passphrase_str.to_string())) {
|
||||
Ok(id) => ssh::Identity::Unencrypted(id),
|
||||
Err(_) => return AgeResult::InvalidPassphrase,
|
||||
}
|
||||
}
|
||||
ssh::Identity::Unsupported(_) => return AgeResult::UnsupportedKey,
|
||||
};
|
||||
|
||||
let decryptor = match age::Decryptor::new(ciphertext) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
let mut decrypted = Vec::new();
|
||||
let mut reader = match decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity)) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
if reader.read_to_end(&mut decrypted).is_err() {
|
||||
return AgeResult::DecryptionFailed;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*output = AgeBuffer::from_vec(decrypted);
|
||||
}
|
||||
|
||||
AgeResult::Success
|
||||
}
|
||||
|
||||
/// Decrypt data using an SSH private key file.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ciphertext` - Pointer to the encrypted data
|
||||
/// * `ciphertext_len` - Length of the ciphertext
|
||||
/// * `ssh_key_path` - Path to the SSH private key file
|
||||
/// * `passphrase` - Optional passphrase for encrypted SSH keys (can be null)
|
||||
/// * `output` - Pointer to receive the decrypted buffer
|
||||
///
|
||||
/// # Returns
|
||||
/// AgeResult indicating success or failure
|
||||
#[no_mangle]
|
||||
pub extern "C" fn age_decrypt_ssh_file(
|
||||
ciphertext: *const u8,
|
||||
ciphertext_len: usize,
|
||||
ssh_key_path: *const c_char,
|
||||
passphrase: *const c_char,
|
||||
output: *mut AgeBuffer,
|
||||
) -> AgeResult {
|
||||
if ciphertext.is_null() || output.is_null() {
|
||||
return AgeResult::InvalidInput;
|
||||
}
|
||||
|
||||
let ciphertext = unsafe { std::slice::from_raw_parts(ciphertext, ciphertext_len) };
|
||||
|
||||
let path_str = match unsafe { cstr_to_str(ssh_key_path) } {
|
||||
Ok(s) => s,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// The filename is passed as a hint for error messages
|
||||
let filename = Some(path_str.to_string());
|
||||
|
||||
// Read and parse SSH key file
|
||||
let ssh_key_data = match std::fs::read(path_str) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return AgeResult::IoError,
|
||||
};
|
||||
|
||||
let buf_reader = BufReader::new(ssh_key_data.as_slice());
|
||||
let identity = match ssh::Identity::from_buffer(buf_reader, filename) {
|
||||
Ok(id) => id,
|
||||
Err(_) => return AgeResult::SshKeyError,
|
||||
};
|
||||
|
||||
// Handle encrypted SSH keys - keep as ssh::Identity since it implements age::Identity
|
||||
let identity: ssh::Identity = match identity {
|
||||
ssh::Identity::Unencrypted(_) => identity,
|
||||
ssh::Identity::Encrypted(enc) => {
|
||||
// Parse passphrase if provided
|
||||
let passphrase_str = if passphrase.is_null() {
|
||||
return AgeResult::PassphraseRequired;
|
||||
} else {
|
||||
match unsafe { cstr_to_str(passphrase) } {
|
||||
Ok(s) if !s.is_empty() => s,
|
||||
_ => return AgeResult::PassphraseRequired,
|
||||
}
|
||||
};
|
||||
|
||||
match enc.decrypt(age::secrecy::SecretString::from(passphrase_str.to_string())) {
|
||||
Ok(id) => ssh::Identity::Unencrypted(id),
|
||||
Err(_) => return AgeResult::InvalidPassphrase,
|
||||
}
|
||||
}
|
||||
ssh::Identity::Unsupported(_) => return AgeResult::UnsupportedKey,
|
||||
};
|
||||
|
||||
let decryptor = match age::Decryptor::new(ciphertext) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
let mut decrypted = Vec::new();
|
||||
let mut reader = match decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity)) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return AgeResult::DecryptionFailed,
|
||||
};
|
||||
|
||||
if reader.read_to_end(&mut decrypted).is_err() {
|
||||
return AgeResult::DecryptionFailed;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*output = AgeBuffer::from_vec(decrypted);
|
||||
}
|
||||
|
||||
AgeResult::Success
|
||||
}
|
||||
Reference in New Issue
Block a user