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}