summaryrefslogtreecommitdiffstats
path: root/ffi
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2018-11-22 11:15:53 +0100
committerNeal H. Walfield <neal@pep.foundation>2018-11-22 11:18:02 +0100
commitd42f3db864d2b4df1cda82b4f8f79dda7c48acab (patch)
tree6f392a0780e9fe29d4ba6a900c22f66d12d49f2c /ffi
parent6cd2428c93f45b04b4ccad345970ab56a56a8eeb (diff)
ffi: Wrap the Decrypt and Verifier interfaces.
Diffstat (limited to 'ffi')
-rw-r--r--ffi/include/sequoia/openpgp.h67
-rw-r--r--ffi/src/openpgp.rs398
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)
+}