From 4d0a4f527e361ad29c082331e73dd8ed6750ffbd Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 8 Jan 2019 15:09:57 +0100 Subject: ffi: Move TPK glue and friends to a new module. --- ffi/src/openpgp/mod.rs | 707 +---------------------------------------------- ffi/src/openpgp/tpk.rs | 729 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 731 insertions(+), 705 deletions(-) create mode 100644 ffi/src/openpgp/tpk.rs (limited to 'ffi') diff --git a/ffi/src/openpgp/mod.rs b/ffi/src/openpgp/mod.rs index 6f52484e..05afe9c0 100644 --- a/ffi/src/openpgp/mod.rs +++ b/ffi/src/openpgp/mod.rs @@ -1,7 +1,7 @@ //! XXX use failure; -use std::ffi::{CString, CStr}; +use std::ffi::CStr; use std::mem::forget; use std::ptr; use std::slice; @@ -17,7 +17,6 @@ use self::openpgp::{ Fingerprint, KeyID, RevocationStatus, - PacketPile, TPK, TSK, Packet, @@ -30,13 +29,6 @@ use self::openpgp::{ }, crypto::Password, }; -use self::openpgp::tpk::{ - CipherSuite, - TPKBuilder, - UserIDBinding, - UserIDBindingIter, - KeyIter -}; use self::openpgp::packet; use self::openpgp::parse::{ Parse, @@ -56,7 +48,6 @@ use self::openpgp::parse::stream::{ use self::openpgp::serialize::Serialize; use self::openpgp::constants::{ DataFormat, - ReasonForRevocation, }; use super::error::Status; @@ -66,6 +57,7 @@ pub mod armor; pub mod fingerprint; pub mod keyid; pub mod packet_pile; +pub mod tpk; /* openpgp::packet::Tag. */ @@ -102,420 +94,6 @@ pub extern "system" fn sq_tag_to_string(tag: u8) -> *const c_char { }.as_bytes().as_ptr() as *const c_char } -/* sequoia::keys. */ - -/// Returns the first TPK encountered in the reader. -#[no_mangle] -pub extern "system" fn sq_tpk_from_reader(ctx: Option<&mut Context>, - reader: Option<&mut Box>) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - let reader = reader.expect("Reader is NULL"); - fry_box!(ctx, TPK::from_reader(reader)) -} - -/// Returns the first TPK encountered in the file. -#[no_mangle] -pub extern "system" fn sq_tpk_from_file(ctx: Option<&mut Context>, - filename: *const c_char) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - assert!(! filename.is_null()); - let filename = unsafe { - CStr::from_ptr(filename).to_string_lossy().into_owned() - }; - fry_box!(ctx, TPK::from_file(&filename)) -} - -/// Returns the first TPK found in `m`. -/// -/// Consumes `m`. -#[no_mangle] -pub extern "system" fn sq_tpk_from_packet_pile(ctx: Option<&mut Context>, - m: *mut PacketPile) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - assert!(! m.is_null()); - let m = unsafe { Box::from_raw(m) }; - fry_box!(ctx, TPK::from_packet_pile(*m)) -} - -/// Returns the first TPK found in `buf`. -/// -/// `buf` must be an OpenPGP-encoded TPK. -#[no_mangle] -pub extern "system" fn sq_tpk_from_bytes(ctx: Option<&mut Context>, - b: *const uint8_t, len: size_t) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - assert!(!b.is_null()); - let buf = unsafe { - slice::from_raw_parts(b, len as usize) - }; - - fry_box!(ctx, TPK::from_bytes(buf)) -} - -/// Returns the first TPK found in the packet parser. -/// -/// Consumes the packet parser result. -#[no_mangle] -pub extern "system" fn sq_tpk_from_packet_parser(ctx: Option<&mut Context>, - ppr: *mut PacketParserResult) - -> *mut TPK -{ - let ctx = ctx.expect("Context is NULL"); - assert!(! ppr.is_null()); - let ppr = unsafe { Box::from_raw(ppr) }; - - fry_box!(ctx, TPK::from_packet_parser(*ppr)) -} - -/// Frees the TPK. -#[no_mangle] -pub extern "system" fn sq_tpk_free(tpk: *mut TPK) { - if tpk.is_null() { - return - } - unsafe { - drop(Box::from_raw(tpk)); - } -} - -/// Clones the TPK. -#[no_mangle] -pub extern "system" fn sq_tpk_clone(tpk: Option<&TPK>) - -> *mut TPK { - let tpk = tpk.expect("TPK is NULL"); - box_raw!(tpk.clone()) -} - -/// Compares TPKs. -#[no_mangle] -pub extern "system" fn sq_tpk_equal(a: Option<&TPK>, - b: Option<&TPK>) - -> bool { - let a = a.expect("TPK 'a' is NULL"); - let b = b.expect("TPK 'b' is NULL"); - a == b -} - -/// Serializes the TPK. -#[no_mangle] -pub extern "system" fn sq_tpk_serialize(ctx: Option<&mut Context>, - tpk: Option<&TPK>, - writer: Option<&mut Box>) - -> Status { - let ctx = ctx.expect("Context is NULL"); - let tpk = tpk.expect("TPK is NULL"); - let writer = writer.expect("Writer is NULL"); - fry_status!(ctx, tpk.serialize(writer)) -} - -/// Merges `other` into `tpk`. -/// -/// If `other` is a different key, then nothing is merged into -/// `tpk`, but `tpk` is still canonicalized. -/// -/// Consumes `tpk` and `other`. -#[no_mangle] -pub extern "system" fn sq_tpk_merge(ctx: Option<&mut Context>, - tpk: *mut TPK, - other: *mut TPK) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - assert!(! tpk.is_null()); - let tpk = unsafe { Box::from_raw(tpk) }; - assert!(! other.is_null()); - let other = unsafe { Box::from_raw(other) }; - fry_box!(ctx, tpk.merge(*other)) -} - -/// Adds packets to the TPK. -/// -/// This recanonicalizes the TPK. If the packets are invalid, they -/// are dropped. -/// -/// Consumes `tpk` and the packets in `packets`. The buffer, however, -/// must be managed by the caller. -#[no_mangle] -pub extern "system" fn sq_tpk_merge_packets(ctx: Option<&mut Context>, - tpk: *mut TPK, - packets: *mut *mut Packet, - packets_len: size_t) - -> *mut TPK { - let ctx = ctx.expect("Context is NULL"); - assert!(! tpk.is_null()); - let tpk = unsafe { Box::from_raw(tpk) }; - let packets = unsafe { - slice::from_raw_parts_mut(packets, packets_len) - }; - let packets = - packets.iter_mut().map(|p| *unsafe { Box::from_raw(*p) } ).collect(); - fry_box!(ctx, tpk.merge_packets(packets)) -} - -/// Dumps the TPK. -/// -/// XXX Remove this. -#[no_mangle] -pub extern "system" fn sq_tpk_dump(tpk: Option<&TPK>) { - let tpk = tpk.expect("TPK is NULL"); - println!("{:?}", *tpk); -} - -/// Returns the fingerprint. -#[no_mangle] -pub extern "system" fn sq_tpk_fingerprint(tpk: Option<&TPK>) - -> *mut Fingerprint { - let tpk = tpk.expect("TPK is NULL"); - box_raw!(tpk.fingerprint()) -} - -/// Cast the public key into a secret key that allows using the secret -/// parts of the containing keys. -#[no_mangle] -pub extern "system" fn sq_tpk_into_tsk(tpk: *mut TPK) - -> *mut TSK { - assert!(!tpk.is_null()); - let tpk = unsafe { - Box::from_raw(tpk) - }; - box_raw!(tpk.into_tsk()) -} - -/// Returns a reference to the TPK's primary key. -/// -/// The tpk still owns the key. The caller should neither modify nor -/// free the key. -#[no_mangle] -pub extern "system" fn sq_tpk_primary(tpk: Option<&TPK>) - -> Option<&packet::Key> { - let tpk = tpk.expect("TPK is NULL"); - Some(tpk.primary()) -} - -/// Returns the TPK's revocation status. -/// -/// Note: this only returns whether the TPK has been revoked, and does -/// not reflect whether an individual user id, user attribute or -/// subkey has been revoked. -#[no_mangle] -pub extern "system" fn sq_tpk_revocation_status(tpk: Option<&TPK>) - -> *mut RevocationStatus { - let tpk = tpk.expect("TPK is NULL"); - box_raw!(tpk.revoked()) -} - -fn int_to_reason_for_revocation(code: c_int) -> ReasonForRevocation { - match code { - 0 => ReasonForRevocation::KeyCompromised, - 1 => ReasonForRevocation::Unspecified, - 2 => ReasonForRevocation::KeySuperseded, - 3 => ReasonForRevocation::KeyCompromised, - 4 => ReasonForRevocation::KeyRetired, - 5 => ReasonForRevocation::UIDRetired, - _ => panic!("Bad reason for revocation: {}", code), - } -} - - -/// Returns a new revocation certificate for the TPK. -/// -/// This function does *not* consume `tpk`. -/// -/// # Example -/// -/// ```c -/// #include -/// #include -/// -/// sq_context_t ctx; -/// sq_tpk_builder_t builder; -/// sq_tpk_t tpk; -/// sq_signature_t revocation; -/// -/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); -/// -/// builder = sq_tpk_builder_default (); -/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); -/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); -/// assert (tpk); -/// assert (revocation); -/// sq_signature_free (revocation); /* Free the generated one. */ -/// -/// revocation = sq_tpk_revoke (ctx, tpk, -/// SQ_REASON_FOR_REVOCATION_KEY_COMPROMISED, -/// "It was the maid :/"); -/// assert (revocation); -/// -/// sq_packet_t packet = sq_signature_to_packet (revocation); -/// tpk = sq_tpk_merge_packets (ctx, tpk, &packet, 1); -/// assert (tpk); -/// -/// sq_revocation_status_t rs = sq_tpk_revocation_status (tpk); -/// assert (sq_revocation_status_variant (rs) == SQ_REVOCATION_STATUS_REVOKED); -/// sq_revocation_status_free (rs); -/// -/// sq_tpk_free (tpk); -/// sq_context_free (ctx); -/// ``` -#[no_mangle] -pub extern "system" fn sq_tpk_revoke(ctx: Option<&mut Context>, - tpk: Option<&mut TPK>, - code: c_int, - reason: Option<*const c_char>) - -> *mut packet::Signature -{ - let ctx = ctx.expect("Context is NULL"); - let tpk = tpk.expect("TPK is NULL"); - let code = int_to_reason_for_revocation(code); - let reason = if let Some(reason) = reason { - unsafe { - CStr::from_ptr(reason).to_bytes() - } - } else { - b"" - }; - - fry_box!(ctx, tpk.revoke(code, reason)) -} - -/// Adds a revocation certificate to the tpk. -/// -/// This function consumes the tpk. -/// -/// # Example -/// -/// ```c -/// #include -/// #include -/// -/// sq_context_t ctx; -/// sq_tpk_builder_t builder; -/// sq_tpk_t tpk; -/// sq_signature_t revocation; -/// -/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); -/// -/// builder = sq_tpk_builder_default (); -/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); -/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); -/// assert (tpk); -/// assert (revocation); -/// sq_signature_free (revocation); /* Free the generated one. */ -/// -/// tpk = sq_tpk_revoke_in_place (ctx, tpk, -/// SQ_REASON_FOR_REVOCATION_KEY_COMPROMISED, -/// "It was the maid :/"); -/// -/// sq_revocation_status_t rs = sq_tpk_revocation_status (tpk); -/// assert (sq_revocation_status_variant (rs) == SQ_REVOCATION_STATUS_REVOKED); -/// sq_revocation_status_free (rs); -/// -/// sq_tpk_free (tpk); -/// sq_context_free (ctx); -/// ``` -#[no_mangle] -pub extern "system" fn sq_tpk_revoke_in_place(ctx: Option<&mut Context>, - tpk: *mut TPK, - code: c_int, - reason: Option<*const c_char>) - -> *mut TPK -{ - let ctx = ctx.expect("Context is NULL"); - assert!(!tpk.is_null()); - let tpk = unsafe { - Box::from_raw(tpk) - }; - let code = int_to_reason_for_revocation(code); - let reason = if let Some(reason) = reason { - unsafe { - CStr::from_ptr(reason).to_bytes() - } - } else { - b"" - }; - - fry_box!(ctx, tpk.revoke_in_place(code, reason)) -} - -/// Returns whether the TPK has expired. -#[no_mangle] -pub extern "system" fn sq_tpk_expired(tpk: Option<&TPK>) - -> c_int { - let tpk = tpk.expect("TPK is NULL"); - - tpk.expired() as c_int -} - -/// Returns whether the TPK has expired. -#[no_mangle] -pub extern "system" fn sq_tpk_expired_at(tpk: Option<&TPK>, when: time_t) - -> c_int { - let tpk = tpk.expect("TPK is NULL"); - tpk.expired_at(time::at(time::Timespec::new(when as i64, 0))) as c_int -} - -/// Returns whether the TPK is alive. -#[no_mangle] -pub extern "system" fn sq_tpk_alive(tpk: Option<&TPK>) - -> c_int { - let tpk = tpk.expect("TPK is NULL"); - - tpk.alive() as c_int -} - -/// Returns whether the TPK is alive at the specified time. -#[no_mangle] -pub extern "system" fn sq_tpk_alive_at(tpk: Option<&TPK>, when: time_t) - -> c_int { - let tpk = tpk.expect("TPK is NULL"); - tpk.alive_at(time::at(time::Timespec::new(when as i64, 0))) as c_int -} - -/// Changes the TPK's expiration. -/// -/// Expiry is when the key should expire in seconds relative to the -/// key's creation (not the current time). -/// -/// This function consumes `tpk` and returns a new `TPK`. -#[no_mangle] -pub extern "system" fn sq_tpk_set_expiry(ctx: Option<&mut Context>, - tpk: *mut TPK, expiry: u32) - -> *mut TPK { - let ctx = ctx.expect("CTX is NULL"); - assert!(!tpk.is_null()); - let tpk = unsafe { - Box::from_raw(tpk) - }; - - fry_box!(ctx, tpk.set_expiry_in_seconds(expiry)) -} - -/// Returns whether the TPK includes any secret key material. -#[no_mangle] -pub extern "system" fn sq_tpk_is_tsk(tpk: Option<&TPK>) - -> c_int { - let tpk = tpk.expect("TPK is NULL"); - tpk.is_tsk() as c_int -} - -/// Returns an iterator over the TPK's user id bindings. -#[no_mangle] -pub extern "system" fn sq_tpk_primary_user_id(tpk: Option<&TPK>) - -> *mut c_char -{ - let tpk = tpk.expect("TPK is NULL"); - if let Some(binding) = tpk.userids().nth(0) { - CString::new(binding.userid().userid()) - .unwrap() // Errors only on internal nul bytes. - .into_raw() - } else { - ptr::null_mut() - } -} - fn revocation_status_to_int(rs: &RevocationStatus) -> c_int { match rs { RevocationStatus::Revoked(_) => 0, @@ -550,287 +128,6 @@ pub extern "system" fn sq_revocation_status_free( }; } -/* UserIDBinding */ - -/// Returns the user id. -/// -/// This function may fail and return NULL if the user id contains an -/// interior NUL byte. We do this rather than complicate the API, as -/// there is no valid use for such user ids; they must be malicious. -/// -/// The caller must free the returned value. -#[no_mangle] -pub extern "system" fn sq_user_id_binding_user_id( - binding: Option<&UserIDBinding>) - -> *mut c_char -{ - let binding = binding.expect("Binding is NULL"); - - if let Ok(c_str) = CString::new(binding.userid().userid()) { - c_str.into_raw() - } else { - ptr::null_mut() - } -} - -/// Returns a reference to the self-signature, if any. -#[no_mangle] -pub extern "system" fn sq_user_id_binding_selfsig( - binding: Option<&UserIDBinding>) - -> Option<&Signature> -{ - let binding = binding.expect("Binding is NULL"); - binding.binding_signature() -} - - -/* UserIDBindingIter */ - -/// Returns an iterator over the TPK's user id bindings. -#[no_mangle] -pub extern "system" fn sq_tpk_user_id_binding_iter(tpk: Option<&TPK>) - -> *mut UserIDBindingIter -{ - let tpk = tpk.expect("TPK is NULL"); - box_raw!(tpk.userids()) -} - -/// Frees a sq_user_id_binding_iter_t. -#[no_mangle] -pub extern "system" fn sq_user_id_binding_iter_free( - iter: *mut UserIDBindingIter) -{ - if iter.is_null() { return }; - unsafe { - drop(Box::from_raw(iter)) - }; -} - -/// Returns the next `UserIDBinding`. -#[no_mangle] -pub extern "system" fn sq_user_id_binding_iter_next<'a>( - iter: Option<&mut UserIDBindingIter<'a>>) - -> Option<&'a UserIDBinding> -{ - let iter = iter.expect("Iterator is NULL"); - iter.next() -} - -/* tpk::KeyIter. */ - -/// Wrapers a KeyIter for export via the FFI. -pub struct KeyIterWrapper<'a> { - iter: KeyIter<'a>, - rso: Option>, -} - -/// Returns an iterator over the TPK's keys. -/// -/// This iterates over both the primary key and any subkeys. -#[no_mangle] -pub extern "system" fn sq_tpk_key_iter(tpk: Option<&TPK>) - -> *mut KeyIterWrapper -{ - let tpk = tpk.expect("TPK is NULL"); - box_raw!(KeyIterWrapper { - iter: tpk.keys(), - rso: None, - }) -} - -/// Frees a sq_tpk_key_iter_t. -#[no_mangle] -pub extern "system" fn sq_tpk_key_iter_free( - iter: *mut KeyIterWrapper) -{ - if iter.is_null() { return }; - unsafe { - drop(Box::from_raw(iter)) - }; -} - -/// Returns the next key. Returns NULL if there are no more elements. -/// -/// If sigo is not NULL, stores the current self-signature (if any) in -/// *sigo. (Note: subkeys always have signatures, but a primary key -/// may not have a direct signature, and there might not be any user -/// ids.) -/// -/// If rso is not NULL, this stores the key's revocation status in -/// *rso. -#[no_mangle] -pub extern "system" fn sq_tpk_key_iter_next<'a>( - iter_wrapper: Option<&'a mut KeyIterWrapper<'a>>, - sigo: Option<&mut Option<&'a packet::Signature>>, - rso: Option<&mut &'a RevocationStatus<'a>>) - -> Option<&'a packet::Key> -{ - let iter_wrapper = iter_wrapper.expect("Iterator is NULL"); - iter_wrapper.rso = None; - - if let Some((sig, rs, key)) = iter_wrapper.iter.next() { - if let Some(ptr) = sigo { - *ptr = sig; - } - - if let Some(ptr) = rso { - iter_wrapper.rso = Some(rs); - *ptr = iter_wrapper.rso.as_ref().unwrap(); - } - - Some(key) - } else { - None - } -} - -/* TPKBuilder */ - -/// Creates a default `sq_tpk_builder_t`. -/// -/// # Example -/// -/// ```c -/// #include -/// #include -/// -/// sq_context_t ctx; -/// sq_tpk_builder_t builder; -/// sq_tpk_t tpk; -/// sq_signature_t revocation; -/// -/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); -/// -/// builder = sq_tpk_builder_default (); -/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); -/// sq_tpk_builder_add_userid (&builder, "some@example.org"); -/// sq_tpk_builder_add_signing_subkey (&builder); -/// sq_tpk_builder_add_encryption_subkey (&builder); -/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); -/// assert (tpk); -/// assert (revocation); -/// ``` -#[no_mangle] -pub extern "system" fn sq_tpk_builder_default() -> *mut TPKBuilder { - box_raw!(TPKBuilder::default()) -} - -/// Generates a key compliant to [Autocrypt Level 1]. -/// -/// [Autocrypt Level 1]: https://autocrypt.org/level1.html -#[no_mangle] -pub extern "system" fn sq_tpk_builder_autocrypt() -> *mut TPKBuilder { - use self::openpgp::autocrypt::Autocrypt; - box_raw!(TPKBuilder::autocrypt(Autocrypt::V1)) -} - -/// Frees an `sq_tpk_builder_t`. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_free(tpkb: *mut TPKBuilder) -{ - if tpkb.is_null() { - return - } - unsafe { - drop(Box::from_raw(tpkb)); - } -} - -/// Sets the encryption and signature algorithms for primary and all -/// subkeys. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_set_cipher_suite - (tpkb: Option<&mut *mut TPKBuilder>, cs: c_int) -{ - use self::CipherSuite::*; - let tpkb = tpkb.expect("TPKB is NULL"); - assert!(! tpkb.is_null()); - let tpkb_ = unsafe { Box::from_raw(*tpkb) }; - let cs = match cs { - 0 => Cv25519, - 1 => RSA3k, - n => panic!("Bad ciphersuite: {}", n), - }; - let tpkb_ = tpkb_.set_cipher_suite(cs); - *tpkb = box_raw!(tpkb_); -} - -/// Adds a new user ID. The first user ID added replaces the default -/// ID that is just the empty string. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_add_userid - (tpkb: Option<&mut *mut TPKBuilder>, uid: *const c_char) -{ - let tpkb = tpkb.expect("TPKB is NULL"); - assert!(!tpkb.is_null()); - let tpkb_ = unsafe { Box::from_raw(*tpkb) }; - let uid = unsafe { CStr::from_ptr(uid).to_string_lossy().to_string() }; - let tpkb_ = tpkb_.add_userid(uid.as_ref()); - *tpkb = box_raw!(tpkb_); -} - -/// Adds a signing capable subkey. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_add_signing_subkey - (tpkb: Option<&mut *mut TPKBuilder>) -{ - let tpkb = tpkb.expect("TPKB is NULL"); - assert!(!tpkb.is_null()); - let tpkb_ = unsafe { Box::from_raw(*tpkb) }; - let tpkb_ = tpkb_.add_signing_subkey(); - *tpkb = box_raw!(tpkb_); -} - -/// Adds an encryption capable subkey. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_add_encryption_subkey - (tpkb: Option<&mut *mut TPKBuilder>) -{ - let tpkb = tpkb.expect("TPKB is NULL"); - assert!(!tpkb.is_null()); - let tpkb_ = unsafe { Box::from_raw(*tpkb) }; - let tpkb_ = tpkb_.add_encryption_subkey(); - *tpkb = box_raw!(tpkb_); -} - -/// Adds an certification capable subkey. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_add_certification_subkey - (tpkb: Option<&mut *mut TPKBuilder>) -{ - let tpkb = tpkb.expect("TPKB is NULL"); - assert!(!tpkb.is_null()); - let tpkb_ = unsafe { Box::from_raw(*tpkb) }; - let tpkb_ = tpkb_.add_certification_subkey(); - *tpkb = box_raw!(tpkb_); -} - -/// Generates the actual TPK. -/// -/// Consumes `tpkb`. -#[no_mangle] -pub extern "system" fn sq_tpk_builder_generate - (ctx: Option<&mut Context>, tpkb: *mut TPKBuilder, - tpk_out: Option<&mut *mut TPK>, - revocation_out: Option<&mut *mut Signature>) - -> Status -{ - let ctx = ctx.expect("CTX is NULL"); - assert!(!tpkb.is_null()); - let tpk_out = tpk_out.expect("TPK is NULL"); - let revocation_out = revocation_out.expect("REVOCATION is NULL"); - let tpkb = unsafe { Box::from_raw(tpkb) }; - match tpkb.generate() { - Ok((tpk, revocation)) => { - *tpk_out = box_raw!(tpk); - *revocation_out = box_raw!(revocation); - Status::Success - }, - Err(e) => fry_status!(ctx, Err::<(), failure::Error>(e)), - } -} - - /* TSK */ /// Generates a new RSA 3072 bit key with UID `primary_uid`. diff --git a/ffi/src/openpgp/tpk.rs b/ffi/src/openpgp/tpk.rs new file mode 100644 index 00000000..eaa8ef40 --- /dev/null +++ b/ffi/src/openpgp/tpk.rs @@ -0,0 +1,729 @@ +//! Handles TPKs. +//! +//! Wraps [`sequoia-openpgp::TPK`] and [related functionality]. +//! +//! [`sequoia-openpgp::TPK`]: ../../../sequoia_openpgp/struct.TPK.html +//! [related functionality]: ../../../sequoia_openpgp/tpk/index.html + +use std::ffi::{CString, CStr}; +use std::ptr; +use std::slice; +use std::io::{Read, Write}; +use libc::{uint8_t, c_char, c_int, size_t, time_t}; + +extern crate sequoia_openpgp; +use self::sequoia_openpgp::{ + Fingerprint, + Packet, + PacketPile, + RevocationStatus, + TPK, + TSK, + autocrypt::Autocrypt, + constants::ReasonForRevocation, + packet::{self, Signature}, + parse::PacketParserResult, + parse::Parse, + serialize::Serialize, + tpk::{ + CipherSuite, + KeyIter, + TPKBuilder, + UserIDBinding, + UserIDBindingIter, + }, +}; + +use ::core::Context; +use ::error::Status; + +/// Returns the first TPK encountered in the reader. +#[no_mangle] +pub extern "system" fn sq_tpk_from_reader(ctx: Option<&mut Context>, + reader: Option<&mut Box>) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + let reader = reader.expect("Reader is NULL"); + fry_box!(ctx, TPK::from_reader(reader)) +} + +/// Returns the first TPK encountered in the file. +#[no_mangle] +pub extern "system" fn sq_tpk_from_file(ctx: Option<&mut Context>, + filename: *const c_char) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + assert!(! filename.is_null()); + let filename = unsafe { + CStr::from_ptr(filename).to_string_lossy().into_owned() + }; + fry_box!(ctx, TPK::from_file(&filename)) +} + +/// Returns the first TPK found in `m`. +/// +/// Consumes `m`. +#[no_mangle] +pub extern "system" fn sq_tpk_from_packet_pile(ctx: Option<&mut Context>, + m: *mut PacketPile) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + assert!(! m.is_null()); + let m = unsafe { Box::from_raw(m) }; + fry_box!(ctx, TPK::from_packet_pile(*m)) +} + +/// Returns the first TPK found in `buf`. +/// +/// `buf` must be an OpenPGP-encoded TPK. +#[no_mangle] +pub extern "system" fn sq_tpk_from_bytes(ctx: Option<&mut Context>, + b: *const uint8_t, len: size_t) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + assert!(!b.is_null()); + let buf = unsafe { + slice::from_raw_parts(b, len as usize) + }; + + fry_box!(ctx, TPK::from_bytes(buf)) +} + +/// Returns the first TPK found in the packet parser. +/// +/// Consumes the packet parser result. +#[no_mangle] +pub extern "system" fn sq_tpk_from_packet_parser(ctx: Option<&mut Context>, + ppr: *mut PacketParserResult) + -> *mut TPK +{ + let ctx = ctx.expect("Context is NULL"); + assert!(! ppr.is_null()); + let ppr = unsafe { Box::from_raw(ppr) }; + + fry_box!(ctx, TPK::from_packet_parser(*ppr)) +} + +/// Frees the TPK. +#[no_mangle] +pub extern "system" fn sq_tpk_free(tpk: *mut TPK) { + if tpk.is_null() { + return + } + unsafe { + drop(Box::from_raw(tpk)); + } +} + +/// Clones the TPK. +#[no_mangle] +pub extern "system" fn sq_tpk_clone(tpk: Option<&TPK>) + -> *mut TPK { + let tpk = tpk.expect("TPK is NULL"); + box_raw!(tpk.clone()) +} + +/// Compares TPKs. +#[no_mangle] +pub extern "system" fn sq_tpk_equal(a: Option<&TPK>, + b: Option<&TPK>) + -> bool { + let a = a.expect("TPK 'a' is NULL"); + let b = b.expect("TPK 'b' is NULL"); + a == b +} + +/// Serializes the TPK. +#[no_mangle] +pub extern "system" fn sq_tpk_serialize(ctx: Option<&mut Context>, + tpk: Option<&TPK>, + writer: Option<&mut Box>) + -> Status { + let ctx = ctx.expect("Context is NULL"); + let tpk = tpk.expect("TPK is NULL"); + let writer = writer.expect("Writer is NULL"); + fry_status!(ctx, tpk.serialize(writer)) +} + +/// Merges `other` into `tpk`. +/// +/// If `other` is a different key, then nothing is merged into +/// `tpk`, but `tpk` is still canonicalized. +/// +/// Consumes `tpk` and `other`. +#[no_mangle] +pub extern "system" fn sq_tpk_merge(ctx: Option<&mut Context>, + tpk: *mut TPK, + other: *mut TPK) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + assert!(! tpk.is_null()); + let tpk = unsafe { Box::from_raw(tpk) }; + assert!(! other.is_null()); + let other = unsafe { Box::from_raw(other) }; + fry_box!(ctx, tpk.merge(*other)) +} + +/// Adds packets to the TPK. +/// +/// This recanonicalizes the TPK. If the packets are invalid, they +/// are dropped. +/// +/// Consumes `tpk` and the packets in `packets`. The buffer, however, +/// must be managed by the caller. +#[no_mangle] +pub extern "system" fn sq_tpk_merge_packets(ctx: Option<&mut Context>, + tpk: *mut TPK, + packets: *mut *mut Packet, + packets_len: size_t) + -> *mut TPK { + let ctx = ctx.expect("Context is NULL"); + assert!(! tpk.is_null()); + let tpk = unsafe { Box::from_raw(tpk) }; + let packets = unsafe { + slice::from_raw_parts_mut(packets, packets_len) + }; + let packets = + packets.iter_mut().map(|p| *unsafe { Box::from_raw(*p) } ).collect(); + fry_box!(ctx, tpk.merge_packets(packets)) +} + +/// Dumps the TPK. +/// +/// XXX Remove this. +#[no_mangle] +pub extern "system" fn sq_tpk_dump(tpk: Option<&TPK>) { + let tpk = tpk.expect("TPK is NULL"); + println!("{:?}", *tpk); +} + +/// Returns the fingerprint. +#[no_mangle] +pub extern "system" fn sq_tpk_fingerprint(tpk: Option<&TPK>) + -> *mut Fingerprint { + let tpk = tpk.expect("TPK is NULL"); + box_raw!(tpk.fingerprint()) +} + +/// Cast the public key into a secret key that allows using the secret +/// parts of the containing keys. +#[no_mangle] +pub extern "system" fn sq_tpk_into_tsk(tpk: *mut TPK) + -> *mut TSK { + assert!(!tpk.is_null()); + let tpk = unsafe { + Box::from_raw(tpk) + }; + box_raw!(tpk.into_tsk()) +} + +/// Returns a reference to the TPK's primary key. +/// +/// The tpk still owns the key. The caller should neither modify nor +/// free the key. +#[no_mangle] +pub extern "system" fn sq_tpk_primary(tpk: Option<&TPK>) + -> Option<&packet::Key> { + let tpk = tpk.expect("TPK is NULL"); + Some(tpk.primary()) +} + +/// Returns the TPK's revocation status. +/// +/// Note: this only returns whether the TPK has been revoked, and does +/// not reflect whether an individual user id, user attribute or +/// subkey has been revoked. +#[no_mangle] +pub extern "system" fn sq_tpk_revocation_status(tpk: Option<&TPK>) + -> *mut RevocationStatus { + let tpk = tpk.expect("TPK is NULL"); + box_raw!(tpk.revoked()) +} + +fn int_to_reason_for_revocation(code: c_int) -> ReasonForRevocation { + match code { + 0 => ReasonForRevocation::KeyCompromised, + 1 => ReasonForRevocation::Unspecified, + 2 => ReasonForRevocation::KeySuperseded, + 3 => ReasonForRevocation::KeyCompromised, + 4 => ReasonForRevocation::KeyRetired, + 5 => ReasonForRevocation::UIDRetired, + _ => panic!("Bad reason for revocation: {}", code), + } +} + + +/// Returns a new revocation certificate for the TPK. +/// +/// This function does *not* consume `tpk`. +/// +/// # Example +/// +/// ```c +/// #include +/// #include +/// +/// sq_context_t ctx; +/// sq_tpk_builder_t builder; +/// sq_tpk_t tpk; +/// sq_signature_t revocation; +/// +/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); +/// +/// builder = sq_tpk_builder_default (); +/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); +/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); +/// assert (tpk); +/// assert (revocation); +/// sq_signature_free (revocation); /* Free the generated one. */ +/// +/// revocation = sq_tpk_revoke (ctx, tpk, +/// SQ_REASON_FOR_REVOCATION_KEY_COMPROMISED, +/// "It was the maid :/"); +/// assert (revocation); +/// +/// sq_packet_t packet = sq_signature_to_packet (revocation); +/// tpk = sq_tpk_merge_packets (ctx, tpk, &packet, 1); +/// assert (tpk); +/// +/// sq_revocation_status_t rs = sq_tpk_revocation_status (tpk); +/// assert (sq_revocation_status_variant (rs) == SQ_REVOCATION_STATUS_REVOKED); +/// sq_revocation_status_free (rs); +/// +/// sq_tpk_free (tpk); +/// sq_context_free (ctx); +/// ``` +#[no_mangle] +pub extern "system" fn sq_tpk_revoke(ctx: Option<&mut Context>, + tpk: Option<&mut TPK>, + code: c_int, + reason: Option<*const c_char>) + -> *mut packet::Signature +{ + let ctx = ctx.expect("Context is NULL"); + let tpk = tpk.expect("TPK is NULL"); + let code = int_to_reason_for_revocation(code); + let reason = if let Some(reason) = reason { + unsafe { + CStr::from_ptr(reason).to_bytes() + } + } else { + b"" + }; + + fry_box!(ctx, tpk.revoke(code, reason)) +} + +/// Adds a revocation certificate to the tpk. +/// +/// This function consumes the tpk. +/// +/// # Example +/// +/// ```c +/// #include +/// #include +/// +/// sq_context_t ctx; +/// sq_tpk_builder_t builder; +/// sq_tpk_t tpk; +/// sq_signature_t revocation; +/// +/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); +/// +/// builder = sq_tpk_builder_default (); +/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); +/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); +/// assert (tpk); +/// assert (revocation); +/// sq_signature_free (revocation); /* Free the generated one. */ +/// +/// tpk = sq_tpk_revoke_in_place (ctx, tpk, +/// SQ_REASON_FOR_REVOCATION_KEY_COMPROMISED, +/// "It was the maid :/"); +/// +/// sq_revocation_status_t rs = sq_tpk_revocation_status (tpk); +/// assert (sq_revocation_status_variant (rs) == SQ_REVOCATION_STATUS_REVOKED); +/// sq_revocation_status_free (rs); +/// +/// sq_tpk_free (tpk); +/// sq_context_free (ctx); +/// ``` +#[no_mangle] +pub extern "system" fn sq_tpk_revoke_in_place(ctx: Option<&mut Context>, + tpk: *mut TPK, + code: c_int, + reason: Option<*const c_char>) + -> *mut TPK +{ + let ctx = ctx.expect("Context is NULL"); + assert!(!tpk.is_null()); + let tpk = unsafe { + Box::from_raw(tpk) + }; + let code = int_to_reason_for_revocation(code); + let reason = if let Some(reason) = reason { + unsafe { + CStr::from_ptr(reason).to_bytes() + } + } else { + b"" + }; + + fry_box!(ctx, tpk.revoke_in_place(code, reason)) +} + +/// Returns whether the TPK has expired. +#[no_mangle] +pub extern "system" fn sq_tpk_expired(tpk: Option<&TPK>) + -> c_int { + let tpk = tpk.expect("TPK is NULL"); + + tpk.expired() as c_int +} + +/// Returns whether the TPK has expired. +#[no_mangle] +pub extern "system" fn sq_tpk_expired_at(tpk: Option<&TPK>, when: time_t) + -> c_int { + let tpk = tpk.expect("TPK is NULL"); + tpk.expired_at(time::at(time::Timespec::new(when as i64, 0))) as c_int +} + +/// Returns whether the TPK is alive. +#[no_mangle] +pub extern "system" fn sq_tpk_alive(tpk: Option<&TPK>) + -> c_int { + let tpk = tpk.expect("TPK is NULL"); + + tpk.alive() as c_int +} + +/// Returns whether the TPK is alive at the specified time. +#[no_mangle] +pub extern "system" fn sq_tpk_alive_at(tpk: Option<&TPK>, when: time_t) + -> c_int { + let tpk = tpk.expect("TPK is NULL"); + tpk.alive_at(time::at(time::Timespec::new(when as i64, 0))) as c_int +} + +/// Changes the TPK's expiration. +/// +/// Expiry is when the key should expire in seconds relative to the +/// key's creation (not the current time). +/// +/// This function consumes `tpk` and returns a new `TPK`. +#[no_mangle] +pub extern "system" fn sq_tpk_set_expiry(ctx: Option<&mut Context>, + tpk: *mut TPK, expiry: u32) + -> *mut TPK { + let ctx = ctx.expect("CTX is NULL"); + assert!(!tpk.is_null()); + let tpk = unsafe { + Box::from_raw(tpk) + }; + + fry_box!(ctx, tpk.set_expiry_in_seconds(expiry)) +} + +/// Returns whether the TPK includes any secret key material. +#[no_mangle] +pub extern "system" fn sq_tpk_is_tsk(tpk: Option<&TPK>) + -> c_int { + let tpk = tpk.expect("TPK is NULL"); + tpk.is_tsk() as c_int +} + +/// Returns an iterator over the TPK's user id bindings. +#[no_mangle] +pub extern "system" fn sq_tpk_primary_user_id(tpk: Option<&TPK>) + -> *mut c_char +{ + let tpk = tpk.expect("TPK is NULL"); + if let Some(binding) = tpk.userids().nth(0) { + CString::new(binding.userid().userid()) + .unwrap() // Errors only on internal nul bytes. + .into_raw() + } else { + ptr::null_mut() + } +} + +/* UserIDBinding */ + +/// Returns the user id. +/// +/// This function may fail and return NULL if the user id contains an +/// interior NUL byte. We do this rather than complicate the API, as +/// there is no valid use for such user ids; they must be malicious. +/// +/// The caller must free the returned value. +#[no_mangle] +pub extern "system" fn sq_user_id_binding_user_id( + binding: Option<&UserIDBinding>) + -> *mut c_char +{ + let binding = binding.expect("Binding is NULL"); + + if let Ok(c_str) = CString::new(binding.userid().userid()) { + c_str.into_raw() + } else { + ptr::null_mut() + } +} + +/// Returns a reference to the self-signature, if any. +#[no_mangle] +pub extern "system" fn sq_user_id_binding_selfsig( + binding: Option<&UserIDBinding>) + -> Option<&Signature> +{ + let binding = binding.expect("Binding is NULL"); + binding.binding_signature() +} + + +/* UserIDBindingIter */ + +/// Returns an iterator over the TPK's user id bindings. +#[no_mangle] +pub extern "system" fn sq_tpk_user_id_binding_iter(tpk: Option<&TPK>) + -> *mut UserIDBindingIter +{ + let tpk = tpk.expect("TPK is NULL"); + box_raw!(tpk.userids()) +} + +/// Frees a sq_user_id_binding_iter_t. +#[no_mangle] +pub extern "system" fn sq_user_id_binding_iter_free( + iter: *mut UserIDBindingIter) +{ + if iter.is_null() { return }; + unsafe { + drop(Box::from_raw(iter)) + }; +} + +/// Returns the next `UserIDBinding`. +#[no_mangle] +pub extern "system" fn sq_user_id_binding_iter_next<'a>( + iter: Option<&mut UserIDBindingIter<'a>>) + -> Option<&'a UserIDBinding> +{ + let iter = iter.expect("Iterator is NULL"); + iter.next() +} + +/* tpk::KeyIter. */ + +/// Wrapers a KeyIter for export via the FFI. +pub struct KeyIterWrapper<'a> { + iter: KeyIter<'a>, + rso: Option>, +} + +/// Returns an iterator over the TPK's keys. +/// +/// This iterates over both the primary key and any subkeys. +#[no_mangle] +pub extern "system" fn sq_tpk_key_iter(tpk: Option<&TPK>) + -> *mut KeyIterWrapper +{ + let tpk = tpk.expect("TPK is NULL"); + box_raw!(KeyIterWrapper { + iter: tpk.keys(), + rso: None, + }) +} + +/// Frees a sq_tpk_key_iter_t. +#[no_mangle] +pub extern "system" fn sq_tpk_key_iter_free( + iter: *mut KeyIterWrapper) +{ + if iter.is_null() { return }; + unsafe { + drop(Box::from_raw(iter)) + }; +} + +/// Returns the next key. Returns NULL if there are no more elements. +/// +/// If sigo is not NULL, stores the current self-signature (if any) in +/// *sigo. (Note: subkeys always have signatures, but a primary key +/// may not have a direct signature, and there might not be any user +/// ids.) +/// +/// If rso is not NULL, this stores the key's revocation status in +/// *rso. +#[no_mangle] +pub extern "system" fn sq_tpk_key_iter_next<'a>( + iter_wrapper: Option<&'a mut KeyIterWrapper<'a>>, + sigo: Option<&mut Option<&'a packet::Signature>>, + rso: Option<&mut &'a RevocationStatus<'a>>) + -> Option<&'a packet::Key> +{ + let iter_wrapper = iter_wrapper.expect("Iterator is NULL"); + iter_wrapper.rso = None; + + if let Some((sig, rs, key)) = iter_wrapper.iter.next() { + if let Some(ptr) = sigo { + *ptr = sig; + } + + if let Some(ptr) = rso { + iter_wrapper.rso = Some(rs); + *ptr = iter_wrapper.rso.as_ref().unwrap(); + } + + Some(key) + } else { + None + } +} + +/* TPKBuilder */ + +/// Creates a default `sq_tpk_builder_t`. +/// +/// # Example +/// +/// ```c +/// #include +/// #include +/// +/// sq_context_t ctx; +/// sq_tpk_builder_t builder; +/// sq_tpk_t tpk; +/// sq_signature_t revocation; +/// +/// ctx = sq_context_new ("org.sequoia-pgp.tests", NULL); +/// +/// builder = sq_tpk_builder_default (); +/// sq_tpk_builder_set_cipher_suite (&builder, SQ_TPK_CIPHER_SUITE_CV25519); +/// sq_tpk_builder_add_userid (&builder, "some@example.org"); +/// sq_tpk_builder_add_signing_subkey (&builder); +/// sq_tpk_builder_add_encryption_subkey (&builder); +/// sq_tpk_builder_generate (ctx, builder, &tpk, &revocation); +/// assert (tpk); +/// assert (revocation); +/// ``` +#[no_mangle] +pub extern "system" fn sq_tpk_builder_default() -> *mut TPKBuilder { + box_raw!(TPKBuilder::default()) +} + +/// Generates a key compliant to [Autocrypt Level 1]. +/// +/// [Autocrypt Level 1]: https://autocrypt.org/level1.html +#[no_mangle] +pub extern "system" fn sq_tpk_builder_autocrypt() -> *mut TPKBuilder { + box_raw!(TPKBuilder::autocrypt(Autocrypt::V1)) +} + +/// Frees an `sq_tpk_builder_t`. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_free(tpkb: *mut TPKBuilder) +{ + if tpkb.is_null() { + return + } + unsafe { + drop(Box::from_raw(tpkb)); + } +} + +/// Sets the encryption and signature algorithms for primary and all +/// subkeys. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_set_cipher_suite + (tpkb: Option<&mut *mut TPKBuilder>, cs: c_int) +{ + use self::CipherSuite::*; + let tpkb = tpkb.expect("TPKB is NULL"); + assert!(! tpkb.is_null()); + let tpkb_ = unsafe { Box::from_raw(*tpkb) }; + let cs = match cs { + 0 => Cv25519, + 1 => RSA3k, + n => panic!("Bad ciphersuite: {}", n), + }; + let tpkb_ = tpkb_.set_cipher_suite(cs); + *tpkb = box_raw!(tpkb_); +} + +/// Adds a new user ID. The first user ID added replaces the default +/// ID that is just the empty string. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_add_userid + (tpkb: Option<&mut *mut TPKBuilder>, uid: *const c_char) +{ + let tpkb = tpkb.expect("TPKB is NULL"); + assert!(!tpkb.is_null()); + let tpkb_ = unsafe { Box::from_raw(*tpkb) }; + let uid = unsafe { CStr::from_ptr(uid).to_string_lossy().to_string() }; + let tpkb_ = tpkb_.add_userid(uid.as_ref()); + *tpkb = box_raw!(tpkb_); +} + +/// Adds a signing capable subkey. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_add_signing_subkey + (tpkb: Option<&mut *mut TPKBuilder>) +{ + let tpkb = tpkb.expect("TPKB is NULL"); + assert!(!tpkb.is_null()); + let tpkb_ = unsafe { Box::from_raw(*tpkb) }; + let tpkb_ = tpkb_.add_signing_subkey(); + *tpkb = box_raw!(tpkb_); +} + +/// Adds an encryption capable subkey. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_add_encryption_subkey + (tpkb: Option<&mut *mut TPKBuilder>) +{ + let tpkb = tpkb.expect("TPKB is NULL"); + assert!(!tpkb.is_null()); + let tpkb_ = unsafe { Box::from_raw(*tpkb) }; + let tpkb_ = tpkb_.add_encryption_subkey(); + *tpkb = box_raw!(tpkb_); +} + +/// Adds an certification capable subkey. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_add_certification_subkey + (tpkb: Option<&mut *mut TPKBuilder>) +{ + let tpkb = tpkb.expect("TPKB is NULL"); + assert!(!tpkb.is_null()); + let tpkb_ = unsafe { Box::from_raw(*tpkb) }; + let tpkb_ = tpkb_.add_certification_subkey(); + *tpkb = box_raw!(tpkb_); +} + +/// Generates the actual TPK. +/// +/// Consumes `tpkb`. +#[no_mangle] +pub extern "system" fn sq_tpk_builder_generate + (ctx: Option<&mut Context>, tpkb: *mut TPKBuilder, + tpk_out: Option<&mut *mut TPK>, + revocation_out: Option<&mut *mut Signature>) + -> Status +{ + let ctx = ctx.expect("CTX is NULL"); + assert!(!tpkb.is_null()); + let tpk_out = tpk_out.expect("TPK is NULL"); + let revocation_out = revocation_out.expect("REVOCATION is NULL"); + let tpkb = unsafe { Box::from_raw(tpkb) }; + match tpkb.generate() { + Ok((tpk, revocation)) => { + *tpk_out = box_raw!(tpk); + *revocation_out = box_raw!(revocation); + Status::Success + }, + Err(e) => fry_status!(ctx, Err::<(), failure::Error>(e)), + } +} -- cgit v1.2.3