1use 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#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18pub enum Mode {
19 Check,
21 Generate,
23 Sign,
25 Verify,
27}
28
29#[derive(Default, Debug)]
49pub struct Signify {
50 pub mode: Option<Mode>,
52 pub comment: Option<String>,
54 pub embed: bool,
56 pub msg_file: Option<PathBuf>,
58 pub nopass: bool,
60 pub pubkey: Option<PathBuf>,
62 pub quiet: bool,
64 pub seckey: Option<PathBuf>,
66 pub sig_file: Option<PathBuf>,
68 pub gzip: bool,
70 pub key_id: Option<i32>,
72 pub args: Vec<PathBuf>,
74
75 pub pubkey_file_handle: Option<File>,
78 pub seckey_file_handle: Option<File>,
80 pub msg_file_handle: Option<File>,
82 pub sig_file_handle: Option<File>,
84 pub tty_file_handle: Option<File>,
86}
87
88impl Signify {
89 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 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 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 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 Self::sandbox_rlimit_bsd(true)?;
344
345 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().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 Self::sandbox_rlimit_bsd(true)?;
379
380 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 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().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 Self::sandbox_rlimit_bsd(true)?;
423
424 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 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().map_err(Error::Capsicum)?;
454
455 Ok(())
456 }
457
458 #[cfg(target_os = "freebsd")]
459 fn sandbox_check_freebsd(&self) -> Result<()> {
460 Self::sandbox_rlimit_bsd(false)?;
462
463 Ok(())
465 }
466
467 #[cfg(target_os = "netbsd")]
468 fn sandbox_generate_netbsd(&self) -> Result<()> {
469 Self::sandbox_rlimit_bsd(true)?;
471
472 Ok(())
473 }
474
475 #[cfg(target_os = "netbsd")]
476 fn sandbox_sign_netbsd(&self) -> Result<()> {
477 Self::sandbox_rlimit_bsd(true)?;
479
480 Ok(())
481 }
482
483 #[cfg(target_os = "netbsd")]
484 fn sandbox_verify_netbsd(&self) -> Result<()> {
485 Self::sandbox_rlimit_bsd(true)?;
487
488 Ok(())
489 }
490
491 #[cfg(target_os = "netbsd")]
492 fn sandbox_check_netbsd(&self) -> Result<()> {
493 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 Self::sandbox_rlimit_bsd(true)?;
506
507 unveil("/dev/tty", "rw")?;
509 unveil("", "")?;
510
511 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 Self::sandbox_rlimit_bsd(true)?;
524
525 unveil("/dev/tty", "rw")?;
527 unveil("", "")?;
528
529 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 Self::sandbox_rlimit_bsd(true)?;
542
543 unveil("", "")?;
545
546 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 Self::sandbox_rlimit_bsd(false)?;
559
560 unveil("/", "r")?;
562 unveil("", "")?;
563
564 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 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_privs()?;
598
599 set_dumpable(false)?;
601
602 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 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 Self::sandbox_basic_linux()?;
635
636 Self::sandbox_rlimit_linux(true)?;
638
639 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 Self::sandbox_basic_linux()?;
649
650 Self::sandbox_rlimit_linux(true)?;
652
653 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 Self::sandbox_basic_linux()?;
663
664 Self::sandbox_rlimit_linux(true)?;
666
667 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 Self::sandbox_basic_linux()?;
677
678 Self::sandbox_rlimit_linux(false)?;
680
681 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 let mut ruleset = Self::sandbox_init_landlock()?;
693
694 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 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 let ruleset = Self::sandbox_init_landlock()?;
732
733 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 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 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}