libsignify_rs/
signify.rs

1//
2// signify-rs: cryptographically sign and verify files
3// lib/src/signify.rs: Configuration and Mode definitions
4//
5// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
6// SPDX-License-Identifier: ISC
7//
8
9use crate::crypto;
10use crate::error::{Error, Result};
11use crate::file::open;
12use crate::ops::{check_checksums, KeyGenerator, Signer, Verifier};
13use std::fs::File;
14use std::path::PathBuf;
15
16/// Operation mode.
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18pub enum Mode {
19    /// Check signatures.
20    Check,
21    /// Generate key pair.
22    Generate,
23    /// Sign a file.
24    Sign,
25    /// Verify a signature.
26    Verify,
27}
28
29/// Runtime configuration.
30///
31/// This struct holds all configuration options parsed from command-line arguments
32/// or set programmatically.
33///
34/// # Examples
35///
36/// ```
37/// use libsignify_rs::signify::{Signify, Mode};
38/// use std::path::PathBuf;
39///
40/// let mut signify = Signify::default();
41/// signify.mode = Some(Mode::Sign);
42/// signify.seckey = Some(PathBuf::from("key.sec"));
43/// signify.embed = true;
44///
45/// assert_eq!(signify.mode, Some(Mode::Sign));
46/// assert!(signify.embed);
47/// ```
48#[derive(Default, Debug)]
49pub struct Signify {
50    /// Operation mode.
51    pub mode: Option<Mode>,
52    /// Comment for key generation.
53    pub comment: Option<String>,
54    /// Embedding mode flag.
55    pub embed: bool,
56    /// Message file path.
57    pub msg_file: Option<PathBuf>,
58    /// No password flag.
59    pub nopass: bool,
60    /// Public key path.
61    pub pubkey: Option<PathBuf>,
62    /// Quiet mode flag.
63    pub quiet: bool,
64    /// Secret key path.
65    pub seckey: Option<PathBuf>,
66    /// Signature file path.
67    pub sig_file: Option<PathBuf>,
68    /// Gzip mode flag.
69    pub gzip: bool,
70    /// Key ID for keyring lookup.
71    pub key_id: Option<i32>,
72    /// Positional arguments.
73    pub args: Vec<PathBuf>,
74
75    // Pre-opened file handles
76    /// Public key file handle.
77    pub pubkey_file_handle: Option<File>,
78    /// Secret key file handle.
79    pub seckey_file_handle: Option<File>,
80    /// Message file handle.
81    pub msg_file_handle: Option<File>,
82    /// Signature file handle.
83    pub sig_file_handle: Option<File>,
84    /// TTY file handle for password Prompt.
85    pub tty_file_handle: Option<File>,
86}
87
88impl Signify {
89    /// Execute the requested operation based on configuration.
90    ///
91    /// # Errors
92    /// Returns errors if the operation fails or arguments are missing.
93    pub fn execute(&mut self) -> Result<()> {
94        match self.mode {
95            Some(Mode::Generate) => self.execute_generate(),
96            Some(Mode::Sign) => self.execute_sign(),
97            Some(Mode::Verify) => self.execute_verify(),
98            Some(Mode::Check) => self.execute_check(),
99            None => Err(Error::MissingMode),
100        }
101    }
102
103    /// Sandbox the binary depending on the requested operation.
104    ///
105    /// Call this before calling `execute`.
106    pub fn sandbox(&self) -> Result<()> {
107        match self.mode {
108            Some(Mode::Generate) => self.sandbox_generate(),
109            Some(Mode::Sign) => self.sandbox_sign(),
110            Some(Mode::Verify) => self.sandbox_verify(),
111            Some(Mode::Check) => self.sandbox_check(),
112            None => Err(Error::MissingMode),
113        }
114    }
115
116    /// Pre-open files before sandboxing.
117    pub fn preopen(&mut self) -> Result<()> {
118        match self.mode {
119            Some(Mode::Generate) => self.preopen_generate(),
120            Some(Mode::Sign) => self.preopen_sign(),
121            Some(Mode::Verify) => self.preopen_verify(),
122            Some(Mode::Check) => self.preopen_check(),
123            None => Err(Error::MissingMode),
124        }
125    }
126
127    fn execute_generate(&mut self) -> Result<()> {
128        let pubkey_path = self
129            .pubkey
130            .clone()
131            .unwrap_or_else(|| PathBuf::from("key.pub"));
132        let seckey_path = self
133            .seckey
134            .clone()
135            .unwrap_or_else(|| PathBuf::from("key.sec"));
136
137        let rounds = if self.nopass {
138            0
139        } else {
140            crypto::DEFAULT_ROUNDS
141        };
142        let comment = self.comment.as_deref().unwrap_or("signify");
143
144        let mut generator = KeyGenerator::new().rounds(rounds).comment(comment);
145        if let Some(id) = self.key_id {
146            generator = generator.key_id(id);
147        }
148        if let Some(tty) = self.tty_file_handle.take() {
149            generator = generator.tty_handle(tty);
150        }
151        // Use generate_io if handles are available.
152        if let (Some(pub_file), Some(sec_file)) = (
153            self.pubkey_file_handle.take(),
154            self.seckey_file_handle.take(),
155        ) {
156            generator.generate_io(pub_file, sec_file)
157        } else {
158            generator.generate(&pubkey_path, &seckey_path)
159        }
160    }
161
162    fn execute_sign(&mut self) -> Result<()> {
163        let Some(seckey_path) = &self.seckey else {
164            return Err(Error::RequiredArg("-s"));
165        };
166
167        let Some(msg_path) = &self.msg_file else {
168            return Err(Error::Arg("missing -m".into()));
169        };
170
171        let sig_path = self.sig_file.clone().unwrap_or_else(|| {
172            let mut path = msg_path.clone();
173            path.set_extension("sig");
174            path
175        });
176
177        let mut signer = Signer::new()
178            .seckey(seckey_path)
179            .embed(self.embed)
180            .gzip(self.gzip);
181
182        if let Some(id) = self.key_id {
183            signer = signer.key_id(id);
184        }
185        if let Some(tty) = self.tty_file_handle.take() {
186            signer = signer.tty_handle(tty);
187        }
188
189        signer.sign_io(
190            msg_path,
191            &sig_path,
192            self.msg_file_handle.take(),
193            self.sig_file_handle.take(),
194            self.seckey_file_handle.take(),
195        )
196    }
197
198    fn execute_verify(&mut self) -> Result<()> {
199        let (msg_path, sig_path) = self.resolve_verify_paths()?;
200
201        let mut verifier = Verifier::new()
202            .quiet(self.quiet)
203            .embed(self.embed)
204            .gzip(self.gzip);
205
206        if let Some(path) = &self.pubkey {
207            verifier = verifier.pubkey(path);
208        }
209
210        verifier.verify_io(
211            &msg_path,
212            &sig_path,
213            self.msg_file_handle.take(),
214            self.sig_file_handle.take(),
215            self.pubkey_file_handle.take(),
216        )
217    }
218
219    fn execute_check(&mut self) -> Result<()> {
220        let Some(pubkey_path) = &self.pubkey else {
221            return Err(Error::RequiredArg("-p"));
222        };
223
224        let sig_path = self
225            .sig_file
226            .clone()
227            .or_else(|| self.args.first().cloned())
228            .unwrap_or_else(|| PathBuf::from("-"));
229
230        check_checksums(
231            pubkey_path,
232            &sig_path,
233            self.sig_file_handle.take(),
234            self.quiet,
235        )
236    }
237
238    fn sandbox_generate(&self) -> Result<()> {
239        #[cfg(target_os = "freebsd")]
240        {
241            return self.sandbox_generate_freebsd();
242        }
243
244        #[cfg(target_os = "netbsd")]
245        {
246            return self.sandbox_generate_netbsd();
247        }
248
249        #[cfg(target_os = "openbsd")]
250        {
251            return self.sandbox_generate_openbsd();
252        }
253
254        #[cfg(any(target_os = "linux", target_os = "android"))]
255        {
256            return self.sandbox_generate_linux();
257        }
258
259        #[allow(unreachable_code)]
260        Ok(())
261    }
262
263    fn sandbox_sign(&self) -> Result<()> {
264        #[cfg(target_os = "freebsd")]
265        {
266            return self.sandbox_sign_freebsd();
267        }
268
269        #[cfg(target_os = "netbsd")]
270        {
271            return self.sandbox_sign_netbsd();
272        }
273
274        #[cfg(target_os = "openbsd")]
275        {
276            return self.sandbox_sign_openbsd();
277        }
278
279        #[cfg(any(target_os = "linux", target_os = "android"))]
280        {
281            return self.sandbox_sign_linux();
282        }
283
284        #[allow(unreachable_code)]
285        Ok(())
286    }
287
288    fn sandbox_verify(&self) -> Result<()> {
289        #[cfg(target_os = "freebsd")]
290        {
291            return self.sandbox_verify_freebsd();
292        }
293
294        #[cfg(target_os = "netbsd")]
295        {
296            return self.sandbox_verify_netbsd();
297        }
298
299        #[cfg(target_os = "openbsd")]
300        {
301            return self.sandbox_verify_openbsd();
302        }
303
304        #[cfg(any(target_os = "linux", target_os = "android"))]
305        {
306            return self.sandbox_verify_linux();
307        }
308
309        #[allow(unreachable_code)]
310        Ok(())
311    }
312
313    fn sandbox_check(&self) -> Result<()> {
314        #[cfg(target_os = "freebsd")]
315        {
316            return self.sandbox_check_freebsd();
317        }
318
319        #[cfg(target_os = "netbsd")]
320        {
321            return self.sandbox_check_netbsd();
322        }
323
324        #[cfg(target_os = "openbsd")]
325        {
326            return self.sandbox_check_openbsd();
327        }
328
329        #[cfg(any(target_os = "linux", target_os = "android"))]
330        {
331            return self.sandbox_check_linux();
332        }
333
334        #[allow(unreachable_code)]
335        Ok(())
336    }
337
338    #[cfg(target_os = "freebsd")]
339    fn sandbox_generate_freebsd(&self) -> Result<()> {
340        use capsicum::{enter, CapRights, FileRights, Right};
341
342        // Confine resource limits.
343        Self::sandbox_rlimit_bsd(true)?;
344
345        // Confine using Capsicum.
346        // Generate writes to pubkey and seckey.
347        let mut rights = FileRights::new();
348        rights.allow(Right::Write);
349        rights.allow(Right::Seek);
350        rights.allow(Right::Fstat);
351
352        if let Some(f) = &self.pubkey_file_handle {
353            rights.limit(f).map_err(Error::Capsicum)?;
354        }
355        if let Some(f) = &self.seckey_file_handle {
356            rights.limit(f).map_err(Error::Capsicum)?;
357        }
358
359        if let Some(f) = &self.tty_file_handle {
360            let mut tty_rights = FileRights::new();
361            tty_rights.allow(Right::Read);
362            tty_rights.allow(Right::Write);
363            tty_rights.allow(Right::Ioctl);
364            tty_rights.limit(f).map_err(Error::Capsicum)?;
365        }
366
367        // Enter capability mode.
368        enter().map_err(Error::Capsicum)?;
369
370        Ok(())
371    }
372
373    #[cfg(target_os = "freebsd")]
374    fn sandbox_sign_freebsd(&self) -> Result<()> {
375        use capsicum::{enter, CapRights, FileRights, Right};
376
377        // Confine resource limits.
378        Self::sandbox_rlimit_bsd(true)?;
379
380        // Confine using Capsicum.
381        let mut r_rights = FileRights::new();
382        r_rights.allow(Right::Read);
383        r_rights.allow(Right::Seek);
384        r_rights.allow(Right::Fstat);
385
386        let mut w_rights = FileRights::new();
387        w_rights.allow(Right::Write);
388        w_rights.allow(Right::Seek);
389        w_rights.allow(Right::Fstat);
390
391        if let Some(f) = &self.seckey_file_handle {
392            r_rights.limit(f).map_err(Error::Capsicum)?;
393        }
394        if let Some(f) = &self.msg_file_handle {
395            r_rights.limit(f).map_err(Error::Capsicum)?;
396        }
397
398        // sig file is output.
399        if let Some(f) = &self.sig_file_handle {
400            w_rights.limit(f).map_err(Error::Capsicum)?;
401        }
402
403        if let Some(f) = &self.tty_file_handle {
404            let mut tty_rights = FileRights::new();
405            tty_rights.allow(Right::Read);
406            tty_rights.allow(Right::Write);
407            tty_rights.allow(Right::Ioctl);
408            tty_rights.limit(f).map_err(Error::Capsicum)?;
409        }
410
411        // Enter capability mode.
412        enter().map_err(Error::Capsicum)?;
413
414        Ok(())
415    }
416
417    #[cfg(target_os = "freebsd")]
418    fn sandbox_verify_freebsd(&self) -> Result<()> {
419        use capsicum::{enter, CapRights, FileRights, Right};
420
421        // Confine resource limits.
422        Self::sandbox_rlimit_bsd(true)?;
423
424        // Confine using Capsicum.
425        let mut r_rights = FileRights::new();
426        r_rights.allow(Right::Read);
427        r_rights.allow(Right::Seek);
428        r_rights.allow(Right::Fstat);
429
430        if let Some(f) = &self.pubkey_file_handle {
431            r_rights.limit(f).map_err(Error::Capsicum)?;
432        }
433
434        // In verify, msg_file could be input or output (if embed/gzip extraction).
435        // sig_file is input.
436        if let Some(f) = &self.sig_file_handle {
437            r_rights.limit(f).map_err(Error::Capsicum)?;
438        }
439
440        if let Some(f) = &self.msg_file_handle {
441            if self.embed || self.gzip {
442                let mut w_rights = FileRights::new();
443                w_rights.allow(Right::Write);
444                w_rights.allow(Right::Seek);
445                w_rights.allow(Right::Fstat);
446                w_rights.limit(f).map_err(Error::Capsicum)?;
447            } else {
448                r_rights.limit(f).map_err(Error::Capsicum)?;
449            }
450        }
451
452        // Enter capability mode.
453        enter().map_err(Error::Capsicum)?;
454
455        Ok(())
456    }
457
458    #[cfg(target_os = "freebsd")]
459    fn sandbox_check_freebsd(&self) -> Result<()> {
460        // Confine resource limits.
461        Self::sandbox_rlimit_bsd(false)?;
462
463        // TODO: Opening multiple files from a list in strict capability mode requires work.
464        Ok(())
465    }
466
467    #[cfg(target_os = "netbsd")]
468    fn sandbox_generate_netbsd(&self) -> Result<()> {
469        // Confine resource limits.
470        Self::sandbox_rlimit_bsd(true)?;
471
472        Ok(())
473    }
474
475    #[cfg(target_os = "netbsd")]
476    fn sandbox_sign_netbsd(&self) -> Result<()> {
477        // Confine resource limits.
478        Self::sandbox_rlimit_bsd(true)?;
479
480        Ok(())
481    }
482
483    #[cfg(target_os = "netbsd")]
484    fn sandbox_verify_netbsd(&self) -> Result<()> {
485        // Confine resource limits.
486        Self::sandbox_rlimit_bsd(true)?;
487
488        Ok(())
489    }
490
491    #[cfg(target_os = "netbsd")]
492    fn sandbox_check_netbsd(&self) -> Result<()> {
493        // Confine resource limits.
494        Self::sandbox_rlimit_bsd(false)?;
495
496        Ok(())
497    }
498
499    #[cfg(target_os = "openbsd")]
500    fn sandbox_generate_openbsd(&self) -> Result<()> {
501        use pledge::pledge;
502        use unveil::unveil;
503
504        // Confine resource limits.
505        Self::sandbox_rlimit_bsd(true)?;
506
507        // Confine using unveil(2).
508        unveil("/dev/tty", "rw")?;
509        unveil("", "")?;
510
511        // Confine using pledge(2).
512        pledge("stdio tty", None)?;
513
514        Ok(())
515    }
516
517    #[cfg(target_os = "openbsd")]
518    fn sandbox_sign_openbsd(&self) -> Result<()> {
519        use pledge::pledge;
520        use unveil::unveil;
521
522        // Confine resource limits.
523        Self::sandbox_rlimit_bsd(true)?;
524
525        // Confine using unveil(2).
526        unveil("/dev/tty", "rw")?;
527        unveil("", "")?;
528
529        // Confine using pledge(2).
530        pledge("stdio tty", None)?;
531
532        Ok(())
533    }
534
535    #[cfg(target_os = "openbsd")]
536    fn sandbox_verify_openbsd(&self) -> Result<()> {
537        use pledge::pledge;
538        use unveil::unveil;
539
540        // Confine resource limits.
541        Self::sandbox_rlimit_bsd(true)?;
542
543        // Confine using unveil(2).
544        unveil("", "")?;
545
546        // Confine using pledge(2).
547        pledge("stdio", None)?;
548
549        Ok(())
550    }
551
552    #[cfg(target_os = "openbsd")]
553    fn sandbox_check_openbsd(&self) -> Result<()> {
554        use pledge::pledge;
555        use unveil::unveil;
556
557        // Confine resource limits.
558        Self::sandbox_rlimit_bsd(false)?;
559
560        // Confine using unveil(2).
561        unveil("/", "r")?;
562        unveil("", "")?;
563
564        // Confine using pledge(2).
565        pledge("stdio rpath", None)?;
566
567        Ok(())
568    }
569
570    #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
571    fn sandbox_rlimit_bsd(create: bool) -> Result<()> {
572        use nix::sys::resource::{setrlimit, Resource};
573
574        // Set nprocs and filesize rlimits to zero.
575        // Set memory lock and msgqueue rlimits to zero.
576        // Set core dump file size to zero.
577        const CONFINE_RLIMIT: &[Resource] = &[
578            Resource::RLIMIT_CORE,
579            Resource::RLIMIT_NPROC,
580            Resource::RLIMIT_MEMLOCK,
581        ];
582        for resource in CONFINE_RLIMIT {
583            setrlimit(*resource, 0, 0)?;
584        }
585        if !create {
586            setrlimit(Resource::RLIMIT_FSIZE, 0, 0)?;
587        }
588
589        Ok(())
590    }
591
592    #[cfg(any(target_os = "linux", target_os = "android"))]
593    fn sandbox_basic_linux() -> Result<()> {
594        use nix::sys::prctl::{set_dumpable, set_no_new_privs};
595
596        // Set no-new-privileges (NNP) bit.
597        set_no_new_privs()?;
598
599        // Set dumpable attribute to not-dumpable.
600        set_dumpable(false)?;
601
602        // TODO: Set memory-deny-write-execute (best-effort).
603
604        Ok(())
605    }
606
607    #[cfg(any(target_os = "linux", target_os = "android"))]
608    fn sandbox_rlimit_linux(create: bool) -> Result<()> {
609        use nix::sys::resource::{setrlimit, Resource};
610
611        // Set nprocs rlimits to zero.
612        // Set locks, memory lock and msgqueue rlimits to zero.
613        // Set core dump file size to zero.
614        const CONFINE_RLIMIT: &[Resource] = &[
615            Resource::RLIMIT_CORE,
616            Resource::RLIMIT_NPROC,
617            Resource::RLIMIT_LOCKS,
618            Resource::RLIMIT_MEMLOCK,
619            Resource::RLIMIT_MSGQUEUE,
620        ];
621        for resource in CONFINE_RLIMIT {
622            setrlimit(*resource, 0, 0)?;
623        }
624        if !create {
625            setrlimit(Resource::RLIMIT_FSIZE, 0, 0)?;
626        }
627
628        Ok(())
629    }
630
631    #[cfg(any(target_os = "linux", target_os = "android"))]
632    fn sandbox_generate_linux(&self) -> Result<()> {
633        // Basic sandboxing.
634        Self::sandbox_basic_linux()?;
635
636        // Confine resource limits.
637        Self::sandbox_rlimit_linux(true)?;
638
639        // Confine using landlock(7) (best effort).
640        let _ = Self::sandbox_generate_landlock();
641
642        Ok(())
643    }
644
645    #[cfg(any(target_os = "linux", target_os = "android"))]
646    fn sandbox_sign_linux(&self) -> Result<()> {
647        // Basic sandboxing.
648        Self::sandbox_basic_linux()?;
649
650        // Confine resource limits.
651        Self::sandbox_rlimit_linux(true)?;
652
653        // Confine using landlock(7) (best effort).
654        let _ = Self::sandbox_sign_landlock();
655
656        Ok(())
657    }
658
659    #[cfg(any(target_os = "linux", target_os = "android"))]
660    fn sandbox_verify_linux(&self) -> Result<()> {
661        // Basic sandboxing.
662        Self::sandbox_basic_linux()?;
663
664        // Confine resource limits.
665        Self::sandbox_rlimit_linux(true)?;
666
667        // Confine using landlock(7) (best effort).
668        let _ = Self::sandbox_verify_landlock();
669
670        Ok(())
671    }
672
673    #[cfg(any(target_os = "linux", target_os = "android"))]
674    fn sandbox_check_linux(&self) -> Result<()> {
675        // Basic sandboxing.
676        Self::sandbox_basic_linux()?;
677
678        // Confine resource limits.
679        Self::sandbox_rlimit_linux(false)?;
680
681        // Confine using landlock(7) (best effort).
682        let _ = Self::sandbox_check_landlock();
683
684        Ok(())
685    }
686
687    #[cfg(any(target_os = "linux", target_os = "android"))]
688    fn sandbox_generate_landlock() -> Result<()> {
689        use landlock::{AccessFs, BitFlags, RulesetCreatedAttr};
690
691        // Initialize landock(7) ruleset.
692        let mut ruleset = Self::sandbox_init_landlock()?;
693
694        // Allow read/write/ioctl to /dev/tty for password prompt.
695        // Allow read from /dev/{,u}random for RNG.
696        let confine_path: &[(&str, BitFlags<AccessFs, u64>)] = &[
697            (
698                "/dev/tty",
699                AccessFs::ReadFile | AccessFs::WriteFile | AccessFs::IoctlDev,
700            ),
701            ("/dev/random", AccessFs::ReadFile.into()),
702            ("/dev/urandom", AccessFs::ReadFile.into()),
703        ];
704        for (path, access_fs) in confine_path {
705            ruleset = ruleset.add_rules(landlock::path_beneath_rules(&[path], *access_fs))?;
706        }
707
708        ruleset.restrict_self()?;
709
710        Ok(())
711    }
712
713    #[cfg(any(target_os = "linux", target_os = "android"))]
714    fn sandbox_sign_landlock() -> Result<()> {
715        Self::sandbox_generate_landlock()
716    }
717
718    #[cfg(any(target_os = "linux", target_os = "android"))]
719    fn sandbox_verify_landlock() -> Result<()> {
720        // Install empty landlock(7) filter to deny all access.
721        Self::sandbox_init_landlock()?.restrict_self()?;
722
723        Ok(())
724    }
725
726    #[cfg(any(target_os = "linux", target_os = "android"))]
727    fn sandbox_check_landlock() -> Result<()> {
728        use landlock::{AccessFs, RulesetCreatedAttr};
729
730        // Initialize landock(7) ruleset.
731        let ruleset = Self::sandbox_init_landlock()?;
732
733        // Create a read-only sandbox.
734        ruleset
735            .add_rules(landlock::path_beneath_rules(&["/"], AccessFs::ReadFile))?
736            .restrict_self()?;
737
738        Ok(())
739    }
740
741    #[cfg(any(target_os = "linux", target_os = "android"))]
742    fn sandbox_init_landlock() -> Result<landlock::RulesetCreated> {
743        use landlock::{
744            Access, AccessFs, AccessNet, CompatLevel, Compatible, Ruleset, RulesetAttr, Scope, ABI,
745        };
746
747        Ok(Ruleset::default()
748            .handle_access(AccessFs::from_all(ABI::V6))?
749            .handle_access(AccessNet::from_all(ABI::V6))?
750            .scope(Scope::from_all(ABI::V6))?
751            .set_compatibility(CompatLevel::BestEffort)
752            .create()?)
753    }
754
755    fn preopen_tty(&mut self) -> Result<()> {
756        #[cfg(unix)]
757        if !self.nopass {
758            use std::os::unix::fs::OpenOptionsExt;
759
760            // Attempt to open /dev/tty.
761            // It may not be available for noninteractive uses.
762            self.tty_file_handle = std::fs::OpenOptions::new()
763                .read(true)
764                .write(true)
765                .custom_flags(nix::libc::O_NOFOLLOW)
766                .open("/dev/tty")
767                .ok();
768        }
769        Ok(())
770    }
771
772    fn preopen_generate(&mut self) -> Result<()> {
773        self.preopen_tty()?;
774
775        let pubkey_path = self
776            .pubkey
777            .clone()
778            .unwrap_or_else(|| PathBuf::from("key.pub"));
779        let seckey_path = self
780            .seckey
781            .clone()
782            .unwrap_or_else(|| PathBuf::from("key.sec"));
783
784        self.pubkey_file_handle = Some(open(&pubkey_path, true)?);
785        self.seckey_file_handle = Some(open(&seckey_path, true)?);
786
787        Ok(())
788    }
789
790    fn preopen_sign(&mut self) -> Result<()> {
791        self.preopen_tty()?;
792
793        if let Some(path) = &self.seckey {
794            if path.as_os_str() != "-" {
795                self.seckey_file_handle = Some(open(path, false)?);
796            }
797        }
798
799        if let Some(path) = &self.msg_file {
800            if path.as_os_str() != "-" {
801                self.msg_file_handle = Some(open(path, false)?);
802            }
803        }
804
805        let sig_path = self.sig_file.clone().unwrap_or_else(|| {
806            if let Some(msg) = &self.msg_file {
807                let mut path = msg.clone();
808                path.set_extension("sig");
809                path
810            } else {
811                PathBuf::from("-")
812            }
813        });
814
815        if sig_path.as_os_str() != "-" {
816            self.sig_file_handle = Some(open(&sig_path, true)?);
817        }
818
819        Ok(())
820    }
821
822    fn preopen_verify(&mut self) -> Result<()> {
823        if let Some(path) = &self.pubkey {
824            self.pubkey_file_handle = Some(open(path, false)?);
825        }
826
827        let (msg_path, sig_path) = self.resolve_verify_paths()?;
828        if msg_path.as_os_str() != "-" {
829            // For verify, msg is input unless embed or gzip (where it is output)
830            // If embed/gzip, we create it new.
831            if self.embed || self.gzip {
832                self.msg_file_handle = Some(open(&msg_path, true)?);
833            } else {
834                self.msg_file_handle = Some(open(&msg_path, false)?);
835            }
836        }
837
838        if sig_path.as_os_str() != "-" {
839            self.sig_file_handle = Some(open(&sig_path, false)?);
840        }
841
842        Ok(())
843    }
844
845    fn preopen_check(&mut self) -> Result<()> {
846        if let Some(path) = &self.pubkey {
847            self.pubkey_file_handle = Some(open(path, false)?);
848        }
849
850        let sig_path = self
851            .sig_file
852            .clone()
853            .or_else(|| self.args.first().cloned())
854            .unwrap_or_else(|| PathBuf::from("-"));
855        if sig_path.as_os_str() != "-" {
856            self.sig_file_handle = Some(open(&sig_path, false)?);
857        }
858        Ok(())
859    }
860
861    fn resolve_verify_paths(&self) -> Result<(PathBuf, PathBuf)> {
862        if self.gzip {
863            let sig = self
864                .sig_file
865                .clone()
866                .or_else(|| self.args.first().cloned())
867                .unwrap_or_else(|| PathBuf::from("-"));
868            let msg = self.msg_file.clone().unwrap_or_else(|| PathBuf::from("-"));
869            Ok((msg, sig))
870        } else {
871            let msg = if self.embed {
872                self.msg_file.clone().unwrap_or_else(|| {
873                    self.sig_file.as_ref().map_or_else(
874                        || PathBuf::from("msg.out"),
875                        |sig| {
876                            let mut path = sig.clone();
877                            path.set_extension("");
878                            path
879                        },
880                    )
881                })
882            } else if let Some(msg) = &self.msg_file {
883                msg.clone()
884            } else {
885                return Err(Error::Arg("missing -m".into()));
886            };
887
888            let sig = self
889                .sig_file
890                .clone()
891                .or_else(|| self.args.first().cloned())
892                .unwrap_or_else(|| {
893                    let mut path = msg.clone();
894                    if let Some(ext) = path.extension() {
895                        let mut ext_str = ext.to_os_string();
896                        ext_str.push(".sig");
897                        path.set_extension(ext_str);
898                    } else {
899                        path.set_extension("sig");
900                    }
901                    path
902                });
903            Ok((msg, sig))
904        }
905    }
906}