sequoia_octopus_librnp/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    collections::{
5        HashMap,
6    },
7    sync::atomic,
8};
9use std::sync::{Arc, RwLock, RwLockReadGuard};
10use libc::{
11    c_char,
12    c_int,
13    c_void,
14    size_t,
15};
16
17use sequoia_openpgp as openpgp;
18use openpgp::{
19    Fingerprint,
20    KeyHandle,
21    KeyID,
22    cert::{
23        Cert,
24    },
25    crypto::{
26        Password,
27        mem::Protected,
28    },
29    packet::{
30        Key,
31        key::{SecretParts, UnspecifiedParts, UnspecifiedRole},
32        UserID,
33    },
34    policy::{
35        HashAlgoSecurity,
36        NullPolicy,
37        StandardPolicy,
38    },
39    serialize::Serialize,
40    types::HashAlgorithm,
41};
42
43/// Controls tracing.
44const TRACE: bool = cfg!(debug_assertions)
45    && option_env!("SEQUOIA_OCTOPUS_DISABLE_TRACING").is_none();
46
47/// Enable Thunderbird-specific quirks
48///
49/// If true, then the octopus will deviate from RNP's behavior in
50/// order to work around a bug or assumption in Thunderbird.  This
51/// also clearly marks all occurrences of workarounds in this crate.
52const THUNDERBIRD_WORKAROUND: bool = true;
53
54#[allow(unused_macros)]
55macro_rules! stub {
56    ($s: ident) => {
57        #[no_mangle] pub extern "C"
58        fn $s() -> crate::RnpResult {
59            log!("\nSTUB: {}\n", stringify!($s));
60            crate::RNP_ERROR_NOT_IMPLEMENTED
61        }
62    };
63}
64
65#[allow(dead_code)]
66#[macro_use]
67pub mod error;
68use error::*;
69
70#[allow(dead_code)]
71pub mod stubs;
72
73use sequoia_ipc::Keygrip;
74
75pub mod keystore;
76use keystore::Keystore;
77
78pub mod buffer;
79use buffer::*;
80
81#[allow(dead_code)]
82pub mod flags;
83use flags::*;
84#[allow(dead_code)]
85pub mod io;
86use io::*;
87#[allow(dead_code)]
88pub mod utils;
89use utils::*;
90#[allow(dead_code)]
91pub mod conversions;
92use conversions::*;
93
94pub mod version;
95#[allow(dead_code)]
96pub mod op_verify;
97#[allow(dead_code)]
98pub mod op_encrypt;
99#[allow(dead_code)]
100pub mod op_sign;
101pub mod recombine;
102#[allow(dead_code)]
103pub mod op_generate;
104#[allow(dead_code)]
105pub mod signature;
106use signature::RnpSignature;
107#[allow(dead_code)]
108pub mod key;
109use key::RnpKey;
110#[allow(dead_code)]
111pub mod iter;
112#[allow(dead_code)]
113pub mod userid;
114use userid::RnpUserID;
115#[allow(dead_code)]
116pub mod import;
117pub mod security_rules;
118pub mod dump_packets;
119pub mod armor;
120
121// The gpg module is copied from OpenPGP CA.  We don't want to modify
122// it.
123#[allow(dead_code)]
124pub mod gpg;
125
126pub mod tbprofile;
127pub mod wot;
128pub mod parcimonie;
129
130pub const NP: &NullPolicy = unsafe { &NullPolicy::new() };
131
132#[allow(dead_code)]
133fn cert_dump(cert: &Cert) {
134    use openpgp::packet::key::SecretKeyMaterial;
135
136    eprintln!("Cert: {}, {}", cert.fingerprint(),
137              cert.with_policy(&StandardPolicy::new(), None)
138              .map(|cert| {
139                  cert.primary_userid()
140                      .map(|ua| {
141                          String::from_utf8_lossy(ua.userid().value())
142                              .into_owned()
143                      })
144                      .unwrap_or("<No UserID>"[..].into())
145              })
146              .unwrap_or("<Invalid>".into()));
147
148    for (i, k) in cert.keys().enumerate() {
149        eprint!("  {}. {}", i, k.key().fingerprint());
150        match k.key().optional_secret() {
151            Some(SecretKeyMaterial::Unencrypted(_)) => {
152                eprint!(" has unencrypted secret key material");
153            }
154            Some(SecretKeyMaterial::Encrypted(_)) => {
155                eprint!(" has encrypted secret key material");
156            }
157            None => {
158                eprint!(" has NO secret key material");
159            }
160        }
161        eprintln!("");
162    }
163}
164
165#[derive(Default)]
166pub struct RnpContext {
167    policy: Arc<RwLock<StandardPolicy<'static>>>,
168    certs: Keystore,
169    unlocked_keys: HashMap<Fingerprint, Key<SecretParts, UnspecifiedRole>>,
170    password_cb: Option<(RnpPasswordCb, *mut c_void)>,
171    plaintext_cache: recombine::PlaintextCache,
172}
173
174type RnpPasswordCb = unsafe extern "C" fn(*mut RnpContext,
175                                          *mut c_void,
176                                          *const RnpKey,
177                                          *const c_char,
178                                          *mut c_char,
179                                          size_t) -> bool;
180
181#[no_mangle] pub unsafe extern "C"
182fn rnp_ffi_create(ctx: *mut *mut RnpContext,
183                  pub_fmt: *const c_char,
184                  sec_fmt: *const c_char)
185                  -> RnpResult
186{
187    rnp_function!(rnp_ffi_create, crate::TRACE);
188    assert_ptr!(ctx);
189    let pub_fmt = assert_str!(pub_fmt);
190    let sec_fmt = assert_str!(sec_fmt);
191    if pub_fmt != "GPG" || sec_fmt != "GPG" {
192        rnp_return_status!(RNP_ERROR_BAD_FORMAT);
193    }
194
195    // Try to make sure that a pubring.gpg exists.  Thunderbird will
196    // read in the file, and if that succeeds, invoke rnp_load_keys
197    // with it.  It is important to us that this call happens, because
198    // this will set up our GnuPG synchronization.
199    //
200    // This is a best-effort mechanism.
201    if let Some(profile) = tbprofile::TBProfile::path() {
202        let maybe_create_keyring = |path: std::path::PathBuf| {
203            // Create an empty keyring if the file does not exist.
204            if let Ok(mut sink) =
205                std::fs::OpenOptions::new().write(true).create_new(true)
206                .open(&path)
207            {
208                // The empty keyring must not be zero-sized, because
209                // Thunderbird equates that with no file or any other io
210                // error reading the file.  So, let's write a marker
211                // packet.
212                //
213                // A marker packet is safe, it will be ignored by both RNP
214                // and Sequoia.  Thunderbird with both RNP and the Octopus
215                // will happily import certs into such a keyring.
216                match openpgp::Packet::Marker(Default::default())
217                    .serialize(&mut sink)
218                {
219                    Ok(_) =>
220                        t!("Created new empty keyring in {}", path.display()),
221                    Err(e) =>
222                        t!("Creating new empty keyring in {} failed: {}",
223                           path.display(), e),
224                }
225            } else if let Ok(mut sink) =
226                std::fs::OpenOptions::new().write(true).create(false)
227                .open(&path)
228            {
229                // See if the existing file is empty.
230                if let Ok(0) = sink.metadata().map(|m| m.len()) {
231                    // It is.  This will prevent rnp_load_keys from
232                    // being invoked, see above.  Modify it in place.
233                    // No one else is updating these files in place,
234                    // so we don't risk clobbering the keyring here.
235                    match openpgp::Packet::Marker(Default::default())
236                        .serialize(&mut sink)
237                    {
238                        Ok(_) =>
239                            t!("Wrote marker to empty keyring in {}",
240                               path.display()),
241                        Err(e) =>
242                            t!("Writing marker to empty keyring in {} \
243                                failed: {}", path.display(), e),
244                    }
245                }
246            }
247        };
248
249        maybe_create_keyring(profile.join("pubring.gpg"));
250        maybe_create_keyring(profile.join("secring.gpg"));
251    }
252
253    let mut policy = sequoia_policy_config::ConfiguredStandardPolicy::new();
254    if let Err(e) = policy.parse_default_config() {
255        global_warn!("Reading crypto policy: {}", e);
256    }
257    let mut policy = policy.build();
258
259    // Thunderbird checks that MD5 and SHA-1 for self-signatures are
260    // disabled and refuses to fully initialize RNP otherwise.  Meet
261    // its expectations.
262
263    let now = std::time::SystemTime::now();
264    for (algo, prop) in [
265        (HashAlgorithm::MD5, HashAlgoSecurity::CollisionResistance),
266        (HashAlgorithm::MD5, HashAlgoSecurity::SecondPreImageResistance),
267        (HashAlgorithm::SHA1, HashAlgoSecurity::CollisionResistance),
268    ]
269    {
270        let cutoff = policy.hash_cutoff(algo, prop);
271        t!("{} for {:?}: {:?}", algo, prop, cutoff);
272        if cutoff.unwrap_or(now) >= now {
273            warn!("Your crypto policy enables {} in contexts where {:?} is \
274                   needed ({:?}).  Unconditionally rejecting it.",
275                  algo, prop, cutoff);
276            policy.reject_hash_property_at(
277                algo, prop, std::time::UNIX_EPOCH);
278        }
279    }
280
281    *ctx = Box::into_raw(Box::new(RnpContext {
282        policy: Arc::new(RwLock::new(policy)),
283        ..Default::default()
284    }));
285    rnp_success!()
286}
287
288#[no_mangle] pub unsafe extern "C"
289fn rnp_ffi_destroy(ctx: *mut RnpContext) -> RnpResult {
290    rnp_function!(rnp_ffi_destroy, crate::TRACE);
291    arg!(ctx);
292    if ! ctx.is_null() {
293        drop(Box::from_raw(ctx));
294    }
295    rnp_success!()
296}
297
298#[no_mangle] pub unsafe extern "C"
299fn rnp_ffi_set_log_fd(ctx: *mut RnpContext, _fd: c_int) -> RnpResult {
300    rnp_function!(rnp_ffi_set_log_fd, crate::TRACE);
301    let _ = assert_ptr_mut!(ctx);
302    rnp_success!()
303}
304
305#[no_mangle] pub unsafe extern "C"
306fn rnp_ffi_set_pass_provider(ctx: *mut RnpContext,
307                             cb: RnpPasswordCb,
308                             cookie: *mut c_void)
309                             -> RnpResult {
310    rnp_function!(rnp_ffi_set_pass_provider, crate::TRACE);
311    let ctx = assert_ptr_mut!(ctx);
312    arg!(cb);
313    arg!(cookie);
314    ctx.password_cb = Some((cb, cookie));
315    rnp_success!()
316}
317
318impl RnpContext {
319    pub fn policy(&self) -> RwLockReadGuard<StandardPolicy<'static>> {
320        self.policy.read().unwrap()
321    }
322
323    /// Inserts a cert into the keystore.
324    ///
325    /// This strips any secret key material.
326    ///
327    /// # Locking
328    ///
329    /// This acquires a write lock on the keystore and, if the
330    /// certificate is already present, a write lock on the
331    /// certificate's cell.
332    pub fn insert_cert(&mut self, cert: Cert) {
333        self.certs.write().insert(cert.strip_secret_key_material());
334    }
335
336    /// Inserts a cert from an external source into the keystore.
337    ///
338    /// This strips any secret key material.
339    ///
340    /// certs from external sources won't be serialized.
341    ///
342    /// # Locking
343    ///
344    /// This acquires a write lock on the keystore and, if the
345    /// certificate is already present, a write lock on the
346    /// certificate's cell.
347    pub fn insert_cert_external(&mut self, cert: Cert) {
348        self.certs.write().insert_external(cert.strip_secret_key_material());
349    }
350
351    /// Inserts a key into the keystore.
352    ///
353    /// Secret key material is preserved.
354    ///
355    /// # Locking
356    ///
357    /// This acquires a write lock on the keystore and, if the
358    /// certificate is already present, a write lock on the
359    /// certificate's cell.
360    pub fn insert_key(&mut self, cert: Cert) {
361        self.certs.write().insert(cert);
362    }
363
364    /// Retrieves a certificate from the keystore by userid.
365    ///
366    /// RNP searches both the certring and the keyring, and the
367    /// keyhandle can thus refer to two certificates, potentially
368    /// different versions of the same, or even different
369    /// certificates!  Since we merge the key keyrings, this is not a
370    /// problem for us.
371    ///
372    /// # Locking
373    ///
374    /// This acquires a read lock on the keystore and one or more
375    /// certificates' cells.  See the corresponding search methods for
376    /// details.
377    pub fn cert(&self, by: &RnpIdentifier) -> Option<Cert> {
378        rnp_function!(RnpContext::cert, TRACE);
379
380        use RnpIdentifier::*;
381        let cert = match by {
382            UserID(id) => self.cert_by_userid(id),
383            KeyID(id) => self.cert_by_subkey_id(id),
384            Fingerprint(fp) => self.cert_by_subkey_fp(fp),
385            Keygrip(grip) => self.cert_by_subkey_grip(grip),
386        };
387
388        t!("Lookup by {:?} returned cert {:?}",
389           by,
390           cert.as_ref().map(|c| c.fingerprint()));
391
392        cert
393    }
394
395    /// Retrieves a certificate by userid.
396    ///
397    /// XXX: This is super dodgy.  rnp.h says "Note: only valid
398    /// userids are checked while searching by userid." but it is not
399    /// clear what that means.
400    ///
401    /// XXX: I think it would be better to fail these lookups.  Are
402    /// they used by TB?
403    ///
404    /// # Locking
405    ///
406    /// This acquires a read lock on the keystore.  Currently, this
407    /// function performs a linear scan of all keys.  As such, it
408    /// potentially acquires (in turn) a read lock on all of the
409    /// certificates' cells.
410    pub fn cert_by_userid(&self, uid: &UserID) -> Option<Cert> {
411        let mut r_cert = None;
412
413        // XXX O(n)
414        for cert in self.certs.read().iter() {
415            if cert.userids().any(|u| u.userid() == uid) {
416                r_cert = Some(cert.clone());
417                break;
418            }
419        }
420
421        r_cert
422    }
423
424    /// Retrieves a certificate from the keystore by (sub)key
425    /// handle.
426    ///
427    /// # Locking
428    ///
429    /// This acquires a read lock on the keystore and, if a matching
430    /// certificate is present, a read lock on the certificate's cell.
431    pub fn cert_by_subkey_handle(&self, handle: &KeyHandle) -> Option<Cert> {
432        match handle {
433            KeyHandle::Fingerprint(fp) => self.cert_by_subkey_fp(fp),
434            KeyHandle::KeyID(id) => self.cert_by_subkey_id(id),
435        }
436    }
437
438    /// Retrieves a certificate from the keystore by (sub)key
439    /// fingerprint.
440    ///
441    /// # Locking
442    ///
443    /// This acquires a read lock on the keystore and, if a matching
444    /// certificate is present, a read lock on the certificate's cell.
445    pub fn cert_by_subkey_fp(&self, fp: &Fingerprint) -> Option<Cert> {
446        self.certs.read().by_fp(fp).nth(0).map(|c| c.clone())
447    }
448
449    /// Retrieves a certificate from the keystore by (sub)key
450    /// keyid.
451    ///
452    /// # Locking
453    ///
454    /// This acquires a read lock on the keystore and, if a matching
455    /// certificate is present, a read lock on the certificate's cell.
456    pub fn cert_by_subkey_id(&self, id: &KeyID) -> Option<Cert> {
457        let ks = self.certs.read();
458
459         let r = ks.by_primary_id(id).nth(0)
460            .or_else(|| ks.by_subkey_id(id).nth(0))
461            .map(|c| c.clone());
462        r
463    }
464
465    /// Retrieves a certificate from the keystore by (sub)key
466    /// keygrip.
467    ///
468    /// # Locking
469    ///
470    /// This acquires a read lock on the keystore and, if a matching
471    /// certificate is present, a read lock on the certificate's cell.
472    pub fn cert_by_subkey_grip(&self, grip: &Keygrip) -> Option<Cert> {
473        let ks = self.certs.read();
474
475        let r = ks.by_primary_grip(grip).nth(0)
476            .or_else(|| ks.by_subkey_grip(grip).nth(0))
477            .map(|c| c.clone());
478        r
479    }
480}
481
482#[derive(Debug)]
483pub enum RnpPasswordFor {
484    AddSubkey,
485    AddUserID,
486    Sign,
487    Decrypt,
488    Unlock,
489    Protect,
490    Unprotect,
491    DecryptSymmetric,
492    EncryptSymmetric,
493}
494
495impl RnpPasswordFor {
496    fn pgp_context(&self) -> *const c_char {
497        use RnpPasswordFor::*;
498        (match self {
499            AddSubkey => b"add subkey\x00".as_ptr(),
500            AddUserID => b"add userid\x00".as_ptr(),
501            Sign => b"sign\x00".as_ptr(),
502            Decrypt => b"decrypt\x00".as_ptr(),
503            Unlock => b"unlock\x00".as_ptr(),
504            Protect => b"protect\x00".as_ptr(),
505            Unprotect => b"unprotect\x00".as_ptr(),
506            DecryptSymmetric => b"decrypt (symmetric)\x00".as_ptr(),
507            EncryptSymmetric => b"encrypt (symmetric)\x00".as_ptr(),
508        }) as *const c_char
509    }
510}
511
512impl RnpContext {
513    pub fn request_password(&mut self,
514                            key: Option<&RnpKey>,
515                            reason: RnpPasswordFor) -> Option<Password> {
516        rnp_function!(RnpContext::request_password, TRACE);
517        t!("key = {:?}, reason = {:?}", key.map(|k| k.fingerprint()), reason);
518
519        if let Some((f, cookie)) = self.password_cb {
520            let mut buf: Protected = vec![0; 128].into();
521            let len = buf.len();
522            let ok = unsafe {
523                f(self,
524                  cookie,
525                  key.map(|k| k as *const _).unwrap_or(std::ptr::null()),
526                  reason.pgp_context(),
527                  buf.as_mut().as_mut_ptr() as *mut c_char,
528                  len)
529            };
530
531            if ! ok {
532                t!("password_cb returned failure");
533                return None;
534            }
535
536            if let Some(got) = buf.iter().position(|b| *b == 0) {
537                t!("password_cb returned a password");
538                Some(Password::from(&buf[..got]))
539            } else {
540                eprintln!("sequoia-octopus: given password exceeded buffer");
541                None
542            }
543        } else {
544            t!("No password_cb set");
545            None
546        }
547    }
548
549    /// Decrypts the given key, if necessary.
550    pub fn decrypt_key_for(&mut self,
551                           cert: Option<&Cert>,
552                           mut key: Key<SecretParts, UnspecifiedRole>,
553                           reason: RnpPasswordFor)
554                           -> openpgp::Result<Key<SecretParts, UnspecifiedRole>>
555    {
556        rnp_function!(RnpContext::decrypt_key_for, TRACE);
557        t!("cert = {:?}, key = {}, reason = {:?}",
558           cert.map(|c| c.fingerprint()),
559           key.fingerprint(),
560           reason);
561
562        if ! key.has_unencrypted_secret() {
563            if let Some(k) = self.unlocked_keys.get(&key.fingerprint()) {
564                // Use the unlocked key instead of prompting for a
565                // password.
566                t!("Found unlocked key in cache");
567                return Ok(k.clone());
568            }
569
570            let rnp_key = if let Some(cert) = cert {
571                RnpKey::new(self, key.into(), cert)
572            } else {
573                RnpKey::without_cert(self, key.into())
574            };
575
576            if let Some(pw) = self.request_password(Some(&rnp_key), reason) {
577                key = Key::<UnspecifiedParts, UnspecifiedRole>::from(rnp_key)
578                    .parts_into_secret()?;
579                let k = key.clone();
580                key.secret_mut().decrypt_in_place(&k, &pw)
581                    .map_err(|_| Error::BadPassword)?;
582                t!("Key decrypted successfully")
583            } else {
584                return Err(anyhow::anyhow!("no password given"));
585            }
586        } else {
587            t!("Key is not encrypted, nothing to do");
588        }
589        Ok(key)
590    }
591
592    /// Returns false iff the key has not been unlocked.
593    pub fn key_is_locked(&mut self, key: &Key<SecretParts, UnspecifiedRole>)
594                         -> bool {
595        ! self.unlocked_keys.contains_key(&key.fingerprint())
596    }
597
598    /// Locks the key.
599    pub fn key_lock(&mut self, key: &Key<SecretParts, UnspecifiedRole>) {
600        self.unlocked_keys.remove(&key.fingerprint());
601    }
602
603    /// Unlocks the key.
604    ///
605    /// If `password` is None, this function will ask for a password
606    /// using the callback.
607    pub fn key_unlock(&mut self,
608                      mut key: Key<SecretParts, UnspecifiedRole>,
609                      password: Option<Password>)
610                      -> openpgp::Result<()>
611    {
612        rnp_function!(RnpContext::key_unlock, crate::TRACE);
613        t!("key: {}; password: {}",
614           key.fingerprint(),
615           if password.is_some() { "Some(_)" } else { "None" });
616
617        if ! key.has_unencrypted_secret() {
618            if let Some(pw) = password
619                .or_else(|| self.request_password(
620                    None, RnpPasswordFor::Unlock))
621            {
622                let k = key.clone();
623                key.secret_mut().decrypt_in_place(&k, &pw)
624                    .map_err(|_| Error::BadPassword)?;
625            } else {
626                return Err(anyhow::anyhow!("no password given"));
627            }
628        }
629
630        assert!(key.has_unencrypted_secret());
631        self.unlocked_keys.insert(key.fingerprint(), key);
632        Ok(())
633    }
634
635    /// Returns a reference to the unlocked key in the cache, if it
636    /// exists.
637    pub fn key_unlocked_ref(&self,
638                            key: &Key<UnspecifiedParts, UnspecifiedRole>)
639                            -> Option<&Key<SecretParts, UnspecifiedRole>>
640    {
641        self.unlocked_keys.get(&key.fingerprint())
642    }
643}
644
645#[no_mangle] pub unsafe extern "C"
646fn rnp_load_keys(ctx: *mut RnpContext,
647                 format: *const c_char,
648                 input: *mut RnpInput,
649                 flags: RnpLoadSaveFlags)
650                 -> RnpResult {
651    rnp_function!(rnp_load_keys, TRACE);
652
653    let ctx = assert_ptr_mut!(ctx);
654    let format = assert_str!(format);
655    let input = assert_ptr_mut!(input);
656    arg!(flags);
657
658    static BANNER_SHOWN: atomic::AtomicBool = atomic::AtomicBool::new(false);
659    if ! BANNER_SHOWN.load(atomic::Ordering::Relaxed) {
660        warn!("Your Thunderbird is using Sequoia's Octopus, version {}\n\
661               (sequoia-openpgp: {}).  For details, and to report issues please\n\
662               see https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp .",
663              env!("CARGO_PKG_VERSION"), sequoia_openpgp::VERSION);
664        if let Some(path) = crate::tbprofile::TBProfile::path() {
665            warn!("Your Thunderbird profile appears to be: {:?}", path);
666        } else {
667            warn!("Failed to detect your Thunderbird profile.  Please report\n\
668                   open an issue at https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp .");
669        }
670        BANNER_SHOWN.store(true, atomic::Ordering::Relaxed);
671    }
672
673    if format != "GPG" {
674        rnp_return_status!(RNP_ERROR_BAD_FORMAT);
675    }
676
677    let input_size = input.size();
678
679    match flags {
680        RNP_LOAD_SAVE_PUBLIC_KEYS => {
681            if let Ok(input_size) = input_size {
682                if let Some(profile) = tbprofile::TBProfile::path() {
683                    let pubring = profile.join("pubring.gpg");
684                    if let Ok(pubring) = std::fs::metadata(pubring) {
685                        let file_size = pubring.len();
686                        t!("input is {} bytes, pubring.gpg is {} bytes.",
687                           input_size, file_size);
688                        if input_size == file_size {
689                            t!("Looks like a match.  Periodically flushing \
690                                the keystore to disk.");
691                            rnp_try_or!((*ctx).certs.set_directory(profile),
692                                        RNP_ERROR_GENERIC);
693                        } else {
694                            t!("pubring.gpg does not match input.  \
695                                Conservatively disabling flushing the \
696                                keystore to disk.");
697                        }
698                    }
699                }
700            }
701
702            // Also load GPG's public key database.
703            if let Err(err) = (*ctx).certs.load_gpg_keyring_in_background(
704                (*ctx).policy.clone())
705            {
706                warn!("Import gpg's keyring: {}", err);
707            }
708
709            let policy = ctx.policy().clone();
710            rnp_try_or!(ctx.certs.start_parcimonie(policy), RNP_ERROR_GENERIC);
711        }
712        RNP_LOAD_SAVE_SECRET_KEYS => (),
713        f => {
714            warn!("sequoia-octopus: unexpected flags to rnp_load_keys: {:x}",
715                  f);
716            rnp_return_status!(RNP_ERROR_BAD_PARAMETERS);
717        },
718    }
719
720    use std::io::Read;
721    let mut data = Vec::new();
722    if let Err(err) = input.read_to_end(&mut data)
723    {
724        warn!("sequoia-octopus: Error reading input: {}", err);
725        rnp_return_status!(RNP_ERROR_GENERIC);
726    }
727
728    let policy = (*ctx).policy.clone();
729    if let Err(err) = (*ctx).certs.load_keyring_in_background(
730        data, flags == RNP_LOAD_SAVE_SECRET_KEYS, policy)
731    {
732        warn!("sequoia-octopus: Error reading certs: {}", err);
733        rnp_return_status!(RNP_ERROR_GENERIC);
734    }
735
736    rnp_success!()
737}
738
739#[no_mangle] pub unsafe extern "C"
740fn rnp_save_keys(ctx: *mut RnpContext,
741                 format: *const c_char,
742                 output: *mut RnpOutput,
743                 flags: RnpLoadSaveFlags)
744                 -> RnpResult {
745    rnp_function!(rnp_save_keys, TRACE);
746    let ctx = assert_ptr_mut!(ctx);
747    let format = assert_str!(format);
748    let output = assert_ptr_mut!(output);
749    if format != "GPG" {
750        rnp_return_status!(RNP_ERROR_BAD_FORMAT);
751    }
752
753    let mut r = Ok(());
754    let mut count = 0;
755    match flags {
756        RNP_LOAD_SAVE_PUBLIC_KEYS => {
757            let _ = (*ctx).certs.block_on_load();
758            for cert in (*ctx).certs.read().to_save().filter(|cert| ! cert.is_tsk()) {
759                if let Err(err) = cert.serialize(output) {
760                    r = Err(err);
761                    break;
762                } else {
763                    count += 1;
764                }
765            }
766        },
767        RNP_LOAD_SAVE_SECRET_KEYS => {
768            let _ = (*ctx).certs.block_on_load();
769            for cert in (*ctx).certs.read().to_save().filter(|cert| cert.is_tsk()) {
770                if let Err(err) = cert.as_tsk().serialize(output) {
771                    r = Err(err);
772                    break;
773                } else {
774                    count += 1;
775                }
776            }
777        }
778        f => {
779            warn!("unexpected flags to rnp_save_keys: {:x}", f);
780            rnp_return_status!(RNP_ERROR_BAD_PARAMETERS);
781        },
782    };
783
784    if count == 0 {
785        // We didn't write any bytes.  Currently, Thunderbird will not
786        // invoke rnp_load_keys if a keyring is a zero-sized file.  To
787        // avoid that, i.e. make sure that rnp_load_keys is invoked,
788        // we write a placeholder there.  See the comments in
789        // rnp_ffi_create for details.
790        if let Err(err) = openpgp::Packet::Marker(Default::default())
791            .serialize(output)
792        {
793            if r.is_ok() {
794                r = Err(err);
795            }
796        }
797    }
798
799    rnp_return_status!(if let Err(err) = r {
800        warn!("failed saving keys: {}", err);
801        RNP_ERROR_GENERIC
802    } else {
803        RNP_SUCCESS
804    })
805}
806
807#[no_mangle] pub unsafe extern "C"
808fn rnp_get_public_key_count(ctx: *mut RnpContext,
809                            count: *mut size_t)
810                            -> RnpResult {
811    rnp_function!(rnp_get_public_key_count, crate::TRACE);
812    let ctx = assert_ptr_mut!(ctx);
813    let count = assert_ptr_mut!(count);
814
815    // We load the keyrings in the background.  But, once TB tries to
816    // get the number of certs, we have to wait on that process.
817    let _ = ctx.certs.block_on_load();
818
819    // Make sure the agent listing is up to date.
820    let mut ks = ctx.certs.write();
821    ks.key_on_agent_hard(&Fingerprint::V4(Default::default()));
822    drop(ks);
823
824    let ks = ctx.certs.read();
825    *count = ks.iter().filter(|cert| {
826        if cert.is_tsk() {
827            false
828        } else if ks.key_on_agent(&cert.fingerprint()).0 {
829            false
830        } else {
831            true
832        }
833    }).count();
834    t!("-> {}", *count);
835    rnp_success!()
836}
837
838#[no_mangle] pub unsafe extern "C"
839fn rnp_get_secret_key_count(ctx: *mut RnpContext,
840                            count: *mut size_t)
841                            -> RnpResult {
842    rnp_function!(rnp_get_secret_key_count, TRACE);
843    let ctx = assert_ptr_mut!(ctx);
844    let count = assert_ptr_mut!(count);
845
846    // We load the keyrings in the background.  But, once TB tries to
847    // get the number of certs, we have to wait on that process.
848    let _ = ctx.certs.block_on_load();
849
850    // Make sure the agent listing is up to date.
851    let mut ks = ctx.certs.write();
852    ks.key_on_agent_hard(&Fingerprint::V4(Default::default()));
853    drop(ks);
854
855    let ks = ctx.certs.read();
856    *count = ks.iter().filter(|cert| {
857        if cert.is_tsk() {
858            true
859        } else {
860            let fpr = &cert.fingerprint();
861            ks.key_on_agent(fpr).0
862        }
863    }).count();
864    t!("-> {}", *count);
865    rnp_success!()
866}