libsignify_rs/
signify.rs

1//
2// signify-rs: cryptographically sign and verify files
3// signify-rs: cryptographically sign and verify files
4// src/signify.rs: Configuration and Mode definitions
5//
6// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
7// SPDX-License-Identifier: ISC
8//
9
10use crate::crypto;
11use crate::error::{Error, Result};
12use crate::ops::{check_checksums, KeyGenerator, Signer, Verifier};
13use std::path::PathBuf;
14
15/// Operation mode.
16#[derive(Debug, PartialEq, Eq, Clone, Copy)]
17pub enum Mode {
18    /// Check signatures.
19    Check,
20    /// Generate key pair.
21    Generate,
22    /// Sign a file.
23    Sign,
24    /// Verify a signature.
25    Verify,
26}
27
28/// Runtime configuration.
29///
30/// This struct holds all configuration options parsed from command-line arguments
31/// or set programmatically.
32///
33/// # Examples
34///
35/// ```
36/// use libsignify_rs::signify::{Signify, Mode};
37/// use std::path::PathBuf;
38///
39/// let mut signify = Signify::default();
40/// signify.mode = Some(Mode::Sign);
41/// signify.seckey = Some(PathBuf::from("key.sec"));
42/// signify.embed = true;
43///
44/// assert_eq!(signify.mode, Some(Mode::Sign));
45/// assert!(signify.embed);
46/// ```
47#[derive(Default, Debug, Clone)]
48pub struct Signify {
49    /// Operation mode.
50    pub mode: Option<Mode>,
51    /// Comment for key generation.
52    pub comment: Option<String>,
53    /// Embedding mode flag.
54    pub embed: bool,
55    /// Message file path.
56    pub msg_file: Option<PathBuf>,
57    /// No password flag.
58    pub nopass: bool,
59    /// Public key path.
60    pub pubkey: Option<PathBuf>,
61    /// Quiet mode flag.
62    pub quiet: bool,
63    /// Secret key path.
64    pub seckey: Option<PathBuf>,
65    /// Signature file path.
66    pub sig_file: Option<PathBuf>,
67    /// Gzip mode flag.
68    pub gzip: bool,
69    /// Key ID for keyring lookup.
70    pub key_id: Option<i32>,
71    /// Positional arguments.
72    pub args: Vec<PathBuf>,
73}
74
75impl Signify {
76    /// Execute the requested operation based on configuration.
77    ///
78    /// # Errors
79    /// Returns errors if the operation fails or arguments are missing.
80    pub fn execute(&self) -> Result<()> {
81        match self.mode {
82            Some(Mode::Generate) => self.execute_generate(),
83            Some(Mode::Sign) => self.execute_sign(),
84            Some(Mode::Verify) => self.execute_verify(),
85            Some(Mode::Check) => self.execute_check(),
86            None => Err(Error::MissingMode),
87        }
88    }
89
90    fn execute_generate(&self) -> Result<()> {
91        let pubkey_path = self
92            .pubkey
93            .clone()
94            .unwrap_or_else(|| PathBuf::from("key.pub"));
95        let seckey_path = self
96            .seckey
97            .clone()
98            .unwrap_or_else(|| PathBuf::from("key.sec"));
99        let rounds = if self.nopass {
100            0
101        } else {
102            crypto::DEFAULT_ROUNDS
103        };
104        let comment = self.comment.as_deref().unwrap_or("signify key");
105
106        let mut generator = KeyGenerator::new().rounds(rounds).comment(comment);
107        if let Some(id) = self.key_id {
108            generator = generator.key_id(id);
109        }
110        generator.generate(&pubkey_path, &seckey_path)
111    }
112
113    fn execute_sign(&self) -> Result<()> {
114        let Some(seckey_path) = &self.seckey else {
115            return Err(Error::RequiredArg("-s"));
116        };
117
118        let Some(msg_path) = &self.msg_file else {
119            return Err(Error::Arg("missing -m".into()));
120        };
121
122        let sig_path = self.sig_file.clone().unwrap_or_else(|| {
123            let mut path = msg_path.clone();
124            path.set_extension("sig");
125            path
126        });
127
128        let mut signer = Signer::new()
129            .seckey(seckey_path)
130            .embed(self.embed)
131            .gzip(self.gzip);
132
133        if let Some(id) = self.key_id {
134            signer = signer.key_id(id);
135        }
136
137        signer.sign(msg_path, &sig_path)
138    }
139
140    fn execute_verify(&self) -> Result<()> {
141        let (msg_path, sig_path) = if self.gzip {
142            let sig = self
143                .sig_file
144                .clone()
145                .or_else(|| self.args.first().cloned())
146                .unwrap_or_else(|| PathBuf::from("-"));
147            let msg = self.msg_file.clone().unwrap_or_else(|| PathBuf::from("-"));
148            (msg, sig)
149        } else {
150            let msg = if self.embed {
151                self.msg_file.clone().unwrap_or_else(|| {
152                    self.sig_file.as_ref().map_or_else(
153                        || PathBuf::from("msg.out"),
154                        |sig| {
155                            let mut path = sig.clone();
156                            path.set_extension("");
157                            path
158                        },
159                    )
160                })
161            } else if let Some(msg) = &self.msg_file {
162                msg.clone()
163            } else {
164                return Err(Error::Arg("missing -m".into()));
165            };
166
167            let sig = self
168                .sig_file
169                .clone()
170                .or_else(|| self.args.first().cloned())
171                .unwrap_or_else(|| {
172                    let mut path = msg.clone();
173                    if let Some(ext) = path.extension() {
174                        let mut ext_str = ext.to_os_string();
175                        ext_str.push(".sig");
176                        path.set_extension(ext_str);
177                    } else {
178                        path.set_extension("sig");
179                    }
180                    path
181                });
182            (msg, sig)
183        };
184
185        let mut verifier = Verifier::new()
186            .quiet(self.quiet)
187            .embed(self.embed)
188            .gzip(self.gzip);
189
190        if let Some(path) = &self.pubkey {
191            verifier = verifier.pubkey(path);
192        }
193
194        verifier.verify(&msg_path, &sig_path)
195    }
196
197    fn execute_check(&self) -> Result<()> {
198        let Some(pubkey_path) = &self.pubkey else {
199            return Err(Error::RequiredArg("-p"));
200        };
201
202        let Some(sig_path) = &self.sig_file else {
203            return Err(Error::RequiredArg("-x"));
204        };
205
206        check_checksums(pubkey_path, sig_path, self.quiet)
207    }
208}