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