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}