libsignify_rs/
error.rs

1//
2// signify-rs: cryptographically sign and verify files
3// lib/src/error.rs: Error handling functions
4//
5// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
6// Based in part upon OpenBSD's signify which is:
7//   Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
8//   Copyright (c) 2016 Marc Espie <espie@openbsd.org>
9//   Copyright (c) 2019 Adrian Perez de Castro <aperez@igalia.com>
10//   Copyright (c) 2019 Scott Bennett and other contributors
11//   SPDX-License-Identifier: ISC
12//
13// SPDX-License-Identifier: ISC
14
15use std::fmt;
16use std::io;
17use std::path::PathBuf;
18
19/// Custom Error type for Signify operations.
20#[derive(Debug)]
21pub enum Error {
22    /// I/O error.
23    Io(io::Error),
24    /// Key length is invalid.
25    InvalidKeyLength,
26    /// Unsupported public key algorithm (expected Ed).
27    UnsupportedPkAlgo,
28    /// Unsupported KDF algorithm (expected BK).
29    UnsupportedKdfAlgo,
30    /// File is too short.
31    FileTooShort,
32    /// File is too large (exceeds 1GB limit for embedded verification).
33    FileTooLarge,
34    /// Invalid comment header (expected "untrusted comment: ...").
35    InvalidCommentHeader,
36    /// Invalid comment (not utf8)
37    InvalidCommentUtf8,
38    /// Password confirmation failed.
39    PasswordMismatch,
40    /// Failed to read password.
41    PasswordReadFailed,
42    /// Incorrect passphrase (checksum mismatch).
43    IncorrectPassphrase,
44    /// Missing public key (no comment or autolocate failed).
45    MissingPubKey,
46    /// Key fingerprint mismatch.
47    KeyMismatch,
48    /// Signature verification failed.
49    VerifyFailed,
50    /// Autolocate failed to find the key.
51    AutolocateFailed(PathBuf, Box<Error>),
52    /// Key name compliance check failed.
53    InvalidKeyName,
54    /// Path does not contain a filename.
55    InvalidPath,
56    /// Checksum verification failed.
57    CheckFailed,
58    /// Checksum mismatch for a specific file.
59    ChecksumMismatch(String),
60    /// Unable to parse checksum line.
61    ChecksumParseFailed(String),
62    /// Unsupported hash algorithm.
63    UnsupportedHashAlgo(String),
64    /// Integer overflow.
65    Overflow,
66    /// Base64 decoding failed.
67    Base64Decode(base64ct::Error),
68    /// Argument parsing error.
69    Arg(lexopt::Error),
70    /// Invalid signature length.
71    InvalidSignatureLength,
72    /// Missing Gzip header.
73    MissingGzipHeader,
74    /// Missing signature in Gzip comment.
75    MissingGzipSignature,
76    /// Missing newline in signature.
77    MissingSignatureNewline,
78    /// Keyring support is disabled.
79    KeyringDisabled,
80    /// Invalid key ID.
81    InvalidKeyId,
82    /// Required argument missing.
83    RequiredArg(&'static str),
84    /// Mode not specified.
85    MissingMode,
86    /// Invalid UTF-8 in password.
87    InvalidPasswordUtf8,
88    /// Invalid UTF-8 in signature.
89    InvalidSignatureUtf8,
90    /// Password is too weak.
91    WeakPassword(Option<String>),
92    /// Keyring error.
93    #[cfg(any(target_os = "linux", target_os = "android"))]
94    Keyring(linux_keyutils::KeyError),
95    /// Cryptographic error (e.g., invalid key format).
96    Crypto(ed25519_compact::Error),
97    /// RNG error.
98    Rng(rand_core::OsError),
99    #[cfg(unix)]
100    /// UNIX error.
101    Nix(nix::errno::Errno),
102    /// Landlock error.
103    #[cfg(any(target_os = "linux", target_os = "android"))]
104    Landlock(landlock::RulesetError),
105    /// Capsicum error.
106    #[cfg(target_os = "freebsd")]
107    Capsicum(io::Error),
108    /// Pledge error.
109    #[cfg(target_os = "openbsd")]
110    Pledge(pledge::Error),
111    /// Unveil error.
112    #[cfg(target_os = "openbsd")]
113    Unveil(unveil::Error),
114}
115
116impl std::error::Error for Error {
117    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118        match self {
119            Self::Io(err) => Some(err),
120            Self::Base64Decode(err) => Some(err),
121            Self::Arg(err) => Some(err),
122            #[cfg(unix)]
123            Self::Nix(err) => Some(err),
124            #[cfg(any(target_os = "linux", target_os = "android"))]
125            Self::Landlock(err) => Some(err),
126            #[cfg(target_os = "freebsd")]
127            Self::Capsicum(err) => Some(err),
128            #[cfg(target_os = "openbsd")]
129            Self::Pledge(err) => Some(err),
130            #[cfg(target_os = "openbsd")]
131            Self::Unveil(err) => Some(err),
132            _ => None,
133        }
134    }
135}
136
137impl fmt::Display for Error {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            Self::Io(err) => write!(f, "IO error: {err}"),
141            Self::InvalidKeyLength => write!(f, "invalid key length"),
142            Self::UnsupportedPkAlgo => write!(f, "unsupported public key algorithm"),
143            Self::UnsupportedKdfAlgo => write!(f, "unsupported KDF algorithm"),
144            Self::FileTooShort => write!(f, "file too short"),
145            Self::FileTooLarge => write!(f, "file too large (memory limit exceeded)"),
146            Self::InvalidCommentHeader => write!(f, "invalid comment header"),
147            Self::InvalidCommentUtf8 => write!(f, "invalid comment (not utf8)"),
148            Self::InvalidPasswordUtf8 => write!(f, "invalid password (not utf8)"),
149            Self::InvalidSignatureUtf8 => write!(f, "invalid signature (base64 is not utf8)"),
150            Self::Base64Decode(err) => write!(f, "base64 decode error: {err}"),
151            Self::Arg(err) => write!(f, "argument error: {err}"),
152            Self::PasswordMismatch => write!(f, "password mismatch"),
153            Self::PasswordReadFailed => write!(f, "failed to read password"),
154            Self::IncorrectPassphrase => write!(f, "incorrect passphrase"),
155            Self::MissingPubKey => write!(f, "public key not found"),
156            Self::KeyMismatch => write!(f, "verification failed: checked against wrong key"),
157            Self::VerifyFailed => write!(f, "signature verification failed"),
158            Self::AutolocateFailed(path, err) => {
159                write!(f, "autolocate failed loading {}: {err}", path.display())
160            }
161            Self::InvalidKeyName => write!(f, "invalid key name"),
162            Self::InvalidPath => write!(f, "invalid path"),
163            Self::CheckFailed => write!(f, "checksum check failed"),
164            Self::ChecksumMismatch(file) => write!(f, "{file}: FAIL"),
165            Self::ChecksumParseFailed(line) => write!(f, "unable to parse: {line}"),
166            Self::UnsupportedHashAlgo(algo) => write!(f, "unsupported hash algorithm: {algo}"),
167            Self::Overflow => write!(f, "limit exceeded"),
168            Self::InvalidSignatureLength => write!(f, "invalid signature length"),
169            Self::MissingGzipHeader => write!(f, "missing gzip header"),
170            Self::MissingGzipSignature => write!(f, "missing signature in gzip comment"),
171            Self::MissingSignatureNewline => write!(f, "missing newline in signature"),
172            Self::KeyringDisabled => write!(f, "keyring support disabled"),
173            Self::InvalidKeyId => write!(f, "invalid key id"),
174            Self::RequiredArg(arg) => write!(f, "missing required argument: {arg}"),
175            Self::MissingMode => write!(f, "must specify mode"),
176            Self::WeakPassword(feedback) => {
177                if let Some(msg) = feedback {
178                    write!(f, "password too weak: {msg}")
179                } else {
180                    write!(f, "password too weak")
181                }
182            }
183            #[cfg(any(target_os = "linux", target_os = "android"))]
184            Self::Keyring(err) => write!(f, "keyring error: {err:?}"),
185            Self::Crypto(err) => write!(f, "crypto error: {err}"),
186            Self::Rng(err) => write!(f, "rng error: {err}"),
187            #[cfg(unix)]
188            Self::Nix(err) => write!(f, "UNIX error: {err}"),
189            #[cfg(any(target_os = "linux", target_os = "android"))]
190            Self::Landlock(err) => write!(f, "landlock error: {err}"),
191            #[cfg(target_os = "freebsd")]
192            Self::Capsicum(err) => write!(f, "capsicum error: {err}"),
193            #[cfg(target_os = "openbsd")]
194            Self::Pledge(err) => write!(f, "pledge error: {err}"),
195            #[cfg(target_os = "openbsd")]
196            Self::Unveil(err) => write!(f, "unveil error: {err}"),
197        }
198    }
199}
200
201impl From<ed25519_compact::Error> for Error {
202    fn from(err: ed25519_compact::Error) -> Self {
203        Self::Crypto(err)
204    }
205}
206
207#[cfg(unix)]
208impl From<nix::errno::Errno> for Error {
209    fn from(err: nix::errno::Errno) -> Self {
210        Self::Nix(err)
211    }
212}
213
214impl From<io::Error> for Error {
215    fn from(err: io::Error) -> Self {
216        Self::Io(err)
217    }
218}
219
220impl From<base64ct::Error> for Error {
221    fn from(err: base64ct::Error) -> Self {
222        Self::Base64Decode(err)
223    }
224}
225
226impl From<lexopt::Error> for Error {
227    fn from(err: lexopt::Error) -> Self {
228        Self::Arg(err)
229    }
230}
231
232#[cfg(any(target_os = "linux", target_os = "android"))]
233impl From<linux_keyutils::KeyError> for Error {
234    fn from(err: linux_keyutils::KeyError) -> Self {
235        Self::Keyring(err)
236    }
237}
238
239impl From<rand_core::OsError> for Error {
240    fn from(err: rand_core::OsError) -> Self {
241        Self::Rng(err)
242    }
243}
244
245#[cfg(target_os = "openbsd")]
246impl From<pledge::Error> for Error {
247    fn from(err: pledge::Error) -> Self {
248        Self::Pledge(err)
249    }
250}
251
252#[cfg(target_os = "openbsd")]
253impl From<unveil::Error> for Error {
254    fn from(err: unveil::Error) -> Self {
255        Self::Unveil(err)
256    }
257}
258
259#[cfg(any(target_os = "linux", target_os = "android"))]
260impl From<landlock::RulesetError> for Error {
261    fn from(err: landlock::RulesetError) -> Self {
262        Self::Landlock(err)
263    }
264}
265
266/// Result alias for Signify operations.
267pub type Result<T> = std::result::Result<T, Error>;