sequoia_gpg_agent/
lib.rs

1//! This crate includes functionality for interacting with GnuPG's
2//! `gpg-agent`.
3//!
4//! `gpg-agent` is a secret key store, which is shipped as part of GnuPG.
5//! It is used to manage secret key material, and hardware devices that
6//! contain secret key material.  It provides an RPC interface using the
7//! [Assuan protocol].
8//!
9//!   [Assuan protocol]: https://gnupg.org/software/libassuan
10//!
11//! This is how `gpg`, GnuPG's primary command-line interface,
12//! communicates with it.
13//!
14//! This crate provides a Rust API for interacting with `gpg-agent`.
15//!
16//! Note: this crate communicates directly with `gpg-agent`; it does not
17//! go via `gpg`.
18//!
19//! # Examples
20//!
21//! Import secret key material, list the keys, and sign a message:
22//!
23//! ```rust
24//! use std::io::{Read, Write};
25//!
26//! use sequoia_openpgp as openpgp;
27//! use openpgp::Cert;
28//! use openpgp::packet::signature;
29//! use openpgp::parse::Parse;
30//! use openpgp::policy::StandardPolicy;
31//! use openpgp::serialize::stream::{Message, Signer, LiteralWriter};
32//! use openpgp::types::HashAlgorithm;
33//! use openpgp::types::SignatureType;
34//!
35//! use sequoia_gpg_agent as gpg_agent;
36//! use gpg_agent::KeyPair;
37//!
38//! const P: &StandardPolicy = &StandardPolicy::new();
39//!
40//! # let _: Result<(), anyhow::Error> = tokio_test::block_on(async {
41//! #
42//! // Load a TSK, i.e., a certificate with secret key material.
43//! let key = // ...
44//! # "\
45//! # -----BEGIN PGP PRIVATE KEY BLOCK-----
46//! # Comment: B6E6 9975 5F27 FF3D 002D  BD26 0390 037C 6D2B DA9A
47//! #
48//! # xVgEZewcTxYJKwYBBAHaRw8BAQdAitNrSMaEq1ZCXBtefsgJfGHdipTrZnTKXf6R
49//! # mnueg9oAAQC8xOJk0vjPPQNEkXc5xlm7w+f2C7EGQ0/GQYuxTVZAoRC6wsALBB8W
50//! # CgB9BYJl7BxPAwsJBwkQA5ADfG0r2ppHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu
51//! # c2VxdW9pYS1wZ3Aub3JnNHQtwZonz3Z3xLL1M7jK/Sl4R1TNDqZ+xeBhXSzbOAkD
52//! # FQoIApsBAh4BFiEEtuaZdV8n/z0ALb0mA5ADfG0r2poAAKCuAP4ncgpWq32cIDQU
53//! # ApwklDTPpWpahIuYsL0LdIjkOnVNrQD/XXxnSIVPpD9ax6Gkwdjrq21rUSnQOQcb
54//! # LPJ9jbSS2wHHWARl7BxPFgkrBgEEAdpHDwEBB0Brn/OSMDjcjVvcUrY+wYjtpQDw
55//! # BFpow6NrHs4X5K2wkgAA/3sqHgNHP16HDEwfBteL4YfdXIj0fDBHmvLy6csYieke
56//! # D/nCwL8EGBYKATEFgmXsHE8JEAOQA3xtK9qaRxQAAAAAAB4AIHNhbHRAbm90YXRp
57//! # b25zLnNlcXVvaWEtcGdwLm9yZyUyW5yXDcLc93rxkYdz7tnHpOKjviJn17/hCQw/
58//! # p3YkApsCvqAEGRYKAG8FgmXsHE8JEAiqGShDW50RRxQAAAAAAB4AIHNhbHRAbm90
59//! # YXRpb25zLnNlcXVvaWEtcGdwLm9yZx0ribmnTiQaPn4ftkFsyOYwo4cerHsFSjch
60//! # sIRTjGosFiEEHXtwtDIzaa3fXh2iCKoZKENbnREAAH6OAP9aIMae+BJWPfxlf1cL
61//! # 4wwqTihcctO8LjaUk/TZVc8I6AEAlCMAn9SinU0FhgGr8RThuY2Nj8dwzEdCbhVu
62//! # l9jxrQ0WIQS25pl1Xyf/PQAtvSYDkAN8bSvamgAA1Q0A/A4aKmIY7aNShNoJ5cT3
63//! # SmqCrmFBQuIAXY5QhKu44FtcAQDZ4nB8GNORlAe7q719lLV+jo/BymIRstcU0R3R
64//! # lfqgCg==
65//! # =B79/
66//! # -----END PGP PRIVATE KEY BLOCK-----
67//! # ";
68//! let cert = Cert::from_bytes(key)?;
69//! assert!(cert.is_tsk());
70//!
71//! // Connect to a temporary GnuPG home directory.  Usually,
72//! // you'll want to connect to the default home directory using
73//! // Agent::connect_to_default.
74//! let mut agent = gpg_agent::Agent::connect_to_ephemeral().await?;
75//!
76//! // Import the secret key material into gpg-agent.
77//! for k in cert.keys().secret() {
78//!     agent.import(P,
79//!                  &cert, k.key().parts_as_secret().expect("have secret"),
80//!                  true, true).await?;
81//! }
82//!
83//! // List the keys.
84//! let list = agent.list_keys().await?;
85//! # assert_eq!(list.len(), cert.keys().secret().count());
86//! println!("gpg agent manages {} keys:", list.len());
87//! for k in list.iter() {
88//!     eprintln!("  - {}", k.keygrip());
89//! }
90//!
91//! // Sign a message using the signing key we just imported.
92//! let signing_key = cert.with_policy(P, None)?
93//!     .keys().for_signing()
94//!     .next().expect("Have a signing-capable subkey.");
95//!
96//! let mut keypair = agent.keypair(signing_key.key())?;
97//!
98//! let mut signed_message = Vec::new();
99//! let message = Message::new(&mut signed_message);
100//! let message = Signer::new(message, keypair)?.build()?;
101//! let mut message = LiteralWriter::new(message).build()?;
102//! message.write_all(b"I love you!")?;
103//! message.finalize()?;
104//!
105//! # Ok(()) });
106//! ```
107use std::{
108    convert::TryFrom,
109    path::Path,
110    path::PathBuf,
111    ops::Deref,
112    ops::DerefMut,
113    pin::Pin,
114};
115
116use sequoia_openpgp as openpgp;
117use sequoia_ipc as ipc;
118use openpgp::{
119    Cert,
120    cert::ValidCert,
121    crypto::{
122        self,
123        Password,
124        S2K,
125        mem::Protected,
126        mpi::SecretKeyChecksum,
127    },
128    fmt::hex,
129    packet::{
130        Key,
131        key::{
132            KeyRole,
133            PublicParts,
134            SecretParts,
135            UnspecifiedRole,
136            SecretKeyMaterial,
137        },
138        SKESK,
139    },
140    policy::Policy,
141    types::{
142        HashAlgorithm,
143        Timestamp,
144    },
145};
146use ipc::{
147    Keygrip,
148    sexp::Sexp,
149};
150
151use futures::stream::StreamExt;
152
153#[macro_use]
154mod macros;
155
156mod babel;
157mod utils;
158
159pub mod assuan;
160use assuan::Response;
161use assuan::escape;
162
163pub mod gnupg;
164pub use gnupg::Context;
165
166pub mod keyinfo;
167pub mod cardinfo;
168
169#[cfg(test)]
170mod tests;
171
172trace_module!(TRACE);
173
174/// Reexport sequoia_ipc.
175///
176/// Some of our public types use types defined in this crate.
177pub use sequoia_ipc;
178
179#[derive(thiserror::Error, Debug)]
180/// Errors used in this module.
181#[non_exhaustive]
182pub enum Error {
183    /// GnuPG's home directory doesn't exist.
184    #[error("GnuPG's home directory ({0}) doesn't exist")]
185    GnuPGHomeMissing(PathBuf),
186    /// Unknown key.
187    #[error("Unknown key: {0}")]
188    UnknownKey(Keygrip),
189    /// No smartcards are connected.
190    #[error("No smartcards are connected")]
191    NoSmartcards,
192    /// The key already exists.
193    #[error("{0} already exists: {1}")]
194    KeyExists(Keygrip, String),
195
196    #[error(transparent)]
197    Io(#[from] std::io::Error),
198
199    #[error(transparent)]
200    Utf8(#[from] std::str::Utf8Error),
201
202    #[error(transparent)]
203    Assuan(#[from] assuan::Error),
204
205    #[error(transparent)]
206    GnuPG(#[from] gnupg::Error),
207
208    #[error(transparent)]
209    KeyInfo(#[from] keyinfo::Error),
210
211    #[error(transparent)]
212    OpenPGP(#[from] openpgp::Error),
213
214    #[error(transparent)]
215    Other(#[from] anyhow::Error),
216}
217
218type Result<R, E=Error> = std::result::Result<R, E>;
219
220/// Controls how gpg-agent inquires passwords.
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum PinentryMode {
223    /// Ask using pinentry.  This is the default.
224    Ask,
225    /// Cancel all inquiries.
226    Cancel,
227    /// Refuse all inquiries.
228    Error,
229    /// Ask the frontend (us) for passwords.
230    Loopback,
231}
232
233impl Default for PinentryMode {
234    fn default() -> Self {
235        PinentryMode::Ask
236    }
237}
238
239impl PinentryMode {
240    /// Returns a string representation usable with the gpg-agent.
241    pub fn as_str(&self) -> &'static str {
242        match self {
243            PinentryMode::Ask => "ask",
244            PinentryMode::Cancel => "cancel",
245            PinentryMode::Error => "error",
246            PinentryMode::Loopback => "loopback",
247        }
248    }
249}
250
251impl std::fmt::Display for PinentryMode {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        write!(f, "{}", self.as_str())
254    }
255}
256
257impl std::str::FromStr for PinentryMode {
258    type Err = anyhow::Error;
259
260    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
261        match s.to_lowercase().as_str() {
262            "ask" => Ok(PinentryMode::Ask),
263            "default" => Ok(PinentryMode::Ask),
264            "cancel" => Ok(PinentryMode::Cancel),
265            "error" => Ok(PinentryMode::Error),
266            "loopback" => Ok(PinentryMode::Loopback),
267            _ => Err(anyhow::anyhow!("Unknown pinentry mode {:?}", s)),
268        }
269    }
270}
271
272fn trace_data_sent(data: &[u8]) {
273    tracer!(TRACE, "trace_data_sent");
274    let mut data = stfu8::encode_u8(data);
275    if data.len() > 80 && data.starts_with("D ") {
276        data = format!("{}... ({} bytes)",
277                       data.chars().take(65).collect::<String>(),
278                       data.len());
279    }
280    t!("SENT: {}", data);
281}
282
283fn trace_data_received(data: &[u8]) {
284    tracer!(TRACE, "trace_data_received");
285    let mut data = stfu8::encode_u8(data);
286    if data.len() > 80 && data.starts_with("D ") {
287        data = format!("{}... ({} bytes)",
288                       data.chars().take(65).collect::<String>(),
289                       data.len());
290    }
291    t!("RECV: {}", data);
292}
293
294/// A connection to a GnuPG agent.
295#[derive(Debug)]
296pub struct Agent {
297    socket_path: PathBuf,
298    c: assuan::Client,
299    pinentry_mode: Option<PinentryMode>,
300
301    /// Agent version.
302    version: String,
303
304    /// Whether this is an restricted connection.
305    restricted: bool,
306}
307
308impl Deref for Agent {
309    type Target = assuan::Client;
310
311    fn deref(&self) -> &Self::Target {
312        &self.c
313    }
314}
315
316impl DerefMut for Agent {
317    fn deref_mut(&mut self) -> &mut Self::Target {
318        &mut self.c
319    }
320}
321
322impl futures::Stream for Agent {
323    type Item = Result<assuan::Response>;
324
325    /// Attempt to pull out the next value of this stream, returning
326    /// None if the stream is finished.
327    ///
328    /// Note: It _is_ safe to call this again after the stream
329    /// finished, i.e. returned `Ready(None)`.
330    fn poll_next(mut self: Pin<&mut Self>, cx: &mut futures::task::Context<'_>)
331        -> futures::task::Poll<Option<Self::Item>>
332    {
333        Pin::new(&mut self.c).poll_next(cx)
334    }
335}
336
337impl Agent {
338    /// Connects to the `gpg-agent` serving the specified context.
339    ///
340    /// If a `gpg-agent` isn't listening on the specified context,
341    /// this function tries to start one.
342    ///
343    /// The GnuPG context, [`gnupg::Context`], specifies the GnuPG
344    /// home directory.
345    pub async fn connect(ctx: &gnupg::Context) -> Result<Self> {
346        tracer!(TRACE, "connect");
347
348        async fn transaction(ctx: &gnupg::Context) -> Result<Agent> {
349            t!("Starting daemon if not running");
350
351            // If the home directory doesn't exist, gpg-agent won't
352            // start, and we'll later get obscure errors.  Check
353            // eagerly so avoid confusing the eagerly.
354            let homedir = ctx.homedir();
355
356            // Disabled on Windows.  Re-enable when this issue is fixed:
357            //
358            // https://gitlab.com/sequoia-pgp/sequoia/-/issues/1093
359            //
360            // SEE OTHER INSTANCES OF THIS BELOW (search for comment).
361            #[cfg(not(windows))]
362            if let Some(homedir) = homedir {
363                if ! homedir.exists() {
364                    return Err(Error::GnuPGHomeMissing(
365                        homedir.to_path_buf()).into());
366                }
367            }
368
369            // Try to start gpg-agent.
370            ctx.start("gpg-agent")?;
371
372            t!("Connecting to daemon");
373            let path = ctx.socket("agent")?;
374            Agent::connect_to_agent(path).await
375        }
376
377        transaction(ctx).await.map_err(|e| {
378            t!("failed: {}", e);
379            e
380        })
381    }
382
383    /// Connects to the `gpg-agent` serving the specified context.
384    ///
385    /// If a `gpg-agent` isn't listening on the specified context,
386    /// this function tries to start one.
387    ///
388    /// A default [`gnupg::Context`] with the specified home directory
389    /// is used as the context.
390    pub async fn connect_to<P>(homedir: P) -> Result<Self>
391        where P: AsRef<Path>
392    {
393        let ctx = gnupg::Context::with_homedir(homedir)?;
394        Self::connect(&ctx).await
395    }
396
397    /// Connects to the default `gpg-agent`.
398    ///
399    /// If a `gpg-agent` isn't listening on the specified context,
400    /// this function tries to start one.
401    ///
402    /// A default [`gnupg::Context`] with the default home directory
403    /// is used as the context.
404    pub async fn connect_to_default() -> Result<Self>
405    {
406        let ctx = gnupg::Context::new()?;
407        Self::connect(&ctx).await
408    }
409
410    /// Connects to a new, ephemeral `gpg-agent`.
411    ///
412    /// A default [`gnupg::Context`] with an ephemeral home directory
413    /// is used as the context.
414    pub async fn connect_to_ephemeral() -> Result<Self>
415    {
416        let ctx = gnupg::Context::ephemeral()?;
417        Self::connect(&ctx).await
418    }
419
420    /// Connects to the `gpg-agent` listening on the specified socket.
421    ///
422    /// If a `gpg-agent` isn't listening on the specified socket,
423    /// this function does *not* try to start one.
424    pub async fn connect_to_agent<P>(socket_path: P) -> Result<Self>
425        where P: AsRef<Path>
426    {
427        let socket_path = socket_path.as_ref();
428
429        let mut connection = assuan::Client::connect(socket_path).await?;
430        if TRACE.load(std::sync::atomic::Ordering::Relaxed) {
431            connection.trace_data_sent(Box::new(trace_data_sent));
432            connection.trace_data_received(Box::new(trace_data_received));
433        }
434
435        let mut agent = Agent {
436            socket_path: socket_path.to_path_buf(),
437            c: connection,
438            pinentry_mode: None,
439            restricted: false,
440            version: "unknown".into(),
441        };
442
443        // Check whether this is an restricted connection.
444        let restricted =
445            agent.send_simple("GETINFO restricted").await.is_ok();
446        agent.restricted = restricted;
447
448        if let Ok(v) = agent.get_agent_version().await {
449            agent.version = v;
450        }
451
452        Ok(agent)
453    }
454
455    /// Returns the keys managed by the agent.
456    async fn get_agent_version(&mut self) -> Result<String> {
457        self.send("GETINFO version")?;
458
459        let mut version = Vec::new();
460        while let Some(response) = self.next().await {
461            let response = response?;
462            match response {
463                Response::Ok { .. } => break,
464                Response::Comment { .. } => (),
465                Response::Data { partial } =>
466                    version.extend_from_slice(&partial[..]),
467                Response::Error { message, .. } =>
468                    return self.operation_failed(&message).await,
469                response =>
470                    return protocol_error(&response),
471            }
472        }
473
474        self.next().await; // Dummy read to send END.
475        Ok(String::from_utf8(version).map_err(|e| e.utf8_error())?)
476    }
477
478    /// Returns whether this is an restricted connection.
479    pub fn is_restricted(&self) -> bool {
480        self.restricted
481    }
482
483    /// Tells the gpg-agent to reload its configuration file.
484    pub async fn reload(&mut self) -> Result<()>
485    {
486        self.send_simple("RELOADAGENT").await?;
487        Ok(())
488    }
489
490    /// Overrides the pinentry mode.
491    pub fn set_pinentry_mode(&mut self, mode: PinentryMode) {
492        self.pinentry_mode = Some(mode);
493    }
494
495    /// Disables gpg's pinentry.
496    ///
497    /// Changes the pinentry mode to `PinEntryMode::Error`, which
498    /// configures the agent to not ask for a password.
499    pub fn suppress_pinentry(mut self) -> Self {
500        self.pinentry_mode = Some(PinentryMode::Error);
501        self
502    }
503
504    /// Returns whether the agent has a secret key.
505    pub async fn has_key(&mut self,
506                         key: &Key<PublicParts, UnspecifiedRole>)
507                         -> Result<bool>
508    {
509        let grip = Keygrip::of(key.mpis())?;
510        Ok(self.send_simple(format!("HAVEKEY {}", grip)).await.is_ok())
511    }
512
513    /// Returns the keys managed by the agent.
514    pub async fn list_keys(&mut self) -> Result<keyinfo::KeyInfoList> {
515        self.send("KEYINFO --list")?;
516
517        let mut keys = Vec::new();
518        while let Some(response) = self.next().await {
519            let response = response?;
520            match response {
521                Response::Ok { .. } => break,
522                Response::Comment { .. } => (),
523                Response::Status { ref keyword, ref message } => {
524                    if keyword == "KEYINFO" {
525                        keys.push(keyinfo::KeyInfo::parse(message)?)
526                    } else {
527                        return protocol_error(&response);
528                    }
529                }
530                // KEYINFO should not send an inquire.
531                Response::Inquire { .. } => return protocol_error(&response),
532                Response::Error { ref message, .. } =>
533                    return self.operation_failed(message).await,
534                response =>
535                    return protocol_error(&response),
536            }
537        }
538
539        self.next().await; // Dummy read to send END.
540        Ok(keyinfo::KeyInfoList::from_iter(keys.into_iter()))
541    }
542
543    /// Returns information about a key managed by the agent.
544    pub async fn key_info(&mut self, keygrip: &Keygrip) -> Result<keyinfo::KeyInfo> {
545        self.send(format!("KEYINFO {}", keygrip.to_string()))?;
546
547        let mut keyinfo = None;
548        while let Some(response) = self.next().await {
549            let response = response?;
550            match response {
551                Response::Ok { .. } => break,
552                Response::Comment { .. } => (),
553                Response::Status { ref keyword, ref message } => {
554                    if keyword == "KEYINFO" {
555                        if keyinfo.is_none() {
556                            keyinfo = Some(keyinfo::KeyInfo::parse(message)?);
557                        } else {
558                            // Only expect exactly one keyinfo line.
559                            return protocol_error(&response);
560                        }
561                    } else {
562                        return protocol_error(&response);
563                    }
564                }
565                // KEYINFO should not send an inquire.
566                Response::Inquire { .. } => return protocol_error(&response),
567                Response::Error { ref message, .. } =>
568                    return self.operation_failed(message).await,
569                response =>
570                    return protocol_error(&response),
571            }
572        }
573
574        self.next().await; // Dummy read to send END.
575        if let Some(keyinfo) = keyinfo {
576            Ok(keyinfo)
577        } else {
578            Err(Error::UnknownKey(keygrip.clone()).into())
579        }
580    }
581
582    /// Returns information about any *connected* smartcards.
583    ///
584    /// This sends `LEARN --sendinfo` to gpg-agent.
585    pub async fn card_info(&mut self) -> Result<cardinfo::CardInfo> {
586        self.send("learn --sendinfo")?;
587
588        let mut raw: Vec<(String, String)> = Vec::new();
589        while let Some(response) = self.next().await {
590            let response = response?;
591            match response {
592                Response::Ok { .. } => break,
593                Response::Comment { .. } => (),
594                Response::Status { ref keyword, ref message } => {
595                    // Skip progress lines; they are just informative.
596                    if keyword != "PROGRESS" {
597                        raw.push((keyword.clone(), message.clone()));
598                    }
599                }
600                // KEYINFO should not send an inquire.
601                Response::Inquire { .. } => return protocol_error(&response),
602                Response::Error { ref message, .. } => {
603                    if message.as_ref().map(|m| m.starts_with("100663406 "))
604                        .unwrap_or(false)
605                    {
606                        // Card removed.
607                        return self.operation_failed_as(
608                            Error::NoSmartcards.into()).await;
609                    } else {
610                        return self.operation_failed(message).await;
611                    }
612                }
613                response =>
614                    return protocol_error(&response),
615            }
616        }
617
618        self.next().await; // Dummy read to send END.
619
620        if raw.is_empty() {
621            Err(Error::NoSmartcards.into())
622        } else {
623            cardinfo::CardInfo::parse(raw)
624        }
625    }
626
627    /// Imports a secret key into the agent.
628    ///
629    /// `key` is the secret key that will be imported.
630    ///
631    /// `policy` and `cert` are decorative.  They are only used to
632    /// create a better password prompt.
633    ///
634    /// `unattended` tells `gpg-agent` whether it should import the
635    /// key without reencrypting it.  If `false`, the user is prompted
636    /// to decrypt the secret key material, and it is reencrypted.
637    ///
638    /// `overwrite` tells the agent to overwrite an existing version
639    /// of the key.  If `overwrite` is not set, and a variant of the
640    /// key already exists, then `Error::KeyExists` is returned.
641    pub async fn import(&mut self,
642                        policy: &dyn Policy,
643                        cert: &Cert,
644                        key: &Key<SecretParts, UnspecifiedRole>,
645                        unattended: bool,
646                        overwrite: bool)
647                        -> Result<bool>
648    {
649        use ipc::sexp::*;
650
651        let keygrip = Keygrip::of(key.mpis())?;
652
653        /// Makes a tuple cell, i.e. a *Cons*.
654        fn c(name: &str, data: &[u8]) -> Sexp {
655            Sexp::List(vec![Sexp::String(name.as_bytes().into()),
656                            Sexp::String(data.into())])
657        }
658
659        /// Makes a tuple cell with a string value, i.e. a *String* cons.
660        fn s(name: &str, data: impl ToString) -> Sexp {
661            c(name, data.to_string().as_bytes())
662        }
663
664        fn add_signed_mpi(list: &mut Vec<Sexp>, v: &[u8]) {
665            let mut v = v.to_vec();
666
667            // If the high bit is set, we need to prepend a zero byte,
668            // otherwise the agent will interpret the value as signed, and
669            // thus negative.
670            if v[0] & 0x80 > 0 {
671                v.insert(0, 0);
672            }
673
674            add_raw(list, "_", &v);
675        }
676
677        fn add(list: &mut Vec<Sexp>, mpi: &mpi::MPI) {
678            add_signed_mpi(list, mpi.value());
679        }
680        fn addp(list: &mut Vec<Sexp>, checksum: &mut u16, mpi: &mpi::ProtectedMPI) {
681            add_signed_mpi(list, mpi.value());
682
683            use openpgp::serialize::MarshalInto;
684            *checksum = checksum.wrapping_add(
685                mpi.to_vec().expect("infallible").iter()
686                    .fold(0u16, |acc, v| acc.wrapping_add(*v as u16)));
687        }
688
689        fn add_ecc_scalar(list: &mut Vec<Sexp>,
690                          mode: EccScalarChecksumMode,
691                          checksum: &mut u16,
692                          s: &mpi::ProtectedMPI)
693        {
694            match mode {
695                EccScalarChecksumMode::MPI =>
696                    addp(list, checksum, s),
697
698                EccScalarChecksumMode::SOS => {
699                    let mut v = s.value().to_vec();
700
701                    // If the high bit is set, we need to prepend a
702                    // zero byte, otherwise the agent will interpret
703                    // the value as signed, and thus negative.
704                    if *v.get(0).unwrap_or(&0) & 0x80 > 0 {
705                        v.insert(0, 0);
706                    }
707
708                    add_raw(list, "_", &v);
709
710                    // Compute the bit length.
711                    let nbits = v.len() as u32 * 8
712                        - s.value().get(0).map(|o| o.leading_zeros())
713                        .unwrap_or(0);
714
715                    // Compute the checksum.
716                    *checksum = checksum
717                        .wrapping_add(nbits as u16 >> 8)
718                        .wrapping_add(nbits as u16 & 0xff)
719                        .wrapping_add(v.iter().fold(
720                            0u16, |acc, v| acc.wrapping_add(*v as u16)));
721                },
722            }
723        }
724
725        fn add_raw(list: &mut Vec<Sexp>, name: &str, data: &[u8]) {
726            list.push(Sexp::String(name.into()));
727            list.push(Sexp::String(data.into()));
728        }
729
730        use openpgp::crypto::mpi::{self, PublicKey};
731        let mut skey = vec![Sexp::String("skey".into())];
732        let curve = match key.mpis() {
733            PublicKey::RSA { e, n, } => {
734                add(&mut skey, n);
735                add(&mut skey, e);
736                None
737            },
738            PublicKey::DSA { p, q, g, y, } => {
739                add(&mut skey, p);
740                add(&mut skey, q);
741                add(&mut skey, g);
742                add(&mut skey, y);
743                None
744            },
745            PublicKey::ElGamal { p, g, y, } => {
746                add(&mut skey, p);
747                add(&mut skey, g);
748                add(&mut skey, y);
749                None
750            },
751            PublicKey::EdDSA { curve, q, }
752            | PublicKey::ECDSA { curve, q, }
753            | PublicKey::ECDH { curve, q, .. } => {
754                add(&mut skey, q);
755                Some(curve.clone())
756            },
757            PublicKey::Unknown { mpis, rest, } => {
758                for m in mpis.iter() {
759                    add(&mut skey, m);
760                }
761                add_raw(&mut skey, "_", rest);
762                None
763            },
764            _ => return
765                Err(openpgp::Error::UnsupportedPublicKeyAlgorithm(key.pk_algo())
766                    .into()),
767        };
768
769        // Now we append the secret bits.  We also compute a checksum over
770        // the MPIs.
771        let mut checksum = 0u16;
772        let protection = match key.secret() {
773            SecretKeyMaterial::Encrypted(e) => {
774                let mut p =
775                    vec![Sexp::String("protection".into())];
776                p.push(Sexp::String(match e.checksum() {
777                    Some(SecretKeyChecksum::SHA1) => "sha1",
778                    Some(SecretKeyChecksum::Sum16) => "sum",
779                    None => "none", // XXX: does that happen?
780                }.into()));
781                p.push(Sexp::String(babel::Fish(e.algo()).to_string().as_str().into()));
782
783                let iv_len = e.algo().block_size().unwrap_or(0);
784                let iv = e.ciphertext().map(|c| &c[..iv_len.min(c.len())])
785                    .unwrap_or(&[]);
786                p.push(Sexp::String(iv.into()));
787
788                #[allow(deprecated)]
789                match e.s2k() {
790                    S2K::Iterated { hash, salt, hash_bytes, } => {
791                        p.push(Sexp::String("3".into()));
792                        p.push(Sexp::String(babel::Fish(*hash).to_string().as_str().into()));
793                        p.push(Sexp::String(salt[..].into()));
794                        p.push(Sexp::String(
795                            utils::s2k_encode_iteration_count(*hash_bytes)
796                                .unwrap_or_default().to_string().as_str().into()));
797                    },
798                    S2K::Salted { hash, salt } => {
799                        p.push(Sexp::String("1".into()));
800                        p.push(Sexp::String(babel::Fish(*hash).to_string().as_str().into()));
801                        p.push(Sexp::String(salt[..].into()));
802                        p.push(Sexp::String("0".into()));
803                    },
804                    S2K::Simple { hash } => {
805                        p.push(Sexp::String("0".into()));
806                        p.push(Sexp::String(babel::Fish(*hash).to_string().as_str().into()));
807                        p.push(Sexp::String([][..].into()));
808                        p.push(Sexp::String("0".into()));
809                    },
810                    S2K::Private { .. } | S2K::Unknown { .. } | _ => {
811                        return Err(anyhow::anyhow!("Unsupported protection mode").into());
812                    },
813                }
814
815                if let Ok(c) = e.ciphertext() {
816                    skey.push(Sexp::String("e".into()));
817                    // We must omit the IV here.
818                    skey.push(Sexp::String(c[iv_len.min(c.len())..].into()));
819                } else {
820                    return Err(anyhow::anyhow!("Failed to parse ciphertext").into());
821                }
822
823                Sexp::List(p)
824            },
825            SecretKeyMaterial::Unencrypted(u) => {
826                u.map(|s| match s {
827                    mpi::SecretKeyMaterial::RSA { d, p, q, u, } => {
828                        addp(&mut skey, &mut checksum, d);
829                        addp(&mut skey, &mut checksum, p);
830                        addp(&mut skey, &mut checksum, q);
831                        addp(&mut skey, &mut checksum, u);
832                    },
833                    mpi::SecretKeyMaterial::DSA { x, }
834                    | mpi::SecretKeyMaterial::ElGamal { x, } =>
835                        addp(&mut skey, &mut checksum, x),
836
837                    mpi::SecretKeyMaterial::EdDSA { scalar, }
838                    | mpi::SecretKeyMaterial::ECDSA { scalar, }
839                    | mpi::SecretKeyMaterial::ECDH { scalar, } => {
840                        let mode = EccScalarChecksumMode::for_version(
841                            &self.version);
842                        add_ecc_scalar(&mut skey, mode, &mut checksum, scalar);
843                    },
844
845                    mpi::SecretKeyMaterial::Unknown { mpis, rest, } => {
846                        for m in mpis.iter() {
847                            addp(&mut skey, &mut checksum, m);
848                        }
849                        add_raw(&mut skey, "_", rest);
850                        checksum = checksum.wrapping_add(
851                            rest.iter()
852                                .fold(0u16, |acc, v| acc.wrapping_add(*v as u16)));
853                    },
854                    _ => (), // XXX This will fail anyway.
855                });
856                s("protection", "none")
857            },
858        };
859
860        let mut transfer_key = vec![
861            Sexp::String("openpgp-private-key".into()),
862            s("version", key.version()),
863            s("algo", babel::Fish(key.pk_algo())), // XXX does that map correctly?
864        ];
865        if let Some(curve) = curve {
866            transfer_key.push(s("curve", curve.to_string()));
867        }
868        transfer_key.push(Sexp::List(skey));
869        transfer_key.push(s("csum", checksum));
870        transfer_key.push(protection);
871
872        let transfer_key = Sexp::List(transfer_key);
873
874        // Pad to a multiple of 64 bits so that we can AESWRAP it.
875        let mut buf = Vec::new();
876        transfer_key.serialize(&mut buf)?;
877        while buf.len() % 8 > 0 {
878            buf.push(0);
879        }
880        let padded_transfer_key = Protected::from(buf);
881
882        self.send_simple(
883            format!("SETKEYDESC {}",
884                    escape(Self::make_import_prompt(policy, cert, key)))).await?;
885
886        // Get the Key Encapsulation Key for transferring the key.
887        let kek = self.send_simple("KEYWRAP_KEY --import").await?;
888
889        // Now encrypt the key.
890        let encrypted_transfer_key = openpgp::crypto::ecdh::aes_key_wrap(
891            openpgp::types::SymmetricAlgorithm::AES128,
892            &kek,
893            &padded_transfer_key)?;
894        assert_eq!(padded_transfer_key.len() + 8, encrypted_transfer_key.len());
895
896        // Did we import it?
897        let mut imported = false;
898
899        // And send it!
900        if let Some(mode) = self.pinentry_mode.as_ref().map(|m| m.to_string()) {
901            self.send_simple(
902                format!("OPTION pinentry-mode={}", mode)).await?;
903        }
904        self.send(format!("IMPORT_KEY --timestamp={}{}{}",
905                           chrono::DateTime::<chrono::Utc>::from(key.creation_time())
906                           .format("%Y%m%dT%H%M%S"),
907                           if unattended { " --unattended" } else { "" },
908                           if overwrite { " --force" } else { "" },
909        ))?;
910        while let Some(response) = self.next().await {
911            match response? {
912                Response::Ok { .. }
913                | Response::Comment { .. }
914                | Response::Status { .. } =>
915                    (), // Ignore.
916                Response::Inquire { keyword, .. } => {
917                    match keyword.as_str() {
918                        "KEYDATA" => {
919                            self.data(&encrypted_transfer_key)?;
920                            // Dummy read to send data.
921                            self.next().await;
922
923                            // Then, handle the inquiry.
924                            while let Some(r) = self.next().await {
925                                match r? {
926                                    // May send CACHE_NONCE
927                                    Response::Status { .. } =>
928                                        (), // Ignore.
929                                    Response::Ok { .. } => {
930                                        imported = true;
931                                        break;
932                                    },
933                                    // May send PINENTRY_LAUNCHED when
934                                    // importing locked keys.
935                                    Response::Inquire { .. } =>
936                                        self.acknowledge_inquiry().await?,
937                                    Response::Error { code, message } => {
938                                        match code {
939                                            0x4008023 => {
940                                                // Key exists.
941                                                return self.operation_failed_as(
942                                                    Error::KeyExists(
943                                                        keygrip.clone(),
944                                                        message.unwrap_or_else(|| {
945                                                            "key exists".into()
946                                                        })).into()
947                                                ).await;
948                                            }
949                                            _ => {
950                                                return self.operation_failed(
951                                                    &message).await;
952                                            },
953                                        }
954                                    },
955                                    response =>
956                                        return protocol_error(&response),
957                                }
958                            }
959
960                            // Sending the data acknowledges the inquiry.
961                        },
962                        _ => self.acknowledge_inquiry().await?,
963                    }
964                },
965                Response::Error { ref message, .. } =>
966                    return self.operation_failed(message).await,
967                response =>
968                    return protocol_error(&response),
969            }
970        }
971
972        Ok(imported)
973    }
974
975    fn make_import_prompt(policy: &dyn Policy, cert: &Cert,
976                          key: &Key<SecretParts, UnspecifiedRole>)
977                          -> String
978    {
979        let primary_id = cert.keyid();
980        let keyid = key.keyid();
981        let uid = utils::best_effort_primary_uid(policy, cert);
982
983        match (primary_id == keyid, Some(uid)) {
984            (true, Some(uid)) => format!(
985                "Please enter the passphrase to \
986                 unlock the OpenPGP secret key:\n\
987                 {}\n\
988                 ID {:X}, created {}.",
989                uid,
990                keyid,
991                Timestamp::try_from(key.creation_time())
992                    .expect("creation time is representable"),
993            ),
994            (false, Some(uid)) => format!(
995                "Please enter the passphrase to \
996                 unlock the OpenPGP secret key:\n\
997                 {}\n\
998                 ID {:X}, created {} (main key ID {}).",
999                uid,
1000                keyid,
1001                Timestamp::try_from(key.creation_time())
1002                    .expect("creation time is representable"),
1003                primary_id,
1004            ),
1005            (true, None) => format!(
1006                "Please enter the passphrase to \
1007                 unlock the OpenPGP secret key:\n\
1008                 ID {:X}, created {}.",
1009                keyid,
1010                Timestamp::try_from(key.creation_time())
1011                    .expect("creation time is representable"),
1012            ),
1013            (false, None) => format!(
1014                "Please enter the passphrase to \
1015                 unlock the OpenPGP secret key:\n\
1016                 ID {:X}, created {} (main key ID {}).",
1017                keyid,
1018                Timestamp::try_from(key.creation_time())
1019                    .expect("creation time is representable"),
1020                primary_id,
1021            ),
1022        }
1023    }
1024
1025    /// Presets the password for a key.
1026    ///
1027    /// This does not check that the password is correct.
1028    ///
1029    /// This will fail if the user has not enabled presetting
1030    /// passwords.  In particular, the user must explicitly opt-in by
1031    /// adding `allow-preset-passphrase` to their `gpg-agent.conf`.
1032    /// If this is not set, which will usually be the case since it is
1033    /// not the default, this operation will fail and return
1034    /// [`assuan::Error::OperationFailed`].
1035    pub async fn preset_passphrase(&mut self,
1036                                   keygrip: &Keygrip,
1037                                   password: Password)
1038        -> Result<()>
1039    {
1040        let escaped = password
1041            .map(|p| {
1042                p.iter().map(|b| format!("{:02X}", b))
1043                    .collect::<String>()
1044            });
1045        self.send_simple(
1046            format!("PRESET_PASSPHRASE {} -1 {}",
1047                    keygrip, escaped)).await
1048            .map_err(|err| {
1049                err
1050            })?;
1051
1052        Ok(())
1053    }
1054
1055    /// Makes the agent ask for a password.
1056    ///
1057    /// The callback is invoked once for each response.  If the
1058    /// callback returns an error, any outstanding inquiry is
1059    /// acknowledged, and the error is returned.
1060    ///
1061    /// The result of the callback is only used if gpg-agent sent an
1062    /// inquiry.  Otherwise, the result is silently ignored.
1063    ///
1064    /// If the response to an inquiry is `Ok(None)`, the inquiry is
1065    /// acknowledged, and this function waits for another response.
1066    pub async fn get_passphrase<P>(&mut self,
1067                                   cache_id: &Option<String>,
1068                                   err_msg: &Option<String>,
1069                                   prompt: Option<String>,
1070                                   desc_msg: Option<String>,
1071                                   newsymkey: bool,
1072                                   repeat: usize,
1073                                   check: bool,
1074                                   qualitybar: bool,
1075                                   mut pinentry_cb: P)
1076                                   -> Result<Password>
1077    where
1078        P: FnMut(&mut Agent, Response) -> Result<Option<Protected>>,
1079    {
1080        self.send(format!(
1081            "GET_PASSPHRASE --data --repeat={}{}{}{} -- {} {} {} {}",
1082            repeat,
1083            if (repeat > 0 && check) || newsymkey { " --check" } else { "" },
1084            if qualitybar { " --qualitybar" } else { "" },
1085            if newsymkey { " --newsymkey" } else { "" },
1086            cache_id.as_ref().map(escape).unwrap_or_else(|| "X".into()),
1087            err_msg.as_ref().map(escape).unwrap_or_else(|| "X".into()),
1088            prompt.as_ref().map(escape).unwrap_or_else(|| "X".into()),
1089            desc_msg.as_ref().map(escape).unwrap_or_else(|| "X".into()),
1090        ))?;
1091
1092        let mut password = Vec::new();
1093        while let Some(response) = self.next().await {
1094            match response? {
1095                r @ Response::Ok { .. }
1096                | r @ Response::Comment { .. }
1097                | r @ Response::Status { .. } => {
1098                    pinentry_cb(self, r)?;
1099                },
1100                r @ Response::Inquire { .. } => {
1101                    match pinentry_cb(self, r) {
1102                        Ok(Some(data)) => {
1103                            self.data(&data[..])?;
1104                            // Dummy read to send data.
1105                            while let Some(r) = self.next().await {
1106                                if matches!(r?, Response::Ok { .. }) {
1107                                    break;
1108                                }
1109                            }
1110
1111                            // Sending the data acknowledges the inquiry.
1112                        }
1113                        Ok(None) => {
1114                            self.acknowledge_inquiry().await?;
1115                        }
1116                        Err(err) => {
1117                            self.acknowledge_inquiry().await?;
1118                            return Err(err);
1119                        }
1120                    }
1121                },
1122                Response::Data { partial } => {
1123                    // Securely erase partial.
1124                    let partial = Protected::from(partial);
1125                    password.extend_from_slice(&partial);
1126                },
1127                Response::Error { ref message, .. } =>
1128                    return self.operation_failed(message).await,
1129            }
1130        }
1131        let password = Password::from(password);
1132
1133        Ok(password)
1134    }
1135
1136    /// Makes the agent forget a password.
1137    ///
1138    /// The cache_id is usually a keygrip, but gpg-agent can also
1139    /// cache the password for a SKESK.
1140    ///
1141    /// When a passphrase is cleared, `gpg-agent` launches a pinentry
1142    /// and sends it the `CLEARPASSPHRASE` command so that it also
1143    /// flushes the passphrase from its cache, if any.  You can see
1144    /// whether gpg does that by providing a callback.  It will be
1145    /// called for `PINENTRY_LAUNCHED` inquire line.  Most of the time
1146    /// you won't care and can just pass `|_| ()`.
1147    pub async fn forget_passphrase<C, P>(&mut self,
1148                                         cache_id: C,
1149                                         mut pinentry_cb: P)
1150                                         -> Result<()>
1151    where
1152        C: AsRef<str>,
1153        P: FnMut(Vec<u8>),
1154    {
1155        self.send(format!("CLEAR_PASSPHRASE {}", escape(cache_id.as_ref())))?;
1156        while let Some(response) = self.next().await {
1157            match response? {
1158                Response::Ok { .. }
1159                | Response::Comment { .. }
1160                | Response::Status { .. } =>
1161                    (), // Ignore.
1162                Response::Inquire { keyword, parameters } => {
1163                    match keyword.as_str() {
1164                        "PINENTRY_LAUNCHED" => {
1165                            pinentry_cb(parameters.unwrap_or_default());
1166                        },
1167                        _ => (),
1168                    }
1169                    self.acknowledge_inquiry().await?
1170                },
1171                Response::Error { ref message, .. } =>
1172                    return self.operation_failed(message).await,
1173                response =>
1174                    return protocol_error(&response),
1175            }
1176        }
1177        Ok(())
1178    }
1179
1180    /// Helper function to respond to inquiries.
1181    ///
1182    /// This function doesn't do something useful, but it ends the
1183    /// current inquiry.
1184    async fn acknowledge_inquiry(&mut self) -> Result<()> {
1185        self.send("END")?;
1186        self.next().await; // Dummy read to send END.
1187        Ok(())
1188    }
1189
1190    /// Returns a convenient Err value for use in the state machines.
1191    ///
1192    /// This function must only be called after the assuan server
1193    /// returns an ERR.  message is the error message returned from
1194    /// the server.  This function first checks that the server hasn't
1195    /// sent anything else, which would be a protocol violation.  If
1196    /// that is not the case, it turns the message into an Err.
1197    async fn operation_failed<T>(&mut self, message: &Option<String>)
1198                                 -> Result<T>
1199    {
1200        self.operation_failed_as(
1201            assuan::Error::OperationFailed(
1202                message.as_ref().map(|e| e.to_string())
1203                    .unwrap_or_else(|| "Unknown reason".into()))
1204                .into()).await
1205    }
1206
1207    async fn operation_failed_as<T>(&mut self, err: Error)
1208                                    -> Result<T>
1209    {
1210        tracer!(TRACE, "operation_failed_as");
1211
1212        if let Some(response) = self.next().await {
1213            t!("Got unexpected response {:?}", response);
1214            Err(assuan::Error::ProtocolError(
1215                format!("Got unexpected response {:?}", response))
1216                .into())
1217        } else {
1218            t!("Operation failed: {}", err);
1219            Err(err)
1220        }
1221    }
1222
1223    /// Computes options that we want to communicate.
1224    fn options(&self) -> Vec<String> {
1225        use std::env::var;
1226
1227        if self.is_restricted() {
1228            // We cannot set any of these options on restricted
1229            // connections.
1230            return vec![];
1231        }
1232
1233        let mut r = Vec::new();
1234
1235        if let Ok(tty) = var("GPG_TTY") {
1236            r.push(format!("OPTION ttyname={}", tty));
1237        } else {
1238            #[cfg(unix)]
1239            unsafe {
1240                use std::ffi::CStr;
1241                let tty = libc::ttyname(0);
1242                if ! tty.is_null() {
1243                    if let Ok(tty) = CStr::from_ptr(tty).to_str() {
1244                        r.push(format!("OPTION ttyname={}", tty));
1245                    }
1246                }
1247            }
1248        }
1249
1250        if let Ok(term) = var("TERM") {
1251            r.push(format!("OPTION ttytype={}", term));
1252        }
1253
1254        if let Ok(display) = var("DISPLAY") {
1255            r.push(format!("OPTION display={}", display));
1256        }
1257
1258        if let Ok(xauthority) = var("XAUTHORITY") {
1259            r.push(format!("OPTION xauthority={}", xauthority));
1260        }
1261
1262        if let Ok(dbus) = var("DBUS_SESSION_BUS_ADDRESS") {
1263            r.push(format!("OPTION putenv=DBUS_SESSION_BUS_ADDRESS={}", dbus));
1264        }
1265
1266        // We're going to pop() options off the end, therefore reverse
1267        // the vec here to preserve the above ordering, which is the
1268        // one GnuPG uses.
1269        r.reverse();
1270        r
1271    }
1272
1273    /// Returns a `KeyPair` for `key` with the secret bits managed by
1274    /// the agent.
1275    ///
1276    /// `KeyPair` implements `crypto::Signer` and `crypto::Decryptor`.
1277    /// This makes it easy for code to use secret key material managed
1278    /// by an instance of gpg agent from code that uses Sequoia.
1279    ///
1280    /// This function does not check that the agent actually manages
1281    /// the secret key material.  If the agent doesn't manage the
1282    /// secret key material, operations that attempt to use it will
1283    /// fail.  If necessary, you can use [`Agent::has_key`] to check
1284    /// if the agent manages the secret key material.
1285    pub fn keypair<R>(&self, key: &Key<PublicParts, R>)
1286        -> Result<KeyPair>
1287        where R: KeyRole
1288    {
1289        let mut pair = KeyPair::new_for_socket(&self.socket_path, key)?;
1290        pair.pinentry_mode = self.pinentry_mode.clone();
1291        Ok(pair)
1292    }
1293
1294    /// Exports a secret key from the agent.
1295    ///
1296    /// `key` is the secret key that will be exported.
1297    pub async fn export(&mut self, key: Key<PublicParts, UnspecifiedRole>)
1298        -> Result<Key<SecretParts, UnspecifiedRole>>
1299    {
1300        let keygrip = Keygrip::of(key.mpis())?;
1301
1302        let kek = self.send_simple("KEYWRAP_KEY --export").await?;
1303
1304        let encrypted_key = self.send_simple(
1305            format!("EXPORT_KEY {}", keygrip.to_string())).await?;
1306
1307        let secret_key = openpgp::crypto::ecdh::aes_key_unwrap(
1308            openpgp::types::SymmetricAlgorithm::AES128,
1309            &kek,
1310            &encrypted_key.as_ref())?;
1311
1312        // Strip any trailing NULs.  They are only there for padding
1313        // purposes.
1314        let mut secret_key = secret_key.as_ref();
1315        while ! secret_key.is_empty() && secret_key[secret_key.len() - 1] == 0 {
1316            secret_key = &secret_key[..secret_key.len() - 1];
1317        }
1318
1319        let sexp = Sexp::from_bytes(secret_key)?;
1320
1321        let secret_key = sexp.to_secret_key(Some(key.mpis()))?;
1322
1323        let (key, _) = key.add_secret(secret_key.into());
1324
1325        Ok(key)
1326    }
1327}
1328
1329/// ECC scalar checksum computation.
1330///
1331/// The gpg-agent shipped with GnuPG 2.4.x calculates the checksum
1332/// over ECC scalars differently.  This is side-effect of the
1333/// premature introduction of the simple-octet-string (SOS) encoding
1334/// into the GnuPG code.
1335///
1336/// See GnuPG commit 2b118516240b4bddd34c68c23a99bea56682a509.
1337enum EccScalarChecksumMode {
1338    /// Sum over the MPI wire encoding in u16.
1339    MPI,
1340
1341    /// Sum over the "opaque MPI encoding" and its bit length in u16.
1342    ///
1343    /// "Opaque MPI" is a gcrypt mode for MPIs that don't represent
1344    /// integers, and mustn't be used in arithmetic functions.  Oddly
1345    /// enough, if the left-most bit is set, gcrypt will prefix it
1346    /// with a zero byte, presumably to prevent it from being
1347    /// interpreted as a sign-bit.
1348    SOS,
1349}
1350
1351impl EccScalarChecksumMode {
1352    fn for_version(v: &str) -> EccScalarChecksumMode {
1353        if v.starts_with("2.0.")
1354            || v.starts_with("2.1.")
1355            || v.starts_with("2.2.")
1356        {
1357            EccScalarChecksumMode::MPI
1358        } else {
1359            EccScalarChecksumMode::SOS
1360        }
1361    }
1362}
1363
1364/// Returns a convenient Err value for use in the state machines
1365/// below.
1366fn protocol_error<T>(response: &Response) -> Result<T> {
1367    tracer!(TRACE, "operation_failed");
1368
1369    t!("Got unexpected response {:?}", response);
1370    Err(assuan::Error::ProtocolError(
1371        format!("Got unexpected response {:?}", response))
1372        .into())
1373}
1374
1375/// Computes the cache id for a SKESK.
1376///
1377/// If an S2K algorithm unsupported by the caching id algorithm is
1378/// given, this function returns `None`.
1379pub fn cacheid_of(s2k: &S2K) -> Option<String> {
1380    #[allow(deprecated)]
1381    let salt = match s2k {
1382        S2K::Iterated { salt, .. } => &salt[..8],
1383        S2K::Salted { salt, .. } => &salt[..8],
1384        _ => return None,
1385    };
1386
1387    Some(format!("S{}", openpgp::fmt::hex::encode(&salt)))
1388}
1389
1390/// Computes the cache id for a set of SKESKs.
1391///
1392/// GnuPG prompts for a password for each SKESK separately, and uses
1393/// the first eight bytes of salt from the S2K.  We ask for one
1394/// password and try it with every SKESK.  Therefore, we have to cache
1395/// that we asked for a set of SKESKs, i.e. this message.  To that
1396/// end, we xor the first eight bytes of salt from every S2K, matching
1397/// GnuPG's result in the common case of having just one SKESK.  Xor
1398/// is also nice because it is commutative, so the order of the SKESKs
1399/// doesn't matter.
1400///
1401/// Unsupported SKESK versions or S2K algorithms unsupported by the
1402/// caching id algorithm are ignored.  We cannot use them anyway.
1403///
1404/// Further, if no SKESKs are given, this function returns `None`.
1405pub fn cacheid_over_all(skesks: &[SKESK]) -> Option<String> {
1406    if skesks.is_empty() {
1407        return None;
1408    }
1409
1410    let mut cacheid = [0; 8];
1411
1412    for skesk in skesks {
1413        let s2k = match skesk {
1414            SKESK::V4(skesk) => skesk.s2k(),
1415            _ => continue,
1416        };
1417
1418        #[allow(deprecated)]
1419        let salt = match s2k {
1420            S2K::Iterated { salt, .. } => &salt[..8],
1421            S2K::Salted { salt, .. } => &salt[..8],
1422            _ => continue,
1423        };
1424
1425        cacheid.iter_mut().zip(salt.iter()).for_each(|(p, s)| *p ^= *s);
1426    }
1427
1428    Some(format!("S{}", openpgp::fmt::hex::encode(&cacheid)))
1429}
1430
1431/// A cryptographic key pair.
1432///
1433/// A `KeyPair` is a combination of public and secret key.  This
1434/// particular implementation does not have the secret key, but
1435/// diverges the cryptographic operations to `gpg-agent`.
1436///
1437/// This provides a convenient, synchronous interface for use with the
1438/// low-level Sequoia crate.
1439pub struct KeyPair {
1440    public: Key<PublicParts, UnspecifiedRole>,
1441    agent_socket: PathBuf,
1442    password: Option<crypto::Password>,
1443    pinentry_mode: Option<PinentryMode>,
1444    password_prompt: String,
1445    delete_prompt: String,
1446}
1447
1448impl KeyPair {
1449    fn default_password_prompt(key: &Key<PublicParts, UnspecifiedRole>)
1450        -> String
1451    {
1452        format!(
1453            "Please enter the passphrase to \
1454             unlock the OpenPGP secret key:\n\
1455             ID {:X}, created {}.",
1456            key.keyid(), Timestamp::try_from(key.creation_time()).unwrap())
1457    }
1458
1459    fn default_delete_prompt(key: &Key<PublicParts, UnspecifiedRole>)
1460        -> String
1461    {
1462        format!(
1463            "Do you really want to permanently delete the OpenPGP secret key:\n\
1464             ID {:X}, created {}.",
1465            key.keyid(), Timestamp::try_from(key.creation_time()).unwrap())
1466    }
1467
1468    /// Returns a `KeyPair` for `key` with the secret bits managed by
1469    /// the agent.
1470    ///
1471    /// This provides a convenient, synchronous interface for use with
1472    /// the low-level Sequoia crate.
1473    pub fn new_for_gnupg_context<R>(ctx: &gnupg::Context,
1474                                    key: &Key<PublicParts, R>)
1475        -> Result<KeyPair>
1476        where R: KeyRole
1477    {
1478        let key = key.role_as_unspecified();
1479        Ok(KeyPair {
1480            password: None,
1481            pinentry_mode: None,
1482            password_prompt: Self::default_password_prompt(key),
1483            delete_prompt: Self::default_delete_prompt(key),
1484            public: key.clone(),
1485            agent_socket: ctx.socket("agent")?.into(),
1486        })
1487    }
1488
1489    /// Returns a `KeyPair` for `key` with the secret bits managed by
1490    /// the agent.
1491    ///
1492    /// If you have a [`Agent`], then you should create a `KeyPair`
1493    /// using [`Agent::keypair`].
1494    pub fn new_for_socket<P, R>(agent_socket: P, key: &Key<PublicParts, R>)
1495        -> Result<KeyPair>
1496        where P: AsRef<Path>,
1497              R: KeyRole
1498    {
1499        let key = key.role_as_unspecified();
1500        Ok(KeyPair {
1501            password: None,
1502            pinentry_mode: None,
1503            password_prompt: Self::default_password_prompt(key),
1504            delete_prompt: Self::default_delete_prompt(key),
1505            public: key.clone(),
1506            agent_socket: agent_socket.as_ref().to_path_buf(),
1507        })
1508    }
1509
1510    /// Changes the password prompt to include information about the
1511    /// cert.
1512    ///
1513    /// Use this function to give more context to the user when she is
1514    /// prompted for a password.  This function will generate a prompt
1515    /// that is very similar to the prompts that GnuPG generates.
1516    ///
1517    /// To set an arbitrary password prompt, use
1518    /// [`KeyPair::with_password_prompt`].
1519    pub fn with_cert(self, cert: &ValidCert) -> Self {
1520        let primary_id = cert.keyid();
1521        let keyid = self.public.keyid();
1522        let password_prompt = match (primary_id == keyid,
1523                                     cert.primary_userid()
1524                                     .map(|uid| uid.clone())
1525                                     .ok())
1526        {
1527            (true, Some(uid)) => format!(
1528                "Please enter the passphrase to \
1529                 unlock the OpenPGP secret key:\n\
1530                 {}\n\
1531                 ID {:X}, created {}.",
1532                uid.userid(),
1533                keyid,
1534                Timestamp::try_from(self.public.creation_time())
1535                    .expect("creation time is representable"),
1536            ),
1537            (false, Some(uid)) => format!(
1538                "Please enter the passphrase to \
1539                 unlock the OpenPGP secret key:\n\
1540                 {}\n\
1541                 ID {:X}, created {} (main key ID {}).",
1542                uid.userid(),
1543                keyid,
1544                Timestamp::try_from(self.public.creation_time())
1545                    .expect("creation time is representable"),
1546                primary_id,
1547            ),
1548            (true, None) => format!(
1549                "Please enter the passphrase to \
1550                 unlock the OpenPGP secret key:\n\
1551                 ID {:X}, created {}.",
1552                keyid,
1553                Timestamp::try_from(self.public.creation_time())
1554                    .expect("creation time is representable"),
1555            ),
1556            (false, None) => format!(
1557                "Please enter the passphrase to \
1558                 unlock the OpenPGP secret key:\n\
1559                 ID {:X}, created {} (main key ID {}).",
1560                keyid,
1561                Timestamp::try_from(self.public.creation_time())
1562                    .expect("creation time is representable"),
1563                primary_id,
1564            ),
1565        };
1566
1567        let delete_prompt = match cert.primary_userid()
1568                                      .map(|uid| uid.clone())
1569                                      .ok()
1570        {
1571            Some(uid) => format!(
1572                "Do you really want to permanently delete the OpenPGP secret key:\n\
1573                 {}\n\
1574                 ID {:X}, created {}.",
1575                uid.userid(),
1576                keyid,
1577                Timestamp::try_from(self.public.creation_time())
1578                    .expect("creation time is representable"),
1579            ),
1580            None => format!(
1581                "Do you really want to permanently delete the OpenPGP secret key:\n\
1582                 ID {:X}, created {}.",
1583                keyid,
1584                Timestamp::try_from(self.public.creation_time())
1585                    .expect("creation time is representable"),
1586            ),
1587        };
1588
1589        self.with_password_prompt(password_prompt)
1590            .with_delete_prompt(delete_prompt)
1591    }
1592
1593    /// Supplies a password to unlock the secret key.
1594    ///
1595    /// This will be used when the secret key operation is performed,
1596    /// e.g. when signing or decrypting a message.
1597    ///
1598    /// Note: This is the equivalent of GnuPG's
1599    /// `--pinentry-mode=loopback` and requires explicit opt-in in the
1600    /// gpg-agent configuration using the `allow-loopback-pinentry`
1601    /// option.  If this is not enabled in the agent, the secret key
1602    /// operation will fail.  It is likely only useful during testing.
1603    pub fn with_password(mut self, password: crypto::Password) -> Self {
1604        self.password = Some(password);
1605        self
1606    }
1607
1608    /// Overrides the pinentry mode.
1609    pub fn set_pinentry_mode(mut self, mode: PinentryMode) -> Self {
1610        self.pinentry_mode = Some(mode);
1611        self
1612    }
1613
1614    /// Disables gpg's pinentry.
1615    ///
1616    /// Changes the pinentry mode to `PinEntryMode::Error`, which
1617    /// configures the agent to not ask for a password.
1618    pub fn suppress_pinentry(mut self) -> Self {
1619        self.pinentry_mode = Some(PinentryMode::Error);
1620        self
1621    }
1622
1623    /// Changes the password prompt.
1624    ///
1625    /// Use this function to give more context to the user when she is
1626    /// prompted for a password.
1627    ///
1628    /// To set an password prompt that uses information from the
1629    /// OpenPGP certificate, use [`KeyPair::with_cert`].
1630    pub fn with_password_prompt(mut self, prompt: String) -> Self {
1631        self.password_prompt = prompt;
1632        self
1633    }
1634
1635    /// Changes the delete prompt.
1636    ///
1637    /// Use this function to give more context to the user when she is
1638    /// prompted to delete a key.
1639    ///
1640    /// To set an password prompt that uses information from the
1641    /// OpenPGP certificate, use [`KeyPair::with_cert`].
1642    pub fn with_delete_prompt(mut self, prompt: String) -> Self {
1643        self.delete_prompt = prompt;
1644        self
1645    }
1646
1647    /// Changes the key's password.
1648    ///
1649    /// If `preset_password` is true, then the password will also be
1650    /// added to the password cache.
1651    pub async fn password(&mut self, preset_password: bool)
1652        -> Result<()>
1653    {
1654        let keygrip = Keygrip::of(self.public.mpis())?;
1655
1656        // Connect to the agent.
1657        let mut agent = Agent::connect_to_agent(&self.agent_socket).await?;
1658
1659        for option in agent.options() {
1660            agent.send_simple(option).await?;
1661        }
1662
1663        if let Some(mode) = agent.pinentry_mode.clone() {
1664            agent.send_simple(
1665                format!("OPTION pinentry-mode={}", mode.as_str())).await?;
1666        }
1667        agent.send_simple(
1668            format!("SETKEYDESC {}",
1669                    assuan::escape(&self.password_prompt))).await?;
1670
1671        agent.send_simple(
1672            format!("PASSWD {}{}",
1673                    if preset_password {
1674                        "--preset "
1675                    } else {
1676                        ""
1677                    },
1678                    keygrip.to_string()))
1679            .await?;
1680
1681        Ok(())
1682    }
1683
1684    /// Deletes the specified key.
1685    ///
1686    /// If `stub_only` is true, then the key will be removed if it is
1687    /// a stub that corresponds to a key on a token.
1688    pub async fn delete_key(&mut self, stub_only: bool)
1689        -> Result<()>
1690    {
1691        let keygrip = Keygrip::of(self.public.mpis())?;
1692
1693        // Connect to the agent.
1694        let mut agent = Agent::connect_to_agent(&self.agent_socket).await?;
1695
1696        for option in agent.options() {
1697            agent.send_simple(option).await?;
1698        }
1699
1700        if let Some(mode) = agent.pinentry_mode.clone() {
1701            agent.send_simple(
1702                format!("OPTION pinentry-mode={}", mode.as_str())).await?;
1703        }
1704        agent.send_simple(
1705            format!("SETKEYDESC {}",
1706                    assuan::escape(&self.delete_prompt))).await?;
1707
1708        agent.send_simple(
1709            format!("DELETE_KEY {}{}",
1710                    if stub_only {
1711                        "--stub-only "
1712                    } else {
1713                        ""
1714                    },
1715                    keygrip.to_string()))
1716            .await?;
1717
1718        Ok(())
1719    }
1720}
1721
1722impl KeyPair {
1723    /// Signs a message.
1724    ///
1725    /// An async implementation of
1726    /// [`sequoia_openpgp::crypto::Signer::sign`].
1727    pub async fn sign_async(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
1728        -> openpgp::Result<openpgp::crypto::mpi::Signature>
1729    {
1730        // Connect to the agent and do the signing
1731        // operation.
1732        let mut agent = Agent::connect_to_agent(&self.agent_socket).await?;
1733
1734        for option in agent.options() {
1735            agent.send_simple(option).await?;
1736        }
1737
1738        if self.password.is_some() {
1739            agent.send_simple("OPTION pinentry-mode=loopback").await?;
1740        } else if let Some(ref mode) = self.pinentry_mode {
1741            agent.send_simple(
1742                format!("OPTION pinentry-mode={}", mode.as_str())).await?;
1743        }
1744
1745        let grip = Keygrip::of(self.public.mpis())?;
1746        agent.send_simple(format!("SIGKEY {}", grip)).await?;
1747        agent.send_simple(
1748            format!("SETKEYDESC {}",
1749                    assuan::escape(&self.password_prompt))).await?;
1750
1751        let algo = u8::from(hash_algo);
1752        let digest = hex::encode(&digest);
1753        agent.send_simple(format!("SETHASH {} {}", algo, digest)).await?;
1754        agent.send("PKSIGN")?;
1755
1756        let mut data = Vec::new();
1757        while let Some(r) = agent.next().await {
1758            match r? {
1759                assuan::Response::Ok { .. }
1760                | assuan::Response::Comment { .. }
1761                | assuan::Response::Status { .. } =>
1762                    (), // Ignore.
1763                assuan::Response::Inquire { keyword, .. } =>
1764                    match (keyword.as_str(), &self.password) {
1765                        ("PASSPHRASE", Some(p)) => {
1766                            p.map(|p| agent.data(p))?;
1767                            // Dummy read to send the data.
1768                            agent.next().await;
1769                        },
1770                        _ => agent.acknowledge_inquiry().await?,
1771                    },
1772                assuan::Response::Error { ref message, .. } =>
1773                    return Ok(agent.operation_failed(message).await?),
1774                assuan::Response::Data { ref partial } =>
1775                    data.extend_from_slice(partial),
1776            }
1777        }
1778
1779        Sexp::from_bytes(&data)?.to_signature()
1780    }
1781}
1782
1783impl crypto::Signer for KeyPair {
1784    fn public(&self) -> &Key<PublicParts, UnspecifiedRole> {
1785        &self.public
1786    }
1787
1788    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
1789        -> openpgp::Result<openpgp::crypto::mpi::Signature>
1790    {
1791        use tokio::runtime::{Handle, Builder};
1792
1793        // See if the current thread is managed by a tokio
1794        // runtime.
1795        if Handle::try_current().is_err() {
1796            // Doesn't seem to be the case, so it is safe
1797            // to create a new runtime and block.
1798            let rt = Builder::new_current_thread()
1799                .enable_io()
1800                .build()?;
1801            rt.block_on(self.sign_async(hash_algo, digest))
1802        } else {
1803            // It is!  We must not create a second runtime
1804            // on this thread, but instead we will
1805            // delegate this to a new thread.
1806            std::thread::scope(|s| {
1807                s.spawn(move || {
1808                    let rt = Builder::new_current_thread()
1809                        .enable_io()
1810                        .build()?;
1811                    rt.block_on(self.sign_async(hash_algo, digest))
1812                }).join()
1813            }).map_err(map_panic)?
1814        }
1815    }
1816}
1817
1818impl KeyPair {
1819    /// Decrypts a message.
1820    ///
1821    /// An async implementation of
1822    /// [`sequoia_openpgp::crypto::Decryptor::decrypt`].
1823    pub async fn decrypt_async(&mut self, ciphertext: &crypto::mpi::Ciphertext,
1824                               plaintext_len: Option<usize>)
1825        -> openpgp::Result<crypto::SessionKey>
1826    {
1827        // Connect to the agent and do the decryption operation.
1828        let mut agent = Agent::connect_to_agent(&self.agent_socket).await?;
1829
1830        for option in agent.options() {
1831            agent.send_simple(option).await?;
1832        }
1833
1834        if self.password.is_some() {
1835            agent.send_simple("OPTION pinentry-mode=loopback").await?;
1836        } else if let Some(ref mode) = self.pinentry_mode {
1837            agent.send_simple(
1838                format!("OPTION pinentry-mode={}", mode.as_str())).await?;
1839        }
1840
1841        let grip = Keygrip::of(self.public.mpis())?;
1842        agent.send_simple(format!("SETKEY {}", grip)).await?;
1843        agent.send_simple(format!("SETKEYDESC {}",
1844                                  assuan::escape(&self.password_prompt))).await?;
1845        agent.send("PKDECRYPT")?;
1846        let mut padding = true;
1847        let mut data = Vec::new();
1848        while let Some(r) = agent.next().await {
1849            match r? {
1850                assuan::Response::Ok { .. } |
1851                assuan::Response::Comment { .. } =>
1852                    (), // Ignore.
1853                assuan::Response::Inquire { ref keyword, .. } =>
1854                    match (keyword.as_str(), &self.password) {
1855                        ("PASSPHRASE", Some(p)) => {
1856                            p.map(|p| agent.data(p))?;
1857                            // Dummy read to send the data.
1858                            agent.next().await;
1859                        },
1860                        ("CIPHERTEXT", _) => {
1861                            let mut buf = Vec::new();
1862                            Sexp::try_from(ciphertext)?.serialize(&mut buf)?;
1863                            agent.data(&buf)?;
1864                            // Dummy read to send the data.
1865                            agent.next().await;
1866                        },
1867                        _ => agent.acknowledge_inquiry().await?,
1868                    },
1869                assuan::Response::Status { ref keyword, ref message } =>
1870                    if keyword == "PADDING" {
1871                        padding = message != "0";
1872                    },
1873                assuan::Response::Error { ref message, .. } =>
1874                    return Ok(agent.operation_failed(message).await?),
1875                assuan::Response::Data { ref partial } =>
1876                    data.extend_from_slice(partial),
1877            }
1878        }
1879
1880        // Get rid of the safety-0.
1881        //
1882        // gpg-agent seems to add a trailing 0, supposedly for good
1883        // measure.
1884        if data.iter().last() == Some(&0) {
1885            let l = data.len();
1886            data.truncate(l - 1);
1887        }
1888
1889        Sexp::from_bytes(&data)?.finish_decryption(
1890            &self.public, ciphertext, plaintext_len, padding)
1891    }
1892}
1893
1894impl crypto::Decryptor for KeyPair {
1895    fn public(&self) -> &Key<PublicParts, UnspecifiedRole> {
1896        &self.public
1897    }
1898
1899    fn decrypt(&mut self, ciphertext: &crypto::mpi::Ciphertext,
1900               plaintext_len: Option<usize>)
1901        -> openpgp::Result<crypto::SessionKey>
1902    {
1903        use tokio::runtime::{Handle, Builder};
1904
1905        // See if the current thread is managed by a tokio
1906        // runtime.
1907        if Handle::try_current().is_err() {
1908            // Doesn't seem to be the case, so it is safe
1909            // to create a new runtime and block.
1910            let rt = Builder::new_current_thread()
1911                .enable_io()
1912                .build()?;
1913            rt.block_on(self.decrypt_async(ciphertext, plaintext_len))
1914        } else {
1915            // It is!  We must not create a second runtime
1916            // on this thread, but instead we will
1917            // delegate this to a new thread.
1918            std::thread::scope(|s| {
1919                s.spawn(move || {
1920                    let rt = Builder::new_current_thread()
1921                        .enable_io()
1922                        .build()?;
1923                    rt.block_on(self.decrypt_async(ciphertext, plaintext_len))
1924                }).join()
1925            }).map_err(map_panic)?
1926        }
1927    }
1928}
1929
1930/// Maps a panic of a worker thread to an error.
1931///
1932/// Unfortunately, there is nothing useful to do with the error, but
1933/// returning a generic error is better than panicking.
1934fn map_panic(_: Box<dyn std::any::Any + std::marker::Send>) -> anyhow::Error
1935{
1936    anyhow::anyhow!("worker thread panicked")
1937}
1938
1939#[cfg(test)]
1940mod test {
1941    use super::*;
1942
1943    use std::fs::File;
1944    use std::io::Write;
1945
1946    use anyhow::Context;
1947
1948    use openpgp::{
1949        Cert,
1950        crypto::{
1951            mpi::Ciphertext,
1952            Decryptor,
1953            Signer,
1954        },
1955        parse::Parse,
1956        policy::StandardPolicy,
1957    };
1958
1959    use ipc::Keygrip;
1960
1961    const P: &StandardPolicy = &StandardPolicy::new();
1962
1963    async fn import_keys(agent: &mut Agent, cert: &Cert) -> Result<()> {
1964        assert!(cert.is_tsk(),
1965                "{} does not contain secret key material", cert.fingerprint());
1966
1967        for k in cert.keys().secret() {
1968            agent.import(P,
1969                         &cert, k.key().parts_as_secret().expect("have secret"),
1970                         true, true).await?;
1971        }
1972
1973        Ok(())
1974    }
1975
1976    async fn import_testy_new(agent: &mut Agent) -> Result<Cert> {
1977        let cert = Cert::from_bytes(crate::tests::key("testy-new-private.pgp"))?;
1978        import_keys(agent, &cert).await?;
1979        Ok(cert)
1980    }
1981
1982    /// Asserts that a <KeyPair as Signer> is usable from an async
1983    /// context.
1984    ///
1985    /// Previously, the test died with
1986    ///
1987    ///     thread 'gnupg::tests::signer_in_async_context' panicked at
1988    ///     'Cannot start a runtime from within a runtime. This
1989    ///     happens because a function (like `block_on`) attempted to
1990    ///     block the current thread while the thread is being used to
1991    ///     drive asynchronous tasks.'
1992    #[test]
1993    fn signer_in_async_context() -> Result<()> {
1994        async fn async_context() -> Result<()> {
1995            let ctx = match gnupg::Context::ephemeral() {
1996                Ok(c) => c,
1997                Err(e) => {
1998                    eprintln!("Failed to create ephemeral context: {}", e);
1999                    eprintln!("Most likely, GnuPG isn't installed.");
2000                    eprintln!("Skipping test.");
2001                    return Ok(());
2002                },
2003            };
2004
2005            let key = Cert::from_bytes(crate::tests::key("testy-new.pgp"))?
2006                .primary_key().key().clone();
2007            let mut pair = KeyPair::new_for_gnupg_context(&ctx, &key)?;
2008            let algo = HashAlgorithm::default();
2009            let digest = algo.context()?.for_digest().into_digest()?;
2010            let _ = pair.sign(algo, &digest);
2011            Ok(())
2012        }
2013
2014        let rt = tokio::runtime::Runtime::new()?;
2015        rt.block_on(async_context())
2016    }
2017
2018    /// Asserts that a <KeyPair as Decryptor> is usable from an async
2019    /// context.
2020    ///
2021    /// Previously, the test died with
2022    ///
2023    ///     thread 'gnupg::tests::decryptor_in_async_context' panicked
2024    ///     at 'Cannot start a runtime from within a runtime. This
2025    ///     happens because a function (like `block_on`) attempted to
2026    ///     block the current thread while the thread is being used to
2027    ///     drive asynchronous tasks.'
2028    #[test]
2029    fn decryptor_in_async_context() -> Result<()> {
2030        async fn async_context() -> Result<()> {
2031            let ctx = match gnupg::Context::ephemeral() {
2032                Ok(c) => c,
2033                Err(e) => {
2034                    eprintln!("Failed to create ephemeral context: {}", e);
2035                    eprintln!("Most likely, GnuPG isn't installed.");
2036                    eprintln!("Skipping test.");
2037                    return Ok(());
2038                },
2039            };
2040
2041            let key = Cert::from_bytes(crate::tests::key("testy-new.pgp"))?
2042                .keys().nth(1).unwrap().key().clone();
2043            let mut pair = KeyPair::new_for_gnupg_context(&ctx, &key)?;
2044            let ciphertext = Ciphertext::ECDH {
2045                e: vec![].into(),
2046                key: vec![].into_boxed_slice(),
2047            };
2048            let _ = pair.decrypt(&ciphertext, None);
2049            Ok(())
2050        }
2051
2052        let rt = tokio::runtime::Runtime::new()?;
2053        rt.block_on(async_context())
2054    }
2055
2056    // Disabled on Windows.  Re-enable when this issue is fixed:
2057    //
2058    // https://gitlab.com/sequoia-pgp/sequoia/-/issues/1093
2059    //
2060    // SEE OTHER INSTANCES OF THIS ABOVE (search for comment).
2061    #[cfg(not(windows))]
2062    #[tokio::test]
2063    async fn non_existent_home_directory() -> Result<()> {
2064        let tempdir = tempfile::tempdir()?;
2065        let homedir = tempdir.path().join("foo");
2066
2067        let result = Agent::connect_to(&homedir).await;
2068        match result {
2069            Ok(_agent) => panic!("Created an agent for a non-existent home!"),
2070            Err(err) => {
2071                if let Error::GnuPGHomeMissing(_) = err {
2072                    // Correct error.
2073                } else {
2074                    panic!("Expected Error::GnuPGHomeMissing, got: {}", err);
2075                }
2076            }
2077        }
2078
2079        Ok(())
2080    }
2081
2082    // Test that we can import a key and sign a message.
2083    #[tokio::test]
2084    async fn import_key_and_sign() -> Result<()> {
2085        let ctx = match gnupg::Context::ephemeral() {
2086            Ok(c) => c,
2087            Err(e) => {
2088                eprintln!("Failed to create ephemeral context: {}", e);
2089                eprintln!("Most likely, GnuPG isn't installed.");
2090                eprintln!("Skipping test.");
2091                return Ok(());
2092            },
2093        };
2094
2095        let mut agent = Agent::connect(&ctx).await?;
2096
2097        let testy = import_testy_new(&mut agent).await?;
2098
2099        let key = testy.primary_key().key();
2100        let mut pair = KeyPair::new_for_gnupg_context(&ctx, &key)?;
2101        let algo = HashAlgorithm::default();
2102        let digest = algo.context()?.for_digest().into_digest()?;
2103        if let Err(err) = pair.sign(algo, &digest) {
2104            panic!("Signing: {}", err);
2105        }
2106        Ok(())
2107    }
2108
2109    // Test that we can list imported keys.
2110    #[tokio::test]
2111    async fn list_keys() -> Result<()> {
2112        use std::collections::HashSet;
2113
2114        let ctx = match gnupg::Context::ephemeral() {
2115            Ok(c) => c,
2116            Err(e) => {
2117                eprintln!("Failed to create ephemeral context: {}", e);
2118                eprintln!("Most likely, GnuPG isn't installed.");
2119                eprintln!("Skipping test.");
2120                return Ok(());
2121            },
2122        };
2123
2124        let mut agent = Agent::connect(&ctx).await?;
2125
2126        let keys = agent.list_keys().await.expect("list keys works");
2127        assert_eq!(keys.len(), 0);
2128
2129        let testy = import_testy_new(&mut agent).await?;
2130        let testy_keygrips = testy.keys().into_iter()
2131            .map(|ka| Keygrip::of(ka.key().mpis()))
2132            .collect::<Result<HashSet<Keygrip>, _>>()
2133            .expect("can compute keygrip");
2134        assert!(testy_keygrips.len() > 0);
2135
2136        let keys = agent.list_keys().await.expect("list keys works");
2137        let keys = keys.into_iter()
2138            .map(|k| k.keygrip().clone())
2139            .collect::<HashSet<Keygrip>>();
2140
2141        assert_eq!(testy_keygrips, keys);
2142
2143        Ok(())
2144    }
2145
2146    // Test Agent::key_info.
2147    #[tokio::test]
2148    async fn key_info() -> Result<()> {
2149        use std::collections::HashSet;
2150
2151        let ctx = match gnupg::Context::ephemeral() {
2152            Ok(c) => c,
2153            Err(e) => {
2154                eprintln!("Failed to create ephemeral context: {}", e);
2155                eprintln!("Most likely, GnuPG isn't installed.");
2156                eprintln!("Skipping test.");
2157                return Ok(());
2158            },
2159        };
2160
2161        let mut agent = Agent::connect(&ctx).await?;
2162
2163        let testy = import_testy_new(&mut agent).await?;
2164        let testy_keygrips = testy.keys().into_iter()
2165            .map(|ka| Keygrip::of(ka.key().mpis()))
2166            .collect::<Result<HashSet<Keygrip>, _>>()
2167            .expect("can compute keygrip");
2168        assert!(testy_keygrips.len() > 0);
2169
2170        for ka in testy.keys() {
2171            let keygrip = Keygrip::of(ka.key().mpis()).expect("has a keygrip");
2172            match agent.key_info(&keygrip).await {
2173                Ok(info) => {
2174                    assert_eq!(info.keygrip(), &keygrip);
2175                }
2176                Err(err) => {
2177                    panic!("Getting keyinfo for {}: {}", keygrip, err);
2178                }
2179            }
2180        }
2181
2182        Ok(())
2183    }
2184
2185    // Test Agent::preset_passphrase
2186    //
2187    // Disabled on Windows.  Re-enable when this issue is fixed:
2188    //
2189    // https://gitlab.com/sequoia-pgp/sequoia/-/issues/1093
2190    //
2191    // SEE OTHER INSTANCES OF THIS ABOVE (search for comment).
2192    #[cfg(not(windows))]
2193    #[tokio::test]
2194    async fn preset_passphrase() -> Result<()> {
2195        trace(true);
2196        tracer!(TRACE, "preset_passphrase");
2197
2198        async fn test(file: &str, password: &str)
2199            -> Result<()>
2200        {
2201            let ctx = match gnupg::Context::ephemeral() {
2202                Ok(c) => c,
2203                Err(e) => {
2204                    eprintln!("Failed to create ephemeral context: {}", e);
2205                    eprintln!("Most likely, GnuPG isn't installed.");
2206                    eprintln!("Skipping test.");
2207                    return Ok(());
2208                },
2209            };
2210
2211            let mut agent = Agent::connect(&ctx).await?;
2212
2213            let cert = Cert::from_bytes(crate::tests::key(file))?;
2214            import_keys(&mut agent, &cert).await?;
2215            let vc = cert.with_policy(P, None).expect("valid cert");
2216            let vka = vc.keys().for_signing().next().expect("have signing key");
2217            let key_keygrip = Keygrip::of(vka.key().mpis()).expect("has a keygrip");
2218
2219            t!("Testing with {} ({})", vka.key().fingerprint(), key_keygrip);
2220            t!("gpg knows about:");
2221            let mut saw = false;
2222            for (i, info) in agent.list_keys().await.expect("works").iter().enumerate() {
2223                t!("{}. {}: {:?}, password cached: {}",
2224                   i + 1, info.keygrip(), info.protection(),
2225                   info.passphrase_cached());
2226                if info.keygrip() == &key_keygrip {
2227                    saw = true;
2228                }
2229            }
2230            assert!(saw, "Failed to load test key");
2231
2232            // Try with allow-preset-passphrase not set and an
2233            // incorrect password.  This should fail.
2234            let r = agent.preset_passphrase(&key_keygrip, "wrong".into()).await;
2235            match r {
2236                Ok(()) => panic!("preset_passphrase should fail if \
2237                                  'allow-preset-passphrase' is not set."),
2238                Err(err) => {
2239                    if let Error::Assuan(assuan::Error::OperationFailed(_)) = err {
2240                        // That's what we expect.
2241                    } else {
2242                        panic!("Expected assuan::Error::OperationFailed, \
2243                                but got {}", err);
2244                    }
2245                }
2246            }
2247
2248            // Set allow-preset-passphrase.
2249            let dot_gnupg = ctx.homedir().expect("have a homedir");
2250            let conf = dot_gnupg.join("gpg-agent.conf");
2251            let mut f = File::options().create(true).append(true).open(&conf)
2252                .with_context(|| {
2253                    format!("Opening {}", conf.display())
2254                }).expect("can open gpg-agent.conf");
2255            writeln!(&mut f, "allow-preset-passphrase").expect("can write");
2256            drop(f);
2257
2258            agent.reload().await.expect("valid");
2259
2260            // Try with allow-preset-passphrase set, but still an
2261            // incorrect password.  This should still fail.
2262            let r = agent.preset_passphrase(
2263                &key_keygrip, "this is the wrong passphrase".into()).await;
2264            match r {
2265                Ok(()) => (),
2266                Err(err) => {
2267                    panic!("Failed to set password: {}", err);
2268                }
2269            }
2270
2271            let algo = HashAlgorithm::default();
2272            let digest = algo.context()?.for_digest().into_digest()?;
2273            let mut pair = KeyPair::new_for_gnupg_context(&ctx, vka.key())?
2274                // Don't prompt for the passphrase.
2275                .suppress_pinentry();
2276            match pair.sign(algo, &digest) {
2277                Ok(_) => {
2278                    panic!("Signing should have failed");
2279                }
2280                Err(err) => {
2281                    t!("Signing failed (expected): {}", err);
2282                }
2283            }
2284
2285            // Finally, try to sign a message using the correct
2286            // passphrase.
2287            let r = agent.preset_passphrase(&key_keygrip, password.into()).await;
2288            match r {
2289                Ok(()) => (),
2290                Err(err) => {
2291                    panic!("Failed to set password: {}", err);
2292                }
2293            }
2294
2295            t!("gpg-agent's key info:");
2296            for (i, info) in agent.list_keys().await.unwrap().iter().enumerate()
2297            {
2298                t!("  {}. {}: {:?}, password cached: {}",
2299                   i + 1, info.keygrip(), info.protection(),
2300                   info.passphrase_cached());
2301            }
2302
2303            let algo = HashAlgorithm::default();
2304            let digest = algo.context()?.for_digest().into_digest()?;
2305            let mut pair = KeyPair::new_for_gnupg_context(&ctx, vka.key())?
2306                // Don't prompt for the passphrase.
2307                .suppress_pinentry();
2308            match pair.sign(algo, &digest) {
2309                Ok(_) => {
2310                }
2311                Err(err) => {
2312                    panic!("Signing failed (unexpected): {}", err);
2313                }
2314            }
2315
2316            // Now, forget the passphrase and try to use the key.  It
2317            // should fail again.
2318            agent.forget_passphrase(&key_keygrip.to_string(), |_| ()).await
2319                .expect("can forget");
2320
2321            let algo = HashAlgorithm::default();
2322            let digest = algo.context()?.for_digest().into_digest()?;
2323            let mut pair = KeyPair::new_for_gnupg_context(&ctx, vka.key())?
2324                // Don't prompt for the passphrase.
2325                .suppress_pinentry();
2326            match pair.sign(algo, &digest) {
2327                Ok(_) => {
2328                    panic!("Signing should have failed");
2329                }
2330                Err(err) => {
2331                    t!("Signing failed (expected): {}", err);
2332                }
2333            }
2334
2335            Ok(())
2336        }
2337
2338        test("password-xyzzy-private.pgp", "xyzzy").await.expect("all okay");
2339        test("password-foospacespacebar-private.pgp", "foo  bar").await.expect("all okay");
2340
2341        Ok(())
2342    }
2343
2344    // Test Agent::export
2345    #[tokio::test]
2346    async fn export() -> Result<()> {
2347        use std::collections::HashSet;
2348
2349        let ctx = match gnupg::Context::ephemeral() {
2350            Ok(c) => c,
2351            Err(e) => {
2352                eprintln!("Failed to create ephemeral context: {}", e);
2353                eprintln!("Most likely, GnuPG isn't installed.");
2354                eprintln!("Skipping test.");
2355                return Ok(());
2356            },
2357        };
2358
2359        let mut agent = Agent::connect(&ctx).await?;
2360
2361        let testy = import_testy_new(&mut agent).await?;
2362        let testy_keygrips = testy.keys().into_iter()
2363            .map(|ka| Keygrip::of(ka.key().mpis()))
2364            .collect::<Result<HashSet<Keygrip>, _>>()
2365            .expect("can compute keygrip");
2366        assert!(testy_keygrips.len() > 0);
2367
2368        for ka in testy.keys().secret() {
2369            let keygrip = Keygrip::of(ka.key().mpis()).expect("has a keygrip");
2370            match agent.export(ka.key().parts_as_public().clone()).await {
2371                Ok(key) => {
2372                    assert_eq!(ka.key(), &key);
2373                }
2374                Err(err) => {
2375                    panic!("Exporting key {}: {}", keygrip, err);
2376                }
2377            }
2378        }
2379
2380        Ok(())
2381    }
2382}