gnupg_rs/
lib.rs

1use std::{io::Write, process::Stdio};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4/// A struct representing a public key.
5pub struct Key {
6    /// The fingerprint of the key.
7    pub fingerprint: String,
8    /// The name associated with the key.
9    pub name: String,
10    /// The email address associated with the key.
11    pub mail: String,
12    /// The password the key is protected with
13    pub password: Option<String>,
14}
15
16impl Key {
17    /// Creates a new GPG key with the given name and email address.
18    ///
19    /// # Parameters
20    ///
21    /// - `name`: the name of the key
22    /// - `mail`: the email address associated with the key
23    /// - `password`: an optional password to use for the key
24    /// - `gpg`: a reference to `GnuPG` struct
25    ///
26    /// # Returns
27    ///
28    /// If the key is successfully created, returns an instance of `Key` wrapped in an `Ok` variant.
29    /// If the key creation fails, returns an instance of `GPGError` wrapped in an `Err` variant.
30    pub fn new(
31        name: &str,
32        mail: &str,
33        password: Option<&str>,
34        gpg: &GnuPG,
35    ) -> Result<Self, GPGError> {
36        match gpg.generate_key(name, mail, password) {
37            Ok(key) => match password {
38                Some(pw) => Ok(key.unlock(pw)),
39                None => Ok(key),
40            },
41            Err(e) => Err(e),
42        }
43    }
44
45    /// Retrieves a GPG key by its name.
46    ///
47    /// # Parameters
48    ///
49    /// - `name`: the name of the key to retrieve
50    /// - `gpg`: a reference to `GnuPG` struct
51    ///
52    /// # Returns
53    ///
54    /// If a key with the given name is found, returns an instance of `Key`.
55    /// If no key with the given name is found, returns `None`.
56    #[must_use]
57    pub fn get_by_name(name: &str, gpg: &GnuPG) -> Option<Self> {
58        let keys = gpg.list_keys().ok()?;
59        let key = keys.iter().find(|x| x.name.contains(name))?;
60        Some(key.clone())
61    }
62
63    /// Retrieves a GPG key by its email address.
64    ///
65    /// # Parameters
66    ///
67    /// - `mail`: the email address associated with the key to retrieve
68    /// - `gpg`: a reference to `GnuPG` struct
69    ///
70    /// # Returns
71    ///
72    /// If a key with the given email address is found, returns an instance of `Key`.
73    /// If no key with the given email address is found, returns `None`.
74    #[must_use]
75    pub fn get_by_mail(mail: &str, gpg: &GnuPG) -> Option<Self> {
76        let keys = gpg.list_keys().ok()?;
77        let key = keys.iter().find(|x| x.mail.contains(mail))?;
78        Some(key.clone())
79    }
80
81    /// Retrieves a GPG key by its fingerprint.
82    ///
83    /// # Parameters
84    ///
85    /// - `fingerprint`: the fingerprint associated with the key to retrieve
86    /// - `gpg`: a reference to `GnuPG` struct
87    ///
88    /// # Returns
89    ///
90    /// If a key with the given fingerprint is found, returns an instance of `Key`.
91    /// If no key with the given fingerprint is found, returns `None`.
92    #[must_use]
93    pub fn get_by_fingerprint(fingerprint: &str, gpg: &GnuPG) -> Option<Self> {
94        let keys = gpg.list_keys().ok()?;
95        let key = keys.iter().find(|x| x.fingerprint.contains(fingerprint))?;
96        Some(key.clone())
97    }
98
99    /// Adds information to unlock a GPG key with the given password.
100    ///
101    /// # Parameters
102    ///
103    /// - `pw`:  the password to use to unlock the key
104    ///
105    /// # Returns
106    ///
107    /// Returns a new `Key` object, with the password set to the given value.
108    /// # Example
109    ///
110    /// ```
111    /// use gnupg::*;
112    /// let gnupg = GnuPG::new().unwrap();
113    /// let encrypted = "-----BEGIN PGP MESSAGE-----
114    ///                 ...
115    ///                 -----END PGP MESSAGE-----";
116    /// let key = Key::get_by_name("Arisu", &gnupg).unwrap();
117    /// let plain = gnupg.decrypt(&key.unlock("pass"), encrypted);
118    /// println!("Plain Text: {:?}", plain);
119    /// ```
120    #[must_use]
121    pub fn unlock(&self, pw: &str) -> Self {
122        let mut key = self.clone();
123        key.password = Some(pw.to_owned());
124        key
125    }
126}
127
128#[derive(Debug)]
129/// A struct representing a decrypted message.
130pub struct DecryptedMessage {
131    /// The text of the decrypted message.
132    pub text: String,
133    /// The date the message was decrypted.
134    pub date: String,
135}
136
137/// Represents a message that has been signed with a cryptographic key.
138#[derive(Debug)]
139pub struct SignedMessage {
140    /// The text of the message.
141    pub text: String,
142    /// The date the message was signed.
143    pub date: String,
144    /// The key used to sign the message.
145    pub signed_by: Key,
146}
147
148/// Represents the result of a verification
149///
150/// The `Verify` enum has two variants:
151///
152/// - `Ok(SignedMessage)`: The signature was successfully validated, and the signed message with additional information is returned.
153/// - `Failed`: The signature is not valid.
154#[derive(Debug)]
155pub enum Verify {
156    Ok(SignedMessage),
157    Failed,
158}
159
160#[derive(Debug)]
161/// An enum representing errors that can occur when working with GPG.
162pub enum GPGError {
163    /// An error occurred while parsing data.
164    ParseError,
165    /// The data input was not valid OpenPGP data.
166    NoValidOpenPGPData,
167    /// A required public key was not found.
168    MissingPublicKey,
169    /// A required secret key was not found.
170    MissingSecretKey,
171    /// Key already exists
172    KeyAlreadyExists(Key),
173    /// An error involving the passphrase occurred
174    BadPassphrase,
175    /// An error occurred with a key.
176    KeyError,
177}
178
179impl std::error::Error for GPGError {}
180
181impl std::fmt::Display for GPGError {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        f.write_str(&format!("{self:?}"))
184    }
185}
186
187/// A struct representing a `GnuPG` instance.
188pub struct GnuPG {
189    /// The path to the GnuPG executable.
190    exec: String,
191    /// The path to the home directory for the GnuPG instance.
192    /// If `None`, the default home directory will be used.
193    homedir: Option<String>,
194}
195
196/// Returns the path to the GPG executable, if it is installed.
197fn get_gnupg_exec() -> Option<String> {
198    let output = std::process::Command::new("gpg").arg("--version").output();
199
200    if let Err(e) = output {
201        if e.kind() == std::io::ErrorKind::NotFound {
202            return None;
203        }
204    }
205
206    Some("gpg".to_owned())
207}
208
209fn run_command(cmd: &str, args: Vec<&str>, input: &str) -> (String, String) {
210    let mut cmd = std::process::Command::new(cmd)
211        .args(args)
212        .env("LANG", "en")
213        .stdin(Stdio::piped())
214        .stdout(Stdio::piped())
215        .stderr(Stdio::piped())
216        .spawn()
217        .expect("gpg process could not be created");
218
219    {
220        let mut stdin = cmd.stdin.take().expect("gpg stdin failed");
221        stdin
222            .write_all(input.as_bytes())
223            .expect("input could not be given to gpg");
224    }
225
226    let out = cmd.wait_with_output().expect("gpg executable error");
227
228    let stdout = String::from_utf8_lossy(&out.stdout).to_string();
229    let stderr = String::from_utf8_lossy(&out.stderr).to_string();
230
231    (stdout, stderr)
232}
233
234fn split_at_last(str: &str, pat: &str) -> Option<(String, String)> {
235    let mut split: Vec<&str> = str.split(pat).collect();
236
237    let last_str = split.pop()?;
238
239    let mut last = String::from(pat);
240    last.push_str(last_str);
241
242    let before = split.join(pat);
243
244    if last.starts_with(' ') {
245        last = last[1..].to_string();
246    }
247
248    Some((before, last))
249}
250
251fn split_first(str: &str, pat: &str) -> String {
252    let mut split = str.split(pat).collect::<Vec<_>>();
253    split.remove(0);
254    split.join(pat)
255}
256
257fn split_last(str: &str, pat: &str) -> String {
258    let mut split = str.split(pat).collect::<Vec<_>>();
259    split.pop();
260    split.join(pat)
261}
262
263impl GnuPG {
264    /// Returns a new `GnuPG` instance with the default executable and default home directory.
265    /// If the default executable is not found, returns `None`.
266    #[must_use]
267    #[inline]
268    pub fn new() -> Option<Self> {
269        let exec = get_gnupg_exec();
270
271        exec.map(|exec| Self {
272            exec,
273            homedir: None,
274        })
275    }
276
277    /// Returns a new `GnuPG` instance with the specified executable and no specified home directory.
278    #[must_use]
279    #[inline]
280    pub fn with_executable(exec: &str) -> Self {
281        Self {
282            exec: exec.to_owned(),
283            homedir: None,
284        }
285    }
286
287    /// Returns a new `GnuPG` instance with the specified home directory.
288    #[must_use]
289    pub fn set_homedir(mut self, dir: &str) -> Self {
290        std::fs::create_dir_all(dir).expect("gpg homedir could not be created");
291        self.homedir = Some(dir.to_owned());
292        self
293    }
294
295    /// Adds homedir to arguments if present
296    fn gpg_args<'a>(&'a self, mut args: Vec<&'a str>) -> Vec<&'a str> {
297        if let Some(homedir) = &self.homedir {
298            args.insert(0, "--homedir");
299            args.insert(1, homedir);
300        }
301        args
302    }
303
304    // KEYS
305
306    /// Imports a public key and returns the `Key` struct representing it.
307    ///
308    /// # Arguments
309    ///
310    /// * `key_data` - A string containing the key data to be imported.
311    ///
312    /// # Returns
313    ///
314    /// A `Result` containing either the imported `Key` or a `GPGError` variant indicating the error that occurred.
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// use gnupg::GnuPG;
320    /// let gnupg = GnuPG::new().unwrap();
321    /// let key_data = "-----BEGIN PGP PUBLIC KEY BLOCK-----
322    ///                 ...
323    ///                 -----END PGP PUBLIC KEY BLOCK-----";
324    /// let key = gnupg.import_key(key_data).unwrap();
325    /// println!("Imported key with fingerprint: {}", key.fingerprint);
326    /// ```
327    pub fn import_key(&self, key_data: &str) -> Result<Key, GPGError> {
328        let out = run_command(&self.exec, self.gpg_args(vec!["--import"]), key_data);
329        let out = out.1;
330
331        if out.contains("no valid OpenPGP data found") {
332            return Err(GPGError::NoValidOpenPGPData);
333        }
334
335        let lines: Vec<&str> = out.lines().collect();
336
337        let mut key_fingerprint = String::new();
338
339        for line in lines {
340            if line.starts_with("gpg: key ") {
341                let words: Vec<&str> = line.split(' ').collect();
342                let fp = words.get(2).ok_or(GPGError::ParseError)?;
343                key_fingerprint = (*fp).to_owned();
344                break;
345            }
346        }
347
348        key_fingerprint.pop();
349
350        let keys = self.list_keys()?;
351        for k in keys {
352            if k.fingerprint.contains(&key_fingerprint) {
353                return Ok(k);
354            }
355        }
356
357        Err(GPGError::ParseError)
358    }
359
360    /// Exports the specified key in ASCII-armored format.
361    ///
362    /// # Arguments
363    ///
364    /// * `key` - The `Key` struct representing the key to export.
365    ///
366    /// # Returns
367    ///
368    /// The exported key as a string in ASCII-armored format.
369    ///
370    /// # Example
371    ///
372    /// ```
373    /// let gnupg = GnuPG::new().unwrap();
374    /// let key = gnupg.list_keys().unwrap().first().unwrap();
375    /// let exported_key = gnupg.export_key(key);
376    /// println!("Exported key:\n{}", exported_key);
377    /// ```
378    #[must_use]
379    pub fn export_key(&self, key: &Key) -> String {
380        let out = run_command(
381            &self.exec,
382            self.gpg_args(vec!["--armor", "--export", &key.fingerprint]),
383            "",
384        );
385        out.0
386    }
387
388    /// Exports the private key of the specified key pair in ASCII-armored format.
389    ///
390    /// # Arguments
391    ///
392    /// * `key` - The `Key` struct representing the key pair whose private key to export.
393    /// * `pw` - The password for the private key. If the private key is not password-protected, this argument can be left as `None` (default value: `None`).
394    ///
395    /// # Returns
396    ///
397    /// The exported private key as a string in ASCII-armored format.
398    ///
399    /// # Example
400    ///
401    /// ```
402    /// let gnupg = GnuPG::new().unwrap();
403    /// let key = gnupg.list_keys().unwrap().first().unwrap();
404    /// let exported_key = gnupg.export_secret_key(key);
405    /// println!("Exported key:\n{}", exported_key);
406    /// ```
407    pub fn export_secret_key(&self, key: &Key) -> Result<String, GPGError> {
408        let mut args = vec!["--armor"];
409        if let Some(pw) = key.password.as_ref() {
410            args.extend_from_slice(&["--pinentry-mode", "loopback", "--passphrase", pw]);
411        }
412        args.extend_from_slice(&["--export-secret-keys", &key.fingerprint]);
413
414        let out = run_command(&self.exec, self.gpg_args(args), "");
415        if out.1.contains("Bad passphrase") {
416            return Err(GPGError::BadPassphrase);
417        } else if out.1.contains("nothing exported") {
418            return Err(GPGError::KeyError);
419        }
420        Ok(out.0)
421    }
422
423    /// Returns a list of all public keys in the keyring.
424    ///
425    /// # Returns
426    ///
427    /// A `Result` containing either a vector of `Key` structs representing the keys, or a `GPGError` variant indicating an error that occurred.
428    ///
429    /// # Example
430    ///
431    /// ```
432    /// let gnupg = GnuPG::new().unwrap();
433    /// let keys = gnupg.list_keys().unwrap();
434    /// println!("Number of keys: {}", keys.len());
435    /// ```
436    pub fn list_keys(&self) -> Result<Vec<Key>, GPGError> {
437        let out = run_command(&self.exec, self.gpg_args(vec!["--list-keys"]), "");
438
439        let out = out.0;
440
441        let mut sections: Vec<&str> = out.split('\n').collect();
442        sections.remove(0);
443        sections.remove(0);
444        let out = sections.join("\n");
445
446        let key_sections: Vec<&str> = out.split("\n\n").collect();
447
448        let mut keys: Vec<Key> = vec![];
449
450        for key in key_sections {
451            if key.is_empty() {
452                continue;
453            }
454
455            let lines: Vec<&str> = key.split('\n').collect();
456
457            let l1 = lines.first().ok_or(GPGError::ParseError)?;
458            if !l1.starts_with("pub ") {
459                return Err(GPGError::ParseError);
460            }
461
462            let key_fingerprint = lines
463                .get(1)
464                .ok_or(GPGError::ParseError)?
465                .split(' ')
466                .collect::<Vec<&str>>()
467                .last()
468                .ok_or(GPGError::ParseError)?
469                .to_owned();
470
471            let name_section: Vec<&str> = lines
472                .get(2)
473                .ok_or(GPGError::ParseError)?
474                .split("] ")
475                .collect();
476
477            let (name, mail_part) =
478                split_at_last(name_section.last().ok_or(GPGError::ParseError)?, " <")
479                    .ok_or(GPGError::ParseError)?;
480
481            let mail = &mail_part[1..mail_part.len() - 1];
482
483            keys.push(Key {
484                fingerprint: key_fingerprint.to_owned(),
485                name,
486                mail: mail.to_owned(),
487                password: None,
488            });
489        }
490
491        Ok(keys)
492    }
493
494    /// Generates a new GPG key with the given name and email.
495    /// If a password is provided, the key will be encrypted with the password.
496    /// If the key already exists, returns a `KeyAlreadyExists` error.
497    ///
498    /// # Arguments
499    ///
500    /// * `name` - the name to be associated with the key
501    /// * `mail` - the email to be associated with the key
502    /// * `pw` - an optional password to encrypt the key
503    ///
504    /// # Examples
505    ///
506    /// ```
507    /// let gpg = GnuPG::new().unwrap();
508    /// let key = gpg.generate_key("Alice", "alice@example.com", None).unwrap();
509    /// ```
510    pub fn generate_key(&self, name: &str, mail: &str, pw: Option<&str>) -> Result<Key, GPGError> {
511        let user_id = format!("{name} <{mail}>");
512        let pass = pw.unwrap_or("");
513
514        let out = run_command(
515            &self.exec,
516            self.gpg_args(vec![
517                "--quick-gen-key",
518                "--batch",
519                "--passphrase",
520                pass,
521                &user_id,
522            ]),
523            "",
524        );
525        let out = out.1;
526
527        if out.contains("already exists") {
528            let keys = self.list_keys()?;
529            let key = keys
530                .iter()
531                .find(|x| x.name == name && x.mail == mail)
532                .ok_or(GPGError::KeyError)?;
533            return Err(GPGError::KeyAlreadyExists(key.clone()));
534        }
535
536        let bind = self.list_keys()?;
537        let key = bind
538            .iter()
539            .find(|k| k.name == name && k.mail == mail)
540            .ok_or(GPGError::KeyError)?;
541
542        Ok(key.clone())
543    }
544
545    /// Signs a message using the specified key.
546    ///
547    /// # Arguments
548    ///
549    /// * `key` - The `Key` struct representing the key to use for signing.
550    /// * `msg` - The message to sign.
551    ///
552    /// # Returns
553    ///
554    /// A `Result` containing either the signed message as a string, or a `GPGError` variant indicating an error that occurred.
555    ///
556    /// # Example
557    ///
558    /// ```
559    /// let gnupg = GnuPG::new().unwrap();
560    /// let key = gnupg.list_keys().unwrap().first().unwrap();
561    /// let signed_msg = gnupg.sign(key, "Hello, world!").unwrap();
562    /// println!("Signed message:\n{}", signed_msg);
563    /// ```
564    pub fn sign(&self, key: &Key, msg: &str) -> Result<String, GPGError> {
565        let mut args = self.gpg_args(vec!["--clear-sign", "-u", &key.fingerprint]);
566        if let Some(pw) = key.password.as_ref() {
567            args.extend_from_slice(&["--pinentry-mode", "loopback", "--passphrase", pw]);
568        }
569        let out = run_command(&self.exec, args, msg);
570
571        if out.1.contains("No secret key") {
572            Err(GPGError::MissingSecretKey)
573        } else if out.1.contains("Bad passphrase") {
574            Err(GPGError::BadPassphrase)
575        } else if out.1.contains("failed") {
576            Err(GPGError::ParseError)
577        } else {
578            Ok(out.0)
579        }
580    }
581
582    /// Encrypts a message for the specified key.
583    ///
584    /// # Arguments
585    ///
586    /// * `receiver` - The `Key` struct representing the key to encrypt the message for.
587    /// * `msg` - The message to encrypt.
588    ///
589    /// # Returns
590    ///
591    /// A `Result` containing either the encrypted message as a string, or a `GPGError` variant indicating an error occurred.
592    ///
593    /// # Example
594    ///
595    /// ```
596    /// let gnupg = GnuPG::new().unwrap();
597    /// let key = gnupg.list_keys().unwrap().first().unwrap();
598    /// let encrypted_msg = gnupg.encrypt(key, "Hello, world!").unwrap();
599    /// println!("Encrypted message:\n{}", encrypted_msg);
600    /// ```
601    pub fn encrypt(&self, receiver: &Key, msg: &str) -> Result<String, GPGError> {
602        let out = run_command(
603            &self.exec,
604            self.gpg_args(vec![
605                "--encrypt",
606                "--batch",
607                "--trust-model",
608                "always",
609                "--armor",
610                "--output",
611                "-",
612                "-r",
613                &receiver.fingerprint,
614            ]),
615            msg,
616        );
617
618        let err = out.1;
619
620        if err.contains("Unusable public key") {
621            Err(GPGError::KeyError)
622        } else {
623            Ok(out.0)
624        }
625    }
626
627    /// Encrypts a message using a password.
628    ///
629    /// # Arguments
630    ///
631    /// * `pw` - The password to use for encryption.
632    /// * `msg` - The message to encrypt.
633    ///
634    /// # Returns
635    ///
636    /// A `Result` containing either the encrypted message as a string, or a `GPGError` variant indicating an error that occurred.
637    ///
638    /// # Example
639    ///
640    /// ```
641    /// let gnupg = GnuPG::new().unwrap();
642    /// let encrypted_msg = gnupg.pw_encrypt("mypassword", "Hello, world!").unwrap();
643    /// println!("Encrypted message:\n{}", encrypted_msg);
644    /// ```
645    pub fn pw_encrypt(&self, pw: &str, msg: &str) -> Result<String, GPGError> {
646        let out = run_command(
647            &self.exec,
648            self.gpg_args(vec![
649                "--symmetric",
650                "--armor",
651                "--batch",
652                "--passphrase",
653                pw,
654            ]),
655            msg,
656        );
657
658        if out.0.is_empty() {
659            Err(GPGError::ParseError)
660        } else {
661            Ok(out.0)
662        }
663    }
664
665    /// Decrypts a message using a password.
666    ///
667    /// # Arguments
668    ///
669    /// * `pw` - The password to use for decryption.
670    /// * `msg` - The message to decrypt.
671    ///
672    /// # Returns
673    ///
674    /// A `Result` containing either the decrypted message as a string, or a `GPGError` variant indicating an error that occurred.
675    ///
676    /// # Example
677    ///
678    /// ```
679    /// let gnupg = GnuPG::new().unwrap();
680    /// let encrypted_msg = "-----BEGIN PGP MESSAGE-----
681    ///                     ...
682    ///                     -----END PGP MESSAGE-----";
683    /// let decrypted_msg = gnupg.pw_decrypt("mypassword", encrypted_msg).unwrap();
684    /// println!("Decrypted message:\n{}", decrypted_msg);
685    /// ```
686    pub fn pw_decrypt(&self, pw: &str, msg: &str) -> Result<String, GPGError> {
687        let out = run_command(
688            &self.exec,
689            self.gpg_args(vec!["--decrypt", "--batch", "--passphrase", pw]),
690            msg,
691        );
692        let out = out.0;
693
694        if out.is_empty() {
695            Err(GPGError::ParseError)
696        } else {
697            Ok(out)
698        }
699    }
700
701    /// Decrypts a message using the specified key.
702    ///
703    /// # Arguments
704    ///
705    /// * `key` - The `Key` struct representing the key to use for decryption.
706    /// * `msg` - The message to decrypt.
707    ///
708    /// # Returns
709    ///
710    /// A `Result` containing either a `DecryptedMessage` struct with the decrypted message text and date of creation, or a `GPGError` variant indicating an error that occurred.
711    ///
712    /// # Example
713    ///
714    /// ```
715    /// let gnupg = GnuPG::new().unwrap();
716    /// let key = gnupg.list_keys().unwrap().first().unwrap();
717    /// let encrypted_msg = "-----BEGIN PGP MESSAGE-----
718    ///                     ...
719    ///                     -----END PGP MESSAGE-----";
720    /// let decrypted_msg = gnupg.decrypt(key, encrypted_msg).unwrap();
721    /// println!("Decrypted message:\n{}\nDate: {}", decrypted_msg.text, decrypted_msg.date);
722    /// ```
723    pub fn decrypt(&self, key: &Key, msg: &str) -> Result<DecryptedMessage, GPGError> {
724        let mut args = self.gpg_args(vec!["--decrypt", "-u", &key.fingerprint]);
725        if let Some(pw) = key.password.as_ref() {
726            args.extend_from_slice(&["--pinentry-mode", "loopback", "--passphrase", pw]);
727        }
728        let out = run_command(&self.exec, args, msg);
729
730        let (out, err) = out;
731
732        if !out.is_empty() {
733            let date = &err
734                .split("created ")
735                .collect::<Vec<&str>>()
736                .last()
737                .ok_or(GPGError::ParseError)?
738                .to_owned()[0..10];
739            return Ok(DecryptedMessage {
740                text: out,
741                date: date.to_owned(),
742            });
743        }
744
745        if err.contains("no valid OpenPGP data found") {
746            return Err(GPGError::NoValidOpenPGPData);
747        } else if err.contains("Bad passphrase") {
748            return Err(GPGError::BadPassphrase);
749        }
750
751        Err(GPGError::ParseError)
752    }
753
754    /// Verifies the signature of a message using the specified key.
755    ///
756    /// # Arguments
757    ///
758    /// * `key` - The `Key` struct representing the key to use for verification.
759    /// * `msg` - The message with a signature to verify.
760    ///
761    /// # Returns
762    ///
763    /// A `Result` containing either a `Verify` enum with more information on the signature, or a `GPGError` variant indicating an error that occurred.
764    ///
765    /// # Example
766    ///
767    /// ```
768    /// let gnupg = GnuPG::new().unwrap();
769    /// let key = gnupg.list_keys().unwrap().first().unwrap();
770    /// let signed_msg = "-----BEGIN PGP MESSAGE-----
771    ///                  ...
772    ///                  -----END PGP MESSAGE-----";
773    /// let is_valid = gnupg.verify(key, signed_msg).unwrap();
774    /// println!("Signature is valid: {:?}", is_valid);
775    /// ```
776    pub fn verify(&self, key: &Key, msg: &str) -> Result<Verify, GPGError> {
777        let out = run_command(
778            &self.exec,
779            self.gpg_args(vec!["--verify", "-u", &key.fingerprint]),
780            msg,
781        );
782        let out = out.1;
783
784        if out.contains("no valid OpenPGP data found") {
785            Err(GPGError::NoValidOpenPGPData)
786        } else if out.contains("Good signature") {
787            let mut date = String::new();
788            let mut fingerprint = String::new();
789            let mut name = String::new();
790            let mut mail = String::new();
791
792            for line in out.lines() {
793                if line.starts_with("gpg: Signature made ") {
794                    date = line
795                        .split("gpg: Signature made ")
796                        .last()
797                        .ok_or(GPGError::ParseError)?
798                        .to_owned();
799                }
800                if line.contains("using EDDSA key ") {
801                    fingerprint = line
802                        .split("using EDDSA key ")
803                        .last()
804                        .ok_or(GPGError::ParseError)?
805                        .to_owned();
806                }
807                if line.starts_with("gpg: Good signature from ") {
808                    let name_mail = line.split('"').nth(1).ok_or(GPGError::ParseError)?;
809                    let (name_ext, mail_part) =
810                        split_at_last(name_mail, " <").ok_or(GPGError::ParseError)?;
811                    name = name_ext;
812                    mail = mail_part[1..mail_part.len() - 1].to_owned();
813                }
814            }
815
816            let txt = split_first(msg, "Hash: SHA512\n\n");
817            let txt = split_last(&txt, "\n-----BEGIN PGP SIGNATURE-----");
818
819            let msg = SignedMessage {
820                text: txt,
821                date,
822                signed_by: Key {
823                    fingerprint,
824                    name,
825                    mail,
826                    password: None,
827                },
828            };
829
830            Ok(Verify::Ok(msg))
831        } else if out.contains("Can't check signature: No public key") {
832            Err(GPGError::MissingPublicKey)
833        } else if out.contains("BAD signature") {
834            Ok(Verify::Failed)
835        } else {
836            Err(GPGError::ParseError)
837        }
838    }
839}