diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-11-22 11:15:53 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-11-22 11:18:02 +0100 |
commit | d42f3db864d2b4df1cda82b4f8f79dda7c48acab (patch) | |
tree | 6f392a0780e9fe29d4ba6a900c22f66d12d49f2c /ffi | |
parent | 6cd2428c93f45b04b4ccad345970ab56a56a8eeb (diff) |
ffi: Wrap the Decrypt and Verifier interfaces.
Diffstat (limited to 'ffi')
-rw-r--r-- | ffi/include/sequoia/openpgp.h | 67 | ||||
-rw-r--r-- | ffi/src/openpgp.rs | 398 |
2 files changed, 464 insertions, 1 deletions
diff --git a/ffi/include/sequoia/openpgp.h b/ffi/include/sequoia/openpgp.h index 1d11a113..0c7a67b9 100644 --- a/ffi/include/sequoia/openpgp.h +++ b/ffi/include/sequoia/openpgp.h @@ -1321,4 +1321,71 @@ typedef struct sq_secret *sq_secret_t; /*/ sq_secret_t sq_secret_cached(uint8_t algo, uint8_t *session_key, size_t session_key_len); + +typedef struct sq_verification_results *sq_verification_results_t; +typedef struct sq_verification_result *sq_verification_result_t; + +void sq_verification_results_at_level(sq_verification_results_t results, + size_t level, + sq_verification_result_t **r, + size_t *r_count); + + +typedef enum sq_verification_result_code { + SQ_VERIFICATION_RESULT_CODE_GOOD_CHECKSUM = 1, + SQ_VERIFICATION_RESULT_CODE_MISSING_KEY = 2, + SQ_VERIFICATION_RESULT_CODE_BAD_CHECKSUM = 3, + + /* Dummy value to make sure the enumeration has a defined size. Do + not use this value. */ + SQ_VERIFICATION_RESULT_CODE_FORCE_WIDTH = INT_MAX, +} sq_verification_result_code_t; + +/*/ +/// Returns the verification result code. +/*/ +sq_verification_result_code_t sq_verification_result_code( + sq_verification_result_t r); + +/*/ +/// Returns a reference to the signature. +/// +/// Do not modify the signature nor free it. +/*/ +sq_signature_t sq_verification_result_signature( + sq_verification_result_t r); + +/*/ +/// Returns the signature's level. +/// +/// A level of zero means that the data was signed, a level of one +/// means that one or more signatures were notarized, etc. +/*/ +int sq_verification_result_level(sq_verification_result_t r); + +typedef sq_status_t (*sq_sequoia_decrypt_get_public_keys_cb_t) (void *, + sq_keyid_t *, size_t, + sq_tpk_t **, size_t *, + void (**free)(void *)); + +typedef sq_status_t (*sq_sequoia_decrypt_get_secret_keys_cb_t) (void *, + sq_pkesk_t *, size_t, + sq_skesk_t *, size_t, + sq_secret_t *); + +typedef sq_status_t (*sq_sequoia_decrypt_check_signatures_cb_t) (void *, + sq_verification_results_t, + size_t); + +sq_status_t sq_decrypt (sq_context_t ctx, sq_reader_t input, sq_writer_t output, + sq_sequoia_decrypt_get_public_keys_cb_t get_public_keys, + sq_sequoia_decrypt_get_secret_keys_cb_t get_secret_keys, + sq_sequoia_decrypt_check_signatures_cb_t check_signatures, + void *cookie); + +sq_status_t sq_verify (sq_context_t ctx, sq_reader_t input, sq_writer_t output, + sq_sequoia_decrypt_get_public_keys_cb_t get_public_keys, + sq_sequoia_decrypt_check_signatures_cb_t check_signatures, + void *cookie); + #endif diff --git a/ffi/src/openpgp.rs b/ffi/src/openpgp.rs index 8f035e0d..87015901 100644 --- a/ffi/src/openpgp.rs +++ b/ffi/src/openpgp.rs @@ -6,8 +6,10 @@ use std::hash::{Hash, Hasher}; use std::mem::{size_of, forget}; use std::ptr; use std::slice; +use std::io; use std::io::{Read, Write}; -use libc::{self, uint8_t, uint64_t, c_char, c_int, size_t, ssize_t}; +use libc::{self, uint8_t, uint64_t, c_char, c_int, size_t, ssize_t, c_void}; +use failure::ResultExt; extern crate openpgp; @@ -23,6 +25,7 @@ use self::openpgp::{ packet::{ Signature, PKESK, + SKESK, }, SecretKey, crypto::Password, @@ -37,7 +40,12 @@ use self::openpgp::tpk::{ use self::openpgp::packet; use self::openpgp::parse::{PacketParserResult, PacketParser, PacketParserEOF}; use self::openpgp::parse::stream::{ + DecryptionHelper, + Decryptor, Secret, + VerificationHelper, + VerificationResult, + Verifier, }; use self::openpgp::serialize::Serialize; use self::openpgp::constants::{ @@ -2178,3 +2186,391 @@ pub fn sq_secret_cached<'a>(algo: u8, session_key: session_key.to_vec().into() }) } + + +// Decryptor. + +/// A message's verification results. +/// +/// Conceptually, the verification results are an array of an array of +/// VerificationResult. The outer array is for the verification level +/// and is indexed by the verification level. A verification level of +/// zero corresponds to direct signatures; A verification level of 1 +/// corresponds to notorizations (i.e., signatures of signatures); +/// etc. +/// +/// Within each level, there can be one or more signatures. +pub struct VerificationResults<'a> { + results: Vec<Vec<&'a VerificationResult>>, +} + +/// Returns the `VerificationResult`s at level `level. +/// +/// Conceptually, the verification results are an array of an array of +/// VerificationResult. The outer array is for the verification level +/// and is indexed by the verification level. A verification level of +/// zero corresponds to direct signatures; A verification level of 1 +/// corresponds to notorizations (i.e., signatures of signatures); +/// etc. +/// +/// This function returns the verification results for a particular +/// level. The result is an array of references to +/// `VerificationResult`. +#[no_mangle] +pub fn sq_verification_results_at_level<'a>(results: Option<&'a VerificationResults>, + level: size_t, + r: Option<&mut *const &'a VerificationResult>, + r_count: Option<&mut size_t>) { + let results = results.expect("results is NULL"); + let r = r.expect("r is NULL"); + let r_count = r_count.expect("r_count is NULL"); + + assert!(level < results.results.len()); + + // The size of VerificationResult is not known in C. Convert from + // an array of VerificationResult to an array of + // VerificationResult refs. + *r = results.results[level].as_ptr(); + *r_count = results.results[level].len(); +} + +/// Returns the verification result code. +#[no_mangle] +pub fn sq_verification_result_code(result: Option<&VerificationResult>) + -> c_int +{ + let result = result.expect("result is NULL"); + match result { + VerificationResult::GoodChecksum(_) => 1, + VerificationResult::MissingKey(_) => 2, + VerificationResult::BadChecksum(_) => 3, + } +} + +/// Returns the verification result code. +#[no_mangle] +pub fn sq_verification_result_signature(result: Option<&VerificationResult>) + -> *const packet::Signature +{ + let result = result.expect("result is NULL"); + let sig = match result { + VerificationResult::GoodChecksum(ref sig) => sig, + VerificationResult::MissingKey(ref sig) => sig, + VerificationResult::BadChecksum(ref sig) => sig, + }; + + sig as *const packet::Signature +} + +/// Returns the verification result code. +#[no_mangle] +pub fn sq_verification_result_level(result: Option<&VerificationResult>) + -> c_int +{ + let result = result.expect("result is NULL"); + result.level() as c_int +} + + +/// Passed as the first argument to the callbacks used by sq_verify +/// and sq_decrypt. +pub struct HelperCookie { +} + +/// How to free the memory allocated by the callback. +type FreeCallback = fn(*mut c_void); + +/// Returns the TPKs corresponding to the passed KeyIDs. +/// +/// If the free callback is not NULL, then it is called to free the +/// returned array of TPKs. +type GetPublicKeysCallback = fn(*mut HelperCookie, + *const &KeyID, usize, + &mut *mut &mut TPK, *mut usize, + *mut FreeCallback) -> Status; + +/// Returns a session key. +type GetSecretKeysCallback = fn(*mut HelperCookie, + *const &PKESK, usize, + *const &SKESK, usize, + &mut *mut Secret) -> Status; + +/// Process the signatures. +/// +/// If the result is not Status::Success, then this aborts the +/// Verification. +type CheckSignaturesCallback = fn(*mut HelperCookie, + *const VerificationResults, + usize) -> Status; + +// This fetches keys and computes the validity of the verification. +struct VHelper { + get_public_keys_cb: GetPublicKeysCallback, + check_signatures_cb: CheckSignaturesCallback, + cookie: *mut HelperCookie, +} + +impl VHelper { + fn new(get_public_keys: GetPublicKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Self + { + VHelper { + get_public_keys_cb: get_public_keys, + check_signatures_cb: check_signatures, + cookie: cookie, + } + } +} + +impl VerificationHelper for VHelper { + fn get_public_keys(&mut self, ids: &[KeyID]) + -> Result<Vec<TPK>, failure::Error> + { + // The size of KeyID is not known in C. Convert from an array + // of KeyIDs to an array of KeyID refs. + let ids : Vec<&KeyID> = ids.iter().collect(); + + let mut tpk_refs_raw : *mut &mut TPK = ptr::null_mut(); + let mut tpk_refs_raw_len = 0usize; + + let mut free : FreeCallback = |_| {}; + + let result = (self.get_public_keys_cb)( + self.cookie, + ids.as_ptr(), ids.len(), + &mut tpk_refs_raw, &mut tpk_refs_raw_len as *mut usize, + &mut free); + if result != Status::Success { + // XXX: We need to convert the status to an error. A + // status contains less information, but we should do the + // best we can. For now, we just use + // Error::InvalidArgument. + return Err(openpgp::Error::InvalidArgument( + format!("{:?}", result)).into()); + } + + // Convert the array of references to TPKs to a Vec<TPK> + // (i.e., not a Vec<&TPK>). + let mut tpks : Vec<TPK> = Vec::with_capacity(tpk_refs_raw_len); + for i in 0..tpk_refs_raw_len { + let tpk = unsafe { ptr::read(*tpk_refs_raw.offset(i as isize)) }; + tpks.push(tpk); + } + + forget(tpk_refs_raw); + (free)(tpk_refs_raw as *mut c_void); + + Ok(tpks) + } + + fn check(&mut self, sigs: Vec<Vec<VerificationResult>>) + -> Result<(), failure::Error> + { + // The size of VerificationResult is not known in C. Convert + // from an array of VerificationResults to an array of + // VerificationResult refs. + let results = VerificationResults { + results: sigs.iter().map( + |r| r.iter().collect::<Vec<&VerificationResult>>()).collect() + }; + + let result = (self.check_signatures_cb)(self.cookie, + &results, + results.results.len()); + if result != Status::Success { + // XXX: We need to convert the status to an error. A + // status contains less information, but we should do the + // best we can. For now, we just use + // Error::InvalidArgument. + return Err(openpgp::Error::InvalidArgument( + format!("{:?}", result)).into()); + } + + Ok(()) + } +} + +fn verify_real<'a>(input: &'a mut Box<'a + Read>, + output: Option<&'a mut Box<'a + Write>>, + get_public_keys: GetPublicKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Result<(), failure::Error> +{ + let h = VHelper::new(get_public_keys, check_signatures, cookie); + let mut v = Verifier::from_reader(input, h)?; + + let r = if let Some(output) = output { + io::copy(&mut v, output) + } else { + let mut buffer = vec![0u8; 64 * 1024]; + loop { + match v.read(&mut buffer) { + // EOF. + Ok(0) => break Ok(0), + // Some error. + Err(err) => break Err(err), + // Still something to read. + Ok(_) => continue, + } + } + }; + + r.map_err(|e| if e.get_ref().is_some() { + // Wrapped failure::Error. Recover it. + failure::Error::from_boxed_compat(e.into_inner().unwrap()) + } else { + // Plain io::Error. + e.into() + }).context("Verification failed")?; + + Ok(()) +} + + +/// Verifies an OpenPGP message. +/// +/// No attempt is made to decrypt any encryption packets. These are +/// treated as opaque containers. +/// +/// Note: output may be NULL, if the output is not required. +#[no_mangle] +pub fn sq_verify<'a>(ctx: Option<&mut Context>, + input: Option<&'a mut Box<'a + Read>>, + output: Option<&'a mut Box<'a + Write>>, + get_public_keys: GetPublicKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Status +{ + let ctx = ctx.expect("Context is NULL"); + let input = input.expect("Input is NULL"); + + let r = verify_real(input, output, + get_public_keys, check_signatures, cookie); + + fry_status!(ctx, r) +} + + +struct DHelper { + vhelper: VHelper, + get_secret_keys_cb: GetSecretKeysCallback, +} + +impl DHelper { + fn new(get_public_keys: GetPublicKeysCallback, + get_secret_keys: GetSecretKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Self + { + DHelper { + vhelper: VHelper::new(get_public_keys, check_signatures, cookie), + get_secret_keys_cb: get_secret_keys, + } + } +} + +impl VerificationHelper for DHelper { + fn get_public_keys(&mut self, ids: &[KeyID]) + -> Result<Vec<TPK>, failure::Error> + { + self.vhelper.get_public_keys(ids) + } + + fn check(&mut self, sigs: Vec<Vec<VerificationResult>>) + -> Result<(), failure::Error> + { + self.vhelper.check(sigs) + } +} + +impl DecryptionHelper for DHelper { + fn get_secret(&mut self, pkesks: &[&PKESK], skesks: &[&SKESK]) + -> Result<Option<Secret>, failure::Error> + { + let mut secret : *mut Secret = ptr::null_mut(); + + let result = (self.get_secret_keys_cb)( + self.vhelper.cookie, + pkesks.as_ptr(), pkesks.len(), skesks.as_ptr(), skesks.len(), + &mut secret); + if result != Status::Success { + // XXX: We need to convert the status to an error. A + // status contains less information, but we should do the + // best we can. For now, we just use + // Error::InvalidArgument. + return Err(openpgp::Error::InvalidArgument( + format!("{:?}", result)).into()); + } + + let secret = unsafe { + Box::from_raw(secret) + }; + + Ok(Some(*secret)) + } +} + +// A helper function that returns a Result so that we can use ? to +// propagate errors. +fn decrypt_real<'a>(input: &'a mut Box<'a + Read>, + output: &'a mut Box<'a + Write>, + get_public_keys: GetPublicKeysCallback, + get_secret_keys: GetSecretKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Result<(), failure::Error> +{ + let helper = DHelper::new( + get_public_keys, get_secret_keys, check_signatures, cookie); + + let mut decryptor = Decryptor::from_reader(input, helper) + .context("Decryption failed")?; + + io::copy(&mut decryptor, output) + .map_err(|e| if e.get_ref().is_some() { + // Wrapped failure::Error. Recover it. + failure::Error::from_boxed_compat(e.into_inner().unwrap()) + } else { + // Plain io::Error. + e.into() + }).context("Decryption failed")?; + + Ok(()) +} + +/// Decrypts an OpenPGP message. +/// +/// The message is read from `input` and the content of the +/// `LiteralData` packet is written to output. Note: the content is +/// written even if the message is not encrypted. You can determine +/// whether the message was actually decrypted by recording whether +/// the get_secret_keys callback was called in the cookie. +/// +/// The function takes three callbacks. The `cookie` is passed as the +/// first parameter to each of them. +/// +/// Note: all of the parameters are required; none may be NULL. +#[no_mangle] +pub fn sq_decrypt<'a>(ctx: Option<&mut Context>, + input: Option<&'a mut Box<'a + Read>>, + output: Option<&'a mut Box<'a + Write>>, + get_public_keys: GetPublicKeysCallback, + get_secret_keys: GetSecretKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Status +{ + let ctx = ctx.expect("Context is NULL"); + let input = input.expect("Input is NULL"); + let output = output.expect("Output is NULL"); + + let r = decrypt_real(input, output, + get_public_keys, get_secret_keys, check_signatures, cookie); + + fry_status!(ctx, r) +} |