Skip to main content

smime_tree/
error.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4// ---------------------------------------------------------------------------
5// Public result types (live here so error.rs does not depend on verify.rs)
6// ---------------------------------------------------------------------------
7
8/// Overall result from verifying a `multipart/signed` S/MIME message.
9///
10/// `Ok(VerificationResult)` is returned only when at least one signer
11/// verified successfully.  Per-signer detail (including failures for other
12/// signers) is available in the `signers` vec.
13///
14/// # Security
15///
16/// [`is_verified`][Self::is_verified] returns `true` if **any one** signer passed
17/// verification.  For messages with multiple signers, inspect [`signers`][Self::signers]
18/// individually to confirm all expected signers are present and valid.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[non_exhaustive]
21pub struct VerificationResult {
22    /// One entry per `SignerInfo` found in the `SignedData`.
23    pub signers: Vec<SignerResult>,
24}
25
26impl VerificationResult {
27    /// Returns `true` if at least one signer verified successfully.
28    ///
29    /// # Security
30    ///
31    /// This method returns `true` if **any one** signer's certificate and signature verified
32    /// successfully. In a message with multiple signers, some may have failed verification
33    /// while this method still returns `true`. For security-critical decisions — especially
34    /// when a policy requires all signers to be valid — use [`all_verified`][Self::all_verified]
35    /// instead, or iterate [`signers`][Self::signers] directly.
36    #[must_use]
37    pub fn is_verified(&self) -> bool {
38        self.signers.iter().any(|s| s.verified)
39    }
40
41    /// Returns `true` only if **every** signer verified successfully.
42    ///
43    /// Prefer this over [`is_verified`][Self::is_verified] whenever your policy requires
44    /// that all signers on a message are valid — for example, dual-control workflows or
45    /// messages that must carry a specific set of signers.
46    ///
47    /// Returns `false` if `signers` is empty (no signers at all is not a success).
48    #[must_use]
49    pub fn all_verified(&self) -> bool {
50        !self.signers.is_empty() && self.signers.iter().all(|s| s.verified)
51    }
52
53    /// Returns an iterator over only the [`SignerResult`] entries that verified successfully.
54    ///
55    /// Useful when you need to inspect or count the verified signers without examining
56    /// failed ones. Combine with [`all_verified`][Self::all_verified] to enforce a
57    /// minimum verified-signer count.
58    pub fn verified_signers(&self) -> impl Iterator<Item = &SignerResult> {
59        self.signers.iter().filter(|s| s.verified)
60    }
61}
62
63/// Result for a single `SignerInfo` within a `SignedData`.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[non_exhaustive]
66pub struct SignerResult {
67    /// `true` iff all of the following succeeded:
68    /// message-digest check, signature verification, and cert-chain validation.
69    pub verified: bool,
70    /// Distinguished name of the signer's certificate subject, if found.
71    pub subject: Option<String>,
72    /// Human-readable error string when `verified == false`.
73    pub error: Option<String>,
74}
75
76// ---------------------------------------------------------------------------
77
78/// Structured failure reason for certificate chain validation.
79///
80/// Returned inside [`SmimeError::CertChain`].  Callers can match on this enum
81/// to distinguish specific failure modes (e.g. expired certificate vs. missing
82/// trust anchor) without parsing error strings.
83///
84/// # Compatibility stubs
85///
86/// Two variants — `AllTrustAnchorsExpired` and `PathLenViolated` — are marked
87/// `#[deprecated]` and are **never emitted** by the current validator.  They
88/// exist solely so that serialized data produced by older versions of this crate
89/// can be deserialized without error.  New code should not match on them; see
90/// each variant's deprecation message for the current equivalent.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[non_exhaustive]
93pub enum CertChainError {
94    /// No trust anchors were provided.
95    NoTrustAnchors,
96    /// Certificate validity period does not contain the check time.
97    CertificateExpired {
98        /// Subject DN of the certificate that failed the validity check.
99        subject: String,
100        /// The certificate's `notAfter` date (ISO 8601).
101        not_after: String,
102    },
103    /// All trust anchors matching the issuer DN are outside their validity period.
104    ///
105    /// Present only for deserialization compatibility with data produced by older versions
106    /// of this crate; never emitted by the current validator.  Match [`CertChainError::TooDeep`]
107    /// or [`CertChainError::CertificateExpired`] for the equivalent current failure modes.
108    #[deprecated(since = "0.2.0", note = "match CertificateExpired instead")]
109    AllTrustAnchorsExpired {
110        /// Issuer DN for which all matching trust anchors were expired.
111        issuer: String,
112    },
113    /// Certificate signature does not match the issuer's public key.
114    SignatureVerification {
115        /// Subject DN of the certificate whose signature could not be verified.
116        subject: String,
117    },
118    /// A `pathLen` constraint in a CA certificate was violated.
119    ///
120    /// Present only for deserialization compatibility with data produced by older versions
121    /// of this crate; never emitted by the current validator.  Match
122    /// [`CertChainError::TooDeep`] for the equivalent current failure mode.
123    #[deprecated = "never produced by the current pkix-chain-based validator; match TooDeep instead"]
124    PathLenViolated {
125        /// Number of intermediate CA certificates below the constrained issuer.
126        intermediate_count: usize,
127        /// The `pathLen` value from the `BasicConstraints` extension.
128        path_len: u8,
129    },
130    /// An intermediate certificate lacks the CA flag (`BasicConstraints.cA = false`).
131    NotACa {
132        /// Subject DN of the certificate that was found not to be a CA.
133        subject: String,
134    },
135    /// The certificate chain contains a cycle (A signed by B, B signed by A).
136    Cycle {
137        /// Subject DN of the certificate that closed the cycle.
138        subject: String,
139    },
140    /// No trust anchor or intermediate certificate matches the issuer DN.
141    NoMatchingIssuer {
142        /// Issuer DN for which no matching certificate was found.
143        issuer: String,
144    },
145    /// Certificate chain exceeds the maximum allowed depth.
146    TooDeep,
147    /// Other chain validation error (DER encoding failures, etc.).
148    Other(String),
149}
150
151#[allow(deprecated)]
152impl fmt::Display for CertChainError {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            CertChainError::NoTrustAnchors => write!(f, "no trust anchors provided"),
156            CertChainError::CertificateExpired { subject, not_after } => write!(
157                f,
158                "certificate '{subject}' expired or not yet valid (not_after={not_after})"
159            ),
160            CertChainError::AllTrustAnchorsExpired { issuer } => write!(
161                f,
162                "all trust anchors matching issuer '{issuer}' are expired or not yet valid"
163            ),
164            CertChainError::SignatureVerification { subject } => {
165                write!(f, "issuer signature on '{subject}' does not match")
166            }
167            CertChainError::PathLenViolated {
168                intermediate_count,
169                path_len,
170            } => write!(
171                f,
172                "pathLen constraint violated: {intermediate_count} intermediate CA(s) \
173                 but pathLen is {path_len}"
174            ),
175            CertChainError::NotACa { subject } => {
176                write!(f, "certificate '{subject}' is not a CA")
177            }
178            CertChainError::Cycle { subject } => {
179                write!(f, "certificate chain cycle at '{subject}'")
180            }
181            CertChainError::NoMatchingIssuer { issuer } => write!(
182                f,
183                "no trust anchor or intermediate matches issuer '{issuer}' \
184                 (add the CA root cert to trust_anchors)"
185            ),
186            CertChainError::TooDeep => {
187                write!(f, "certificate chain exceeds the maximum allowed depth")
188            }
189            CertChainError::Other(msg) => write!(f, "{msg}"),
190        }
191    }
192}
193
194/// Error type for S/MIME operations.
195///
196/// # Serde round-trip
197///
198/// `SmimeError` implements `Serialize` and `Deserialize`. The `Der` variant
199/// contains a `der::Error` which has no serde support, so it round-trips
200/// through a string representation: serialized as `{"Der":"<message>"}`,
201/// deserialized back as `SmimeError::Der` with a `der::Error` carrying
202/// the message (the original error kind is not preserved across
203/// serialization boundaries).
204#[derive(Debug, Clone, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum SmimeError {
207    /// DER encoding/decoding failure.
208    Der(der::Error),
209    /// The algorithm identified by the given OID is not supported.
210    UnsupportedAlgorithm(String),
211    /// No decryption key matches any RecipientInfo in the EnvelopedData.
212    NoMatchingRecipient,
213    /// Signature verification failed.
214    SignatureVerification,
215    /// Certificate chain validation failed.
216    CertChain(CertChainError),
217    /// Input is structurally malformed (e.g. missing required CMS fields).
218    MalformedInput(String),
219    /// `encrypt()` was called with an empty recipients slice.
220    NoRecipients,
221    /// OS random number generator failed during a crypto operation.
222    /// This indicates a catastrophic system-level failure.
223    RngFailure(String),
224    /// Decryption failed (e.g. bad padding, wrong CEK, or corrupted ciphertext).
225    DecryptionFailed(String),
226    /// Catch-all for operation errors not covered by a more specific variant.
227    Other(String),
228    /// All signers in the CMS SignedData failed verification.
229    /// The `signers` vec contains per-signer error details.
230    AllSignersFailed(Vec<SignerResult>),
231    /// The `ContentInfo` content type is not what this operation expects.
232    /// For example, passing a `SignedData` blob to `decrypt()`.
233    WrongContentType(String),
234    /// The content contains a MIME boundary that matches the generated separator even after
235    /// 8 random retries. This is extraordinarily unlikely in practice.
236    BoundaryCollision,
237}
238
239impl fmt::Display for SmimeError {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        match self {
242            SmimeError::Der(e) => write!(f, "DER error: {e}"),
243            SmimeError::UnsupportedAlgorithm(alg) => {
244                write!(f, "unsupported algorithm: {alg}")
245            }
246            SmimeError::NoMatchingRecipient => {
247                write!(f, "no decryption key matches any recipient")
248            }
249            SmimeError::SignatureVerification => write!(f, "signature verification failed"),
250            SmimeError::CertChain(e) => write!(f, "certificate chain error: {e}"),
251            SmimeError::MalformedInput(msg) => write!(f, "malformed CMS input: {msg}"),
252            SmimeError::NoRecipients => write!(f, "encrypt() called with no recipients"),
253            SmimeError::RngFailure(msg) => write!(f, "RNG failure: {msg}"),
254            SmimeError::DecryptionFailed(msg) => write!(f, "decryption failed: {msg}"),
255            SmimeError::Other(msg) => write!(f, "{msg}"),
256            SmimeError::WrongContentType(msg) => write!(f, "wrong content type: {msg}"),
257            SmimeError::BoundaryCollision => write!(
258                f,
259                "could not generate a unique MIME boundary after 8 retries"
260            ),
261            SmimeError::AllSignersFailed(signers) => {
262                write!(f, "{} signer(s) failed: ", signers.len())?;
263                for (i, signer) in signers.iter().enumerate() {
264                    if i > 0 {
265                        write!(f, "; ")?;
266                    }
267                    let reason = signer.error.as_deref().unwrap_or("unknown");
268                    write!(f, "{reason}")?;
269                }
270                Ok(())
271            }
272        }
273    }
274}
275
276impl std::error::Error for SmimeError {
277    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
278        match self {
279            SmimeError::Der(e) => Some(e),
280            _ => None,
281        }
282    }
283}
284
285impl From<der::Error> for SmimeError {
286    fn from(e: der::Error) -> Self {
287        SmimeError::Der(e)
288    }
289}
290
291// ---------------------------------------------------------------------------
292// Serde: manual impl because der::Error has no serde support
293// ---------------------------------------------------------------------------
294
295/// Mirror of `SmimeError` with `String` replacing `der::Error` for serde.
296#[derive(Serialize, Deserialize)]
297#[serde(tag = "type", content = "detail")]
298enum SmimeErrorRepr {
299    Der(String),
300    UnsupportedAlgorithm(String),
301    NoMatchingRecipient,
302    SignatureVerification,
303    CertChain(CertChainError),
304    MalformedInput(String),
305    NoRecipients,
306    RngFailure(String),
307    DecryptionFailed(String),
308    Other(String),
309    AllSignersFailed(Vec<SignerResult>),
310    WrongContentType(String),
311    BoundaryCollision,
312}
313
314impl From<&SmimeError> for SmimeErrorRepr {
315    fn from(e: &SmimeError) -> Self {
316        match e {
317            SmimeError::Der(de) => SmimeErrorRepr::Der(de.to_string()),
318            SmimeError::UnsupportedAlgorithm(s) => SmimeErrorRepr::UnsupportedAlgorithm(s.clone()),
319            SmimeError::NoMatchingRecipient => SmimeErrorRepr::NoMatchingRecipient,
320            SmimeError::SignatureVerification => SmimeErrorRepr::SignatureVerification,
321            SmimeError::CertChain(c) => SmimeErrorRepr::CertChain(c.clone()),
322            SmimeError::MalformedInput(s) => SmimeErrorRepr::MalformedInput(s.clone()),
323            SmimeError::NoRecipients => SmimeErrorRepr::NoRecipients,
324            SmimeError::RngFailure(s) => SmimeErrorRepr::RngFailure(s.clone()),
325            SmimeError::DecryptionFailed(s) => SmimeErrorRepr::DecryptionFailed(s.clone()),
326            SmimeError::Other(s) => SmimeErrorRepr::Other(s.clone()),
327            SmimeError::AllSignersFailed(v) => SmimeErrorRepr::AllSignersFailed(v.clone()),
328            SmimeError::WrongContentType(s) => SmimeErrorRepr::WrongContentType(s.clone()),
329            SmimeError::BoundaryCollision => SmimeErrorRepr::BoundaryCollision,
330        }
331    }
332}
333
334impl From<SmimeErrorRepr> for SmimeError {
335    fn from(r: SmimeErrorRepr) -> Self {
336        match r {
337            // der::Error cannot be reconstructed from a string; use a generic
338            // truncation error as the carrier and preserve the message in Other
339            // if it doesn't match. In practice, deserialized Der variants are
340            // used for logging/display, not for programmatic matching on the
341            // inner der::Error kind.
342            SmimeErrorRepr::Der(s) => SmimeError::Other(format!("DER error: {s}")),
343            SmimeErrorRepr::UnsupportedAlgorithm(s) => SmimeError::UnsupportedAlgorithm(s),
344            SmimeErrorRepr::NoMatchingRecipient => SmimeError::NoMatchingRecipient,
345            SmimeErrorRepr::SignatureVerification => SmimeError::SignatureVerification,
346            SmimeErrorRepr::CertChain(c) => SmimeError::CertChain(c),
347            SmimeErrorRepr::MalformedInput(s) => SmimeError::MalformedInput(s),
348            SmimeErrorRepr::NoRecipients => SmimeError::NoRecipients,
349            SmimeErrorRepr::RngFailure(s) => SmimeError::RngFailure(s),
350            SmimeErrorRepr::DecryptionFailed(s) => SmimeError::DecryptionFailed(s),
351            SmimeErrorRepr::Other(s) => SmimeError::Other(s),
352            SmimeErrorRepr::AllSignersFailed(v) => SmimeError::AllSignersFailed(v),
353            SmimeErrorRepr::WrongContentType(s) => SmimeError::WrongContentType(s),
354            SmimeErrorRepr::BoundaryCollision => SmimeError::BoundaryCollision,
355        }
356    }
357}
358
359impl Serialize for SmimeError {
360    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
361        SmimeErrorRepr::from(self).serialize(serializer)
362    }
363}
364
365impl<'de> Deserialize<'de> for SmimeError {
366    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
367        SmimeErrorRepr::deserialize(deserializer).map(SmimeError::from)
368    }
369}