diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-03-20 12:26:36 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-03-20 13:55:25 +0100 |
commit | d865c46bfc2c4b6bd25060dacd7d605f26fbdab7 (patch) | |
tree | 9bf1d4f4a260eb6b7d00de6d5af6ce5e93c118f0 /openpgp-ffi/src/parse/stream.rs | |
parent | 5a36abe9131cd24ba1fbeb76f15fd65b11e1d983 (diff) |
openpgp-ffi: Move the decryption and verification code.
Diffstat (limited to 'openpgp-ffi/src/parse/stream.rs')
-rw-r--r-- | openpgp-ffi/src/parse/stream.rs | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/openpgp-ffi/src/parse/stream.rs b/openpgp-ffi/src/parse/stream.rs new file mode 100644 index 00000000..1342fa6d --- /dev/null +++ b/openpgp-ffi/src/parse/stream.rs @@ -0,0 +1,500 @@ +//! Streaming decryption and verification. +//! +//! This module provides convenient filters for decryption and +//! verification of OpenPGP messages. It is the preferred interface to +//! process OpenPGP messages. These implementations use constant +//! space. +//! +//! Wraps the streaming parsing functions, see +//! [`sequoia-openpgp::parse::stream`]. +//! +//! [`sequoia-openpgp::parse::stream`]: ../../../sequoia_openpgp/parse/stream/index.html + +use std::ptr; +use std::slice; +use std::io as std_io; +use std::io::Read; +use libc::{c_int, size_t, c_void}; +use failure::ResultExt; + +extern crate sequoia_openpgp as openpgp; +extern crate time; + +use self::openpgp::{ + RevocationStatus, + packet::{ + PKESK, + SKESK, + }, +}; +use self::openpgp::parse::stream::{ + DecryptionHelper, + Decryptor, + Secret, + VerificationHelper, + VerificationResult, + Verifier, + DetachedVerifier, +}; + +use Maybe; +use MoveFromRaw; +use MoveIntoRaw; +use RefMutRaw; + +use super::super::{ + error::Status, + io, + keyid, + packet, + tpk::TPK, +}; + +fn revocation_status_to_int(rs: &RevocationStatus) -> c_int { + match rs { + RevocationStatus::Revoked(_) => 0, + RevocationStatus::CouldBe(_) => 1, + RevocationStatus::NotAsFarAsWeKnow => 2, + } +} + +/// Returns the TPK's revocation status variant. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub extern "system" fn pgp_revocation_status_variant( + rs: *mut RevocationStatus) + -> c_int +{ + let rs = ffi_param_move!(rs); + let variant = revocation_status_to_int(rs.as_ref()); + Box::into_raw(rs); + variant +} + +/// Frees a pgp_revocation_status_t. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub extern "system" fn pgp_revocation_status_free( + rs: Option<&mut RevocationStatus>) +{ + ffi_free!(rs) +} + +// Secret. + +/// Creates an pgp_secret_t from a decrypted session key. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_secret_cached<'a>(algo: u8, + session_key: *const u8, + session_key_len: size_t) + -> *mut Secret +{ + let session_key = if session_key_len > 0 { + unsafe { + slice::from_raw_parts(session_key, session_key_len) + } + } else { + &[] + }; + + box_raw!(Secret::Cached { + algo: algo.into(), + 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`. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_verification_results_at_level<'a>(results: *const VerificationResults<'a>, + level: size_t, + r: *mut *const &'a VerificationResult, + r_count: *mut size_t) { + let results = ffi_param_ref!(results); + let r = ffi_param_ref_mut!(r); + let r_count = ffi_param_ref_mut!(r_count); + + 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. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_verification_result_code(result: *const VerificationResult) + -> c_int +{ + let result = ffi_param_ref!(result); + match result { + VerificationResult::GoodChecksum(_) => 1, + VerificationResult::MissingKey(_) => 2, + VerificationResult::BadChecksum(_) => 3, + } +} + +/// Returns the verification result code. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_verification_result_signature(result: *const VerificationResult) + -> *mut packet::signature::Signature +{ + let result = ffi_param_ref!(result); + let sig = match result { + VerificationResult::GoodChecksum(ref sig) => sig, + VerificationResult::MissingKey(ref sig) => sig, + VerificationResult::BadChecksum(ref sig) => sig, + }; + + sig.move_into_raw() +} + +/// Returns the verification result code. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_verification_result_level(result: *const VerificationResult) + -> c_int +{ + let result = ffi_param_ref!(result); + result.level() as c_int +} + + +/// Passed as the first argument to the callbacks used by pgp_verify +/// and pgp_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 *mut keyid::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: &[openpgp::KeyID]) + -> Result<Vec<openpgp::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<*mut keyid::KeyID> = + ids.iter().map(|k| k.move_into_raw()).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<openpgp::TPK> = Vec::with_capacity(tpk_refs_raw_len); + for i in 0..tpk_refs_raw_len { + let tpk_raw = unsafe { *tpk_refs_raw.offset(i as isize) }; + tpks.push(tpk_raw.move_from_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 Read, + dsig: Option<&mut io::ReaderKind>, + output: Option<&mut Box<::std::io::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 = if let Some(dsig) = dsig { + DetachedVerifier::from_reader(dsig, input, h)? + } else { + Verifier::from_reader(input, h)? + }; + + let r = if let Some(output) = output { + std_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. + ::failure::Error::from(e) + }).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. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_verify<'a>(errp: Option<&mut *mut ::error::Error>, + input: *mut io::Reader, + dsig: Maybe<io::Reader>, + output: Maybe<io::Writer>, + get_public_keys: GetPublicKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Status +{ + ffi_make_fry_from_errp!(errp); + let input = input.ref_mut_raw(); + + let r = verify_real(input, dsig.ref_mut_raw(), output.ref_mut_raw(), + get_public_keys, check_signatures, cookie); + + ffi_try_status!(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: &[openpgp::KeyID]) + -> Result<Vec<openpgp::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()); + } + + if secret.is_null() { + return Err(openpgp::Error::MissingSessionKey( + "Callback did not return a session key".into()).into()); + } + + let secret = ffi_param_move!(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 io::ReaderKind, + output: &'a mut ::std::io::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")?; + + std_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. + ::failure::Error::from(e) + }).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. +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] +pub fn pgp_decrypt<'a>(errp: Option<&mut *mut ::error::Error>, + input: *mut io::Reader, + output: *mut io::Writer, + get_public_keys: GetPublicKeysCallback, + get_secret_keys: GetSecretKeysCallback, + check_signatures: CheckSignaturesCallback, + cookie: *mut HelperCookie) + -> Status +{ + ffi_make_fry_from_errp!(errp); + let input = input.ref_mut_raw(); + let output = output.ref_mut_raw(); + + let r = decrypt_real(input, output, + get_public_keys, get_secret_keys, check_signatures, cookie); + + ffi_try_status!(r) +} |