mirror of
https://github.com/chatmail/core.git
synced 2026-05-03 13:26:28 +03:00
use rpgp directly
This commit is contained in:
90
src/pgp/c_vec.rs
Normal file
90
src/pgp/c_vec.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::slice;
|
||||
|
||||
/// Represents a vector, that can be passed to C land.
|
||||
/// Has to be deallocated using [rpgp_cvec_drop], otherwise leaks memory.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct cvec {
|
||||
data: *mut u8,
|
||||
len: libc::size_t,
|
||||
}
|
||||
|
||||
impl PartialEq for cvec {
|
||||
fn eq(&self, other: &cvec) -> bool {
|
||||
if self.len != other.len {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
slice::from_raw_parts(self.data, self.len)
|
||||
== slice::from_raw_parts(other.data, other.len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for cvec {}
|
||||
|
||||
impl Into<cvec> for Vec<u8> {
|
||||
fn into(mut self) -> cvec {
|
||||
self.shrink_to_fit();
|
||||
assert!(self.len() == self.capacity());
|
||||
|
||||
let res = cvec {
|
||||
data: self.as_mut_ptr(),
|
||||
len: self.len() as libc::size_t,
|
||||
};
|
||||
|
||||
// prevent deallocation in Rust
|
||||
std::mem::forget(self);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for cvec {
|
||||
fn into(self) -> Vec<u8> {
|
||||
unsafe { Vec::from_raw_parts(self.data, self.len, self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of the data of the given [cvec].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_cvec_len(cvec_ptr: *mut cvec) -> libc::size_t {
|
||||
assert!(!cvec_ptr.is_null());
|
||||
|
||||
let cvec = &*cvec_ptr;
|
||||
cvec.len
|
||||
}
|
||||
|
||||
/// Get a pointer to the data of the given [cvec].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_cvec_data(cvec_ptr: *mut cvec) -> *const u8 {
|
||||
assert!(!cvec_ptr.is_null());
|
||||
|
||||
let cvec = &*cvec_ptr;
|
||||
cvec.data
|
||||
}
|
||||
|
||||
/// Free the given [cvec].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_cvec_drop(cvec_ptr: *mut cvec) {
|
||||
assert!(!cvec_ptr.is_null());
|
||||
|
||||
let v = &*cvec_ptr;
|
||||
let _ = Vec::from_raw_parts(v.data, v.len, v.len);
|
||||
// Drop
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cvec() {
|
||||
for i in 0..100 {
|
||||
let a = vec![i as u8; i * 10];
|
||||
let b: cvec = a.clone().into();
|
||||
let c: Vec<u8> = b.into();
|
||||
assert_eq!(a, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/pgp/errors.rs
Normal file
66
src/pgp/errors.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use failure::Error;
|
||||
use libc::{c_char, c_int};
|
||||
|
||||
thread_local! {
|
||||
static LAST_ERROR: RefCell<Option<Box<Error>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Update the most recent error, clearing whatever may have been there before.
|
||||
pub fn update_last_error(err: Error) {
|
||||
{
|
||||
// Print a pseudo-backtrace for this error, following back each error's
|
||||
// cause until we reach the root error.
|
||||
let mut cause = err.as_fail().cause();
|
||||
while let Some(parent_err) = cause {
|
||||
cause = parent_err.cause();
|
||||
}
|
||||
}
|
||||
|
||||
LAST_ERROR.with(|prev| {
|
||||
*prev.borrow_mut() = Some(Box::new(err));
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieve the most recent error, clearing it in the process.
|
||||
pub fn take_last_error() -> Option<Box<Error>> {
|
||||
LAST_ERROR.with(|prev| prev.borrow_mut().take())
|
||||
}
|
||||
|
||||
/// Calculate the number of bytes in the last error's error message **not**
|
||||
/// including any trailing `null` characters.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rpgp_last_error_length() -> c_int {
|
||||
LAST_ERROR.with(|prev| match *prev.borrow() {
|
||||
Some(ref err) => err.to_string().len() as c_int + 1,
|
||||
None => 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the most recent error message into a caller-provided buffer as a UTF-8
|
||||
/// string, returning the number of bytes written.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This writes a **UTF-8** string into the buffer. Windows users may need to
|
||||
/// convert it to a UTF-16 "unicode" afterwards.
|
||||
///
|
||||
/// If there are no recent errors then this returns `0` (because we wrote 0
|
||||
/// bytes). `-1` is returned if there are any errors, for example when passed a
|
||||
/// null pointer or a buffer of insufficient size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_last_error_message() -> *mut c_char {
|
||||
let last_error = match take_last_error() {
|
||||
Some(err) => err,
|
||||
None => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
let error_message = last_error.to_string();
|
||||
|
||||
CString::new(error_message)
|
||||
.expect("CString alloc failed")
|
||||
.into_raw()
|
||||
}
|
||||
19
src/pgp/hash.rs
Normal file
19
src/pgp/hash.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::slice;
|
||||
|
||||
use crate::pgp::cvec;
|
||||
|
||||
/// Calculate the SHA256 hash of the given bytes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_hash_sha256(
|
||||
bytes_ptr: *const u8,
|
||||
bytes_len: libc::size_t,
|
||||
) -> *mut cvec {
|
||||
assert!(!bytes_ptr.is_null());
|
||||
assert!(bytes_len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(bytes_ptr, bytes_len);
|
||||
let result = Sha256::digest(bytes);
|
||||
|
||||
Box::into_raw(Box::new(result.to_vec().into()))
|
||||
}
|
||||
151
src/pgp/key.rs
Normal file
151
src/pgp/key.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use std::ffi::CString;
|
||||
use std::io::Cursor;
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
use pgp::composed::{from_armor_many, from_bytes_many, PublicOrSecret};
|
||||
use pgp::types::KeyTrait;
|
||||
|
||||
use crate::pgp::cvec;
|
||||
|
||||
pub type public_or_secret_key = PublicOrSecret;
|
||||
|
||||
/// Creates an in-memory representation of a PGP key, based on the armor file given.
|
||||
/// The returned pointer should be stored, and reused when calling methods "on" this key.
|
||||
/// When done with it [rpgp_key_drop] should be called, to free the memory.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_from_armor(
|
||||
raw: *const u8,
|
||||
len: libc::size_t,
|
||||
) -> *mut public_or_secret_key {
|
||||
assert!(!raw.is_null());
|
||||
assert!(len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(raw, len);
|
||||
let (mut keys, _headers) = try_ffi!(from_armor_many(Cursor::new(bytes)), "failed to parse");
|
||||
|
||||
let key = try_ffi!(
|
||||
try_ffi!(
|
||||
keys.nth(0).ok_or_else(|| format_err!("no valid key found")),
|
||||
"failed to parse key"
|
||||
),
|
||||
"failed to parse key"
|
||||
);
|
||||
|
||||
try_ffi!(key.verify(), "failed to verify key");
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
/// Creates an in-memory representation of a PGP key, based on the serialized bytes given.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_from_bytes(
|
||||
raw: *const u8,
|
||||
len: libc::size_t,
|
||||
) -> *mut public_or_secret_key {
|
||||
assert!(!raw.is_null());
|
||||
assert!(len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(raw, len);
|
||||
let mut keys = from_bytes_many(Cursor::new(bytes));
|
||||
|
||||
let key = try_ffi!(
|
||||
try_ffi!(
|
||||
keys.nth(0).ok_or_else(|| format_err!("no valid key found")),
|
||||
"failed to parse key"
|
||||
),
|
||||
"failed to parse key"
|
||||
);
|
||||
|
||||
try_ffi!(key.verify(), "failed to verify key");
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
/// Returns the KeyID for the passed in key. The caller is responsible to call [rpgp_string_drop] with the returned memory, to free it.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_id(key_ptr: *mut public_or_secret_key) -> *mut c_char {
|
||||
assert!(!key_ptr.is_null());
|
||||
|
||||
let key = &*key_ptr;
|
||||
let id = try_ffi!(
|
||||
CString::new(hex::encode(&key.key_id())),
|
||||
"failed to allocate string"
|
||||
);
|
||||
|
||||
id.into_raw()
|
||||
}
|
||||
|
||||
/// Returns the Fingerprint for the passed in key. The caller is responsible to call [rpgp_cvec_drop] with the returned memory, to free it.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_fingerprint(key_ptr: *mut public_or_secret_key) -> *mut cvec {
|
||||
assert!(!key_ptr.is_null());
|
||||
|
||||
let key = &*key_ptr;
|
||||
let fingerprint = key.fingerprint();
|
||||
|
||||
Box::into_raw(Box::new(fingerprint.into()))
|
||||
}
|
||||
|
||||
/// Returns `true` if this key is a public key, false otherwise.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_is_public(key_ptr: *mut public_or_secret_key) -> bool {
|
||||
assert!(!key_ptr.is_null());
|
||||
|
||||
(&*key_ptr).is_public()
|
||||
}
|
||||
|
||||
/// Returns `true` if this key is a secret key, false otherwise.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_is_secret(key_ptr: *mut public_or_secret_key) -> bool {
|
||||
assert!(!key_ptr.is_null());
|
||||
|
||||
(&*key_ptr).is_secret()
|
||||
}
|
||||
|
||||
/// Frees the memory of the passed in key, making the pointer invalid after this method was called.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_key_drop(key_ptr: *mut public_or_secret_key) {
|
||||
assert!(!key_ptr.is_null());
|
||||
|
||||
let _key = &*key_ptr;
|
||||
// Drop
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
use crate::pgp::{
|
||||
rpgp_create_x25519_skey, rpgp_cvec_data, rpgp_cvec_drop, rpgp_cvec_len, rpgp_skey_to_bytes,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_fingerprint() {
|
||||
let user_id = CStr::from_bytes_with_nul(b"<hello@world.com>\0").unwrap();
|
||||
|
||||
unsafe {
|
||||
// Create the actual key
|
||||
let skey = rpgp_create_x25519_skey(user_id.as_ptr());
|
||||
|
||||
// Serialize secret key into bytes
|
||||
let skey_bytes = rpgp_skey_to_bytes(skey);
|
||||
|
||||
let key = rpgp_key_from_bytes(rpgp_cvec_data(skey_bytes), rpgp_cvec_len(skey_bytes));
|
||||
assert!(rpgp_key_is_secret(key));
|
||||
|
||||
let fingerprint1 = rpgp_key_fingerprint(key);
|
||||
|
||||
// get fingerprint directly
|
||||
let mut fingerprint2: cvec = (&*skey).fingerprint().into();
|
||||
|
||||
assert_eq!(*fingerprint1, fingerprint2);
|
||||
|
||||
// cleanup
|
||||
rpgp_cvec_drop(skey_bytes);
|
||||
rpgp_cvec_drop(fingerprint1);
|
||||
rpgp_cvec_drop(&mut fingerprint2);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/pgp/macros.rs
Normal file
12
src/pgp/macros.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#[macro_export]
|
||||
macro_rules! try_ffi {
|
||||
($e:expr, $fmt:expr) => {
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
$crate::pgp::errors::update_last_error(err.into());
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
364
src/pgp/message.rs
Normal file
364
src/pgp/message.rs
Normal file
@@ -0,0 +1,364 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::Cursor;
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||
use pgp::types::{CompressionAlgorithm, KeyTrait, StringToKey};
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::pgp::{cvec, signed_public_key, signed_secret_key, update_last_error};
|
||||
pub use pgp::composed::Message;
|
||||
pub type message = Message;
|
||||
|
||||
/// Parse an armored message.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_from_armor(
|
||||
msg_ptr: *const u8,
|
||||
msg_len: libc::size_t,
|
||||
) -> *mut message {
|
||||
assert!(!msg_ptr.is_null());
|
||||
assert!(msg_len > 0);
|
||||
|
||||
let enc_msg = slice::from_raw_parts(msg_ptr, msg_len);
|
||||
|
||||
let (msg, _headers) = try_ffi!(
|
||||
Message::from_armor_single(Cursor::new(enc_msg)),
|
||||
"invalid message"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(msg))
|
||||
}
|
||||
|
||||
/// Parse a message in bytes format.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_from_bytes(
|
||||
msg_ptr: *const u8,
|
||||
msg_len: libc::size_t,
|
||||
) -> *mut message {
|
||||
assert!(!msg_ptr.is_null());
|
||||
assert!(msg_len > 0);
|
||||
|
||||
let enc_msg = slice::from_raw_parts(msg_ptr, msg_len);
|
||||
|
||||
let msg = try_ffi!(Message::from_bytes(Cursor::new(enc_msg)), "invalid message");
|
||||
|
||||
Box::into_raw(Box::new(msg))
|
||||
}
|
||||
|
||||
/// Decrypt the passed in message, using a password.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_decrypt_with_password(
|
||||
msg_ptr: *const message,
|
||||
password_ptr: *const c_char,
|
||||
) -> *mut message {
|
||||
assert!(!msg_ptr.is_null());
|
||||
assert!(!password_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
let password = CStr::from_ptr(password_ptr);
|
||||
let password_str = try_ffi!(password.to_str(), "invalid password");
|
||||
let mut decryptor = try_ffi!(
|
||||
msg.decrypt_with_password(|| password_str.into()),
|
||||
"failed to decrypt message"
|
||||
);
|
||||
let decrypted_msg = try_ffi!(
|
||||
try_ffi!(
|
||||
decryptor.next().ok_or_else(|| format_err!("")),
|
||||
"no message found"
|
||||
),
|
||||
"failed to decrypt message"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(decrypted_msg))
|
||||
}
|
||||
|
||||
/// Decrypt the passed in message, without attempting to use a password.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_decrypt_no_pw(
|
||||
msg_ptr: *const message,
|
||||
skeys_ptr: *const *const signed_secret_key,
|
||||
skeys_len: libc::size_t,
|
||||
pkeys_ptr: *const *const signed_public_key,
|
||||
pkeys_len: libc::size_t,
|
||||
) -> *mut message_decrypt_result {
|
||||
assert!(!msg_ptr.is_null());
|
||||
assert!(!skeys_ptr.is_null());
|
||||
assert!(skeys_len > 0);
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
let skeys_raw = slice::from_raw_parts(skeys_ptr, skeys_len);
|
||||
let skeys = skeys_raw
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let v: &SignedSecretKey = &**k;
|
||||
v
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pkeys = if pkeys_ptr.is_null() || pkeys_len == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(slice::from_raw_parts(pkeys_ptr, pkeys_len))
|
||||
};
|
||||
|
||||
let (mut decryptor, _) = try_ffi!(
|
||||
msg.decrypt(|| "".into(), || "".into(), &skeys[..]),
|
||||
"failed to decrypt message"
|
||||
);
|
||||
|
||||
// TODO: how to handle the case when we detect multiple messages?
|
||||
let dec_msg = try_ffi!(
|
||||
try_ffi!(
|
||||
decryptor.next().ok_or_else(|| format_err!("no message")),
|
||||
"no message found"
|
||||
),
|
||||
"failed to decrypt message"
|
||||
);
|
||||
|
||||
let (valid_ids_ptr, valid_ids_len) = if let Some(pkeys) = pkeys {
|
||||
let mut valid_ids = pkeys
|
||||
.iter()
|
||||
.filter_map(|pkey| match dec_msg.verify(&(**pkey).primary_key) {
|
||||
Ok(_) => Some(
|
||||
CString::new(hex::encode_upper(&(&**pkey).fingerprint()))
|
||||
.expect("failed to allocate")
|
||||
.into_raw(),
|
||||
),
|
||||
Err(_) => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
valid_ids.shrink_to_fit();
|
||||
let res = (valid_ids.as_mut_ptr(), valid_ids.len());
|
||||
std::mem::forget(valid_ids);
|
||||
res
|
||||
} else {
|
||||
(std::ptr::null_mut(), 0)
|
||||
};
|
||||
|
||||
Box::into_raw(Box::new(message_decrypt_result {
|
||||
message_ptr: Box::into_raw(Box::new(dec_msg)),
|
||||
valid_ids_ptr,
|
||||
valid_ids_len,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Message decryption result.
|
||||
#[repr(C)]
|
||||
pub struct message_decrypt_result {
|
||||
/// A pointer to the decrypted message.
|
||||
pub message_ptr: *mut message,
|
||||
/// Pointer to a list of fingerprints which verified the signature.
|
||||
pub valid_ids_ptr: *mut *mut c_char,
|
||||
pub valid_ids_len: libc::size_t,
|
||||
}
|
||||
|
||||
/// Free a [message_decrypt_result].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_message_decrypt_result_drop(res_ptr: *mut message_decrypt_result) {
|
||||
assert!(!res_ptr.is_null());
|
||||
|
||||
let res = &*res_ptr;
|
||||
let _msg = &*res.message_ptr;
|
||||
let _ids = Vec::from_raw_parts(res.valid_ids_ptr, res.valid_ids_len, res.valid_ids_len);
|
||||
// Drop
|
||||
}
|
||||
|
||||
/// Returns the underlying data of the given message.
|
||||
/// Fails when the message is encrypted. Decompresses compressed messages.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_to_bytes(msg_ptr: *const message) -> *mut cvec {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
|
||||
let result = try_ffi!(msg.get_content(), "failed to extract content");
|
||||
match result {
|
||||
Some(data) => Box::into_raw(Box::new(data.into())),
|
||||
None => {
|
||||
update_last_error(format_err!("called on encrypted message").into());
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the message into its ascii armored representation.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_to_armored(msg_ptr: *const message) -> *mut cvec {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
|
||||
let result = try_ffi!(
|
||||
msg.to_armored_bytes(None),
|
||||
"failed to encode message to ASCII Armor"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(result.into()))
|
||||
}
|
||||
|
||||
/// Encodes the message into its ascii armored representation, returning a string.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_to_armored_str(msg_ptr: *const message) -> *mut c_char {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
|
||||
let result = try_ffi!(
|
||||
msg.to_armored_string(None),
|
||||
"failed to encode message to ASCII Armor"
|
||||
);
|
||||
|
||||
CString::new(result).expect("allocation failed").into_raw()
|
||||
}
|
||||
|
||||
/// Free a [message], that was created by rpgp.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_drop(msg_ptr: *mut message) {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let _ = &*msg_ptr;
|
||||
// Drop
|
||||
}
|
||||
|
||||
/// Get the number of fingerprints of a given encrypted message.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_recipients_len(msg_ptr: *mut message) -> u32 {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
|
||||
let list = msg.get_recipients();
|
||||
|
||||
list.len() as u32
|
||||
}
|
||||
|
||||
/// Get the fingerprint of a given encrypted message, by index, in hexformat.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_msg_recipients_get(msg_ptr: *mut message, i: u32) -> *mut c_char {
|
||||
assert!(!msg_ptr.is_null());
|
||||
|
||||
let msg = &*msg_ptr;
|
||||
|
||||
let list = msg.get_recipients();
|
||||
if (i as usize) < list.len() {
|
||||
CString::new(hex::encode(&list[i as usize]))
|
||||
.expect("allocation failure")
|
||||
.into_raw()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_encrypt_bytes_to_keys(
|
||||
bytes_ptr: *const u8,
|
||||
bytes_len: libc::size_t,
|
||||
pkeys_ptr: *const *const signed_public_key,
|
||||
pkeys_len: libc::size_t,
|
||||
) -> *mut message {
|
||||
assert!(!bytes_ptr.is_null());
|
||||
assert!(bytes_len > 0);
|
||||
assert!(!pkeys_ptr.is_null());
|
||||
assert!(pkeys_len > 0);
|
||||
|
||||
let pkeys_raw = slice::from_raw_parts(pkeys_ptr, pkeys_len);
|
||||
let pkeys = pkeys_raw
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let v: &SignedPublicKey = &**k;
|
||||
v
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let bytes = slice::from_raw_parts(bytes_ptr, bytes_len);
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let lit_msg = Message::new_literal_bytes("", bytes);
|
||||
|
||||
let msg = try_ffi!(
|
||||
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys),
|
||||
"failed to encrypt"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(msg))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_sign_encrypt_bytes_to_keys(
|
||||
bytes_ptr: *const u8,
|
||||
bytes_len: libc::size_t,
|
||||
pkeys_ptr: *const *const signed_public_key,
|
||||
pkeys_len: libc::size_t,
|
||||
skey_ptr: *const signed_secret_key,
|
||||
) -> *mut message {
|
||||
assert!(!bytes_ptr.is_null());
|
||||
assert!(bytes_len > 0);
|
||||
assert!(!pkeys_ptr.is_null());
|
||||
assert!(pkeys_len > 0);
|
||||
assert!(!skey_ptr.is_null());
|
||||
|
||||
let pkeys_raw = slice::from_raw_parts(pkeys_ptr, pkeys_len);
|
||||
let pkeys = pkeys_raw
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let v: &SignedPublicKey = &**k;
|
||||
v
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let skey = &*skey_ptr;
|
||||
|
||||
let bytes = slice::from_raw_parts(bytes_ptr, bytes_len);
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let lit_msg = Message::new_literal_bytes("", bytes);
|
||||
let signed_msg = try_ffi!(
|
||||
lit_msg.sign(&skey, || "".into(), Default::default()),
|
||||
"failed to sign"
|
||||
);
|
||||
|
||||
let compressed_msg = try_ffi!(
|
||||
signed_msg.compress(CompressionAlgorithm::ZLIB),
|
||||
"failed to compress"
|
||||
);
|
||||
|
||||
let encrypted_msg = try_ffi!(
|
||||
compressed_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys),
|
||||
"failed to encrypt"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(encrypted_msg))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_encrypt_bytes_with_password(
|
||||
bytes_ptr: *const u8,
|
||||
bytes_len: libc::size_t,
|
||||
password_ptr: *const c_char,
|
||||
) -> *mut message {
|
||||
assert!(!bytes_ptr.is_null());
|
||||
assert!(!password_ptr.is_null());
|
||||
assert!(bytes_len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(bytes_ptr, bytes_len);
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let lit_msg = Message::new_literal_bytes("", bytes);
|
||||
|
||||
let password = CStr::from_ptr(password_ptr);
|
||||
let password_str = try_ffi!(password.to_str(), "invalid password");
|
||||
|
||||
let s2k = StringToKey::new_default(&mut rng);
|
||||
|
||||
let msg = try_ffi!(
|
||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || {
|
||||
password_str.into()
|
||||
}),
|
||||
"failed to encrypt"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(msg))
|
||||
}
|
||||
25
src/pgp/mod.rs
Normal file
25
src/pgp/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod c_vec;
|
||||
mod errors;
|
||||
mod hash;
|
||||
mod key;
|
||||
mod message;
|
||||
mod public_key;
|
||||
mod secret_key;
|
||||
|
||||
pub use self::c_vec::*;
|
||||
pub use self::errors::*;
|
||||
pub use self::hash::*;
|
||||
pub use self::key::*;
|
||||
pub use self::message::*;
|
||||
pub use self::public_key::*;
|
||||
pub use self::secret_key::*;
|
||||
|
||||
/// Free string, that was created by rpgp.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_string_drop(p: *mut libc::c_char) {
|
||||
let _ = std::ffi::CString::from_raw(p);
|
||||
// Drop
|
||||
}
|
||||
68
src/pgp/public_key.rs
Normal file
68
src/pgp/public_key.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::ffi::CString;
|
||||
use std::io::Cursor;
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
use pgp::composed::{Deserializable, SignedPublicKey};
|
||||
use pgp::ser::Serialize;
|
||||
use pgp::types::KeyTrait;
|
||||
|
||||
use crate::pgp::cvec;
|
||||
|
||||
pub type signed_public_key = SignedPublicKey;
|
||||
|
||||
/// Parse a serialized public key, into the native rPGP memory representation.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_pkey_from_bytes(
|
||||
raw: *const u8,
|
||||
len: libc::size_t,
|
||||
) -> *mut signed_public_key {
|
||||
assert!(!raw.is_null());
|
||||
assert!(len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(raw, len);
|
||||
let key = try_ffi!(
|
||||
SignedPublicKey::from_bytes(Cursor::new(bytes)),
|
||||
"invalid public key"
|
||||
);
|
||||
|
||||
try_ffi!(key.verify(), "failed to verify key");
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
/// Serialize the [signed_public_key] to bytes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_pkey_to_bytes(pkey_ptr: *mut signed_public_key) -> *mut cvec {
|
||||
assert!(!pkey_ptr.is_null());
|
||||
|
||||
let pkey = &*pkey_ptr;
|
||||
|
||||
let mut res = Vec::new();
|
||||
try_ffi!(pkey.to_writer(&mut res), "failed to serialize key");
|
||||
|
||||
Box::into_raw(Box::new(res.into()))
|
||||
}
|
||||
|
||||
/// Get the key id of the given [signed_public_key].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_pkey_key_id(pkey_ptr: *mut signed_public_key) -> *mut c_char {
|
||||
assert!(!pkey_ptr.is_null());
|
||||
|
||||
let pkey = &*pkey_ptr;
|
||||
let id = try_ffi!(
|
||||
CString::new(hex::encode(&pkey.key_id())),
|
||||
"failed to allocate string"
|
||||
);
|
||||
|
||||
id.into_raw()
|
||||
}
|
||||
|
||||
/// Free the given [signed_public_key].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_pkey_drop(pkey_ptr: *mut signed_public_key) {
|
||||
assert!(!pkey_ptr.is_null());
|
||||
|
||||
let _pkey = &*pkey_ptr;
|
||||
// Drop
|
||||
}
|
||||
207
src/pgp/secret_key.rs
Normal file
207
src/pgp/secret_key.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::Cursor;
|
||||
use std::slice;
|
||||
|
||||
use libc::c_char;
|
||||
use pgp::composed::{
|
||||
Deserializable, KeyType, SecretKeyParamsBuilder, SignedSecretKey, SubkeyParamsBuilder,
|
||||
};
|
||||
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
|
||||
use pgp::errors::Result;
|
||||
use pgp::ser::Serialize;
|
||||
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait};
|
||||
use smallvec::smallvec;
|
||||
|
||||
use crate::pgp::cvec;
|
||||
use crate::pgp::signed_public_key;
|
||||
|
||||
pub type signed_secret_key = SignedSecretKey;
|
||||
|
||||
/// Generates a new RSA key.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_create_rsa_skey(
|
||||
bits: u32,
|
||||
user_id: *const c_char,
|
||||
) -> *mut signed_secret_key {
|
||||
assert!(!user_id.is_null());
|
||||
|
||||
let user_id = CStr::from_ptr(user_id);
|
||||
let user_id_str = try_ffi!(user_id.to_str(), "invalid user id");
|
||||
|
||||
let key = try_ffi!(
|
||||
create_key(KeyType::Rsa(bits), KeyType::Rsa(bits), user_id_str),
|
||||
"failed to generate key"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
/// Generates a new x25519 key.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_create_x25519_skey(user_id: *const c_char) -> *mut signed_secret_key {
|
||||
assert!(!user_id.is_null());
|
||||
|
||||
let user_id = CStr::from_ptr(user_id);
|
||||
let user_id_str = try_ffi!(user_id.to_str(), "invalid user id");
|
||||
let key = try_ffi!(
|
||||
create_key(KeyType::EdDSA, KeyType::ECDH, user_id_str),
|
||||
"failed to generate key"
|
||||
);
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
/// Serialize a secret key into its byte representation.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_skey_to_bytes(skey_ptr: *mut signed_secret_key) -> *mut cvec {
|
||||
assert!(!skey_ptr.is_null());
|
||||
|
||||
let skey = &*skey_ptr;
|
||||
|
||||
let mut res = Vec::new();
|
||||
try_ffi!(skey.to_writer(&mut res), "failed to serialize key");
|
||||
|
||||
Box::into_raw(Box::new(res.into()))
|
||||
}
|
||||
|
||||
/// Get the signed public key matching the given private key. Only works for non password protected keys.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_skey_public_key(
|
||||
skey_ptr: *mut signed_secret_key,
|
||||
) -> *mut signed_public_key {
|
||||
assert!(!skey_ptr.is_null());
|
||||
|
||||
let skey = &*skey_ptr;
|
||||
|
||||
let pkey = skey.public_key();
|
||||
let signed_pkey = try_ffi!(pkey.sign(&skey, || "".into()), "failed to sign key");
|
||||
|
||||
Box::into_raw(Box::new(signed_pkey))
|
||||
}
|
||||
|
||||
/// Returns the KeyID for the passed in key.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_skey_key_id(skey_ptr: *mut signed_secret_key) -> *mut c_char {
|
||||
assert!(!skey_ptr.is_null());
|
||||
|
||||
let key = &*skey_ptr;
|
||||
let id = try_ffi!(
|
||||
CString::new(hex::encode(&key.key_id())),
|
||||
"failed to allocate string"
|
||||
);
|
||||
|
||||
id.into_raw()
|
||||
}
|
||||
|
||||
/// Free the memory of a secret key.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_skey_drop(skey_ptr: *mut signed_secret_key) {
|
||||
assert!(!skey_ptr.is_null());
|
||||
|
||||
let _skey = &*skey_ptr;
|
||||
// Drop
|
||||
}
|
||||
|
||||
/// Creates an in-memory representation of a Secret PGP key, based on the serialized bytes given.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpgp_skey_from_bytes(
|
||||
raw: *const u8,
|
||||
len: libc::size_t,
|
||||
) -> *mut signed_secret_key {
|
||||
assert!(!raw.is_null());
|
||||
assert!(len > 0);
|
||||
|
||||
let bytes = slice::from_raw_parts(raw, len);
|
||||
let key = try_ffi!(
|
||||
SignedSecretKey::from_bytes(Cursor::new(bytes)),
|
||||
"invalid secret key"
|
||||
);
|
||||
try_ffi!(key.verify(), "failed to verify key");
|
||||
|
||||
Box::into_raw(Box::new(key))
|
||||
}
|
||||
|
||||
fn create_key(typ: KeyType, sub_typ: KeyType, user_id: &str) -> Result<SignedSecretKey> {
|
||||
let key_params = SecretKeyParamsBuilder::default()
|
||||
.key_type(typ)
|
||||
.can_create_certificates(true)
|
||||
.can_sign(true)
|
||||
.primary_user_id(user_id.into())
|
||||
.passphrase(None)
|
||||
.preferred_symmetric_algorithms(smallvec![
|
||||
SymmetricKeyAlgorithm::AES256,
|
||||
SymmetricKeyAlgorithm::AES192,
|
||||
SymmetricKeyAlgorithm::AES128,
|
||||
])
|
||||
.preferred_hash_algorithms(smallvec![
|
||||
HashAlgorithm::SHA2_256,
|
||||
HashAlgorithm::SHA2_384,
|
||||
HashAlgorithm::SHA2_512,
|
||||
HashAlgorithm::SHA2_224,
|
||||
HashAlgorithm::SHA1,
|
||||
])
|
||||
.preferred_compression_algorithms(smallvec![
|
||||
CompressionAlgorithm::ZLIB,
|
||||
CompressionAlgorithm::ZIP,
|
||||
])
|
||||
.subkey(
|
||||
SubkeyParamsBuilder::default()
|
||||
.key_type(sub_typ)
|
||||
.can_encrypt(true)
|
||||
.passphrase(None)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let key = key_params.generate()?;
|
||||
|
||||
key.sign(|| "".into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::pgp::*;
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
|
||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||
|
||||
#[test]
|
||||
fn test_keygen_rsa() {
|
||||
let user_id = CStr::from_bytes_with_nul(b"<hello@world.com>\0").unwrap();
|
||||
|
||||
unsafe {
|
||||
/* Create the actual key */
|
||||
let skey = rpgp_create_rsa_skey(2048, user_id.as_ptr());
|
||||
|
||||
/* Serialize secret key into bytes */
|
||||
let skey_bytes = rpgp_skey_to_bytes(skey);
|
||||
|
||||
/* Get the public key */
|
||||
let pkey = rpgp_skey_public_key(skey);
|
||||
|
||||
/* Serialize public key into bytes */
|
||||
let pkey_bytes = rpgp_pkey_to_bytes(pkey);
|
||||
|
||||
let skey_bytes_vec =
|
||||
slice::from_raw_parts(rpgp_cvec_data(skey_bytes), rpgp_cvec_len(skey_bytes));
|
||||
let skey_back =
|
||||
SignedSecretKey::from_bytes(skey_bytes_vec).expect("invalid secret key");
|
||||
assert_eq!(&*skey, &skey_back);
|
||||
|
||||
let pkey_bytes_vec =
|
||||
slice::from_raw_parts(rpgp_cvec_data(pkey_bytes), rpgp_cvec_len(pkey_bytes));
|
||||
let pkey_back =
|
||||
SignedPublicKey::from_bytes(pkey_bytes_vec).expect("invalid public key");
|
||||
assert_eq!(&*pkey, &pkey_back);
|
||||
|
||||
/* cleanup */
|
||||
rpgp_skey_drop(skey);
|
||||
rpgp_cvec_drop(skey_bytes);
|
||||
rpgp_pkey_drop(pkey);
|
||||
rpgp_cvec_drop(pkey_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user