Skip to main content

ans_verify/
error.rs

1//! Error types for ANS verification.
2
3use ans_types::{BadgeStatus, CryptoError, ParseError};
4use thiserror::Error;
5
6/// Top-level error type for ANS operations.
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum AnsError {
10    /// DNS lookup or parsing error
11    #[error("DNS error: {0}")]
12    Dns(#[from] DnsError),
13
14    /// Transparency log API error
15    #[error("Transparency log error: {0}")]
16    TransparencyLog(#[from] TlogError),
17
18    /// Certificate parsing or cryptographic error
19    #[error("Certificate error: {0}")]
20    Certificate(#[from] CryptoError),
21
22    /// Verification logic error
23    #[error("Verification error: {0}")]
24    Verification(#[from] VerificationError),
25
26    /// Parse error for types
27    #[error("Parse error: {0}")]
28    Parse(#[from] ParseError),
29}
30
31/// Result type alias using `AnsError`.
32pub type AnsResult<T> = Result<T, AnsError>;
33
34/// DNS-specific errors.
35#[derive(Debug, Error, Clone)]
36#[non_exhaustive]
37pub enum DnsError {
38    /// Record does not exist (NXDOMAIN)
39    #[error("DNS record not found (NXDOMAIN) for {fqdn}")]
40    NotFound {
41        /// The FQDN that was queried.
42        fqdn: String,
43    },
44
45    /// DNS lookup failed (SERVFAIL, timeout, etc.)
46    #[error("DNS lookup failed for {fqdn}: {reason}")]
47    LookupFailed {
48        /// The FQDN that was queried.
49        fqdn: String,
50        /// The reason the lookup failed.
51        reason: String,
52    },
53
54    /// DNS query timed out
55    #[error("DNS query timed out for {fqdn}")]
56    Timeout {
57        /// The FQDN that timed out.
58        fqdn: String,
59    },
60
61    /// DNSSEC validation failed
62    #[error("DNSSEC validation failed for {fqdn}")]
63    DnssecFailed {
64        /// The FQDN that failed DNSSEC validation.
65        fqdn: String,
66    },
67
68    /// Invalid TXT record format
69    #[error("Invalid badge TXT record format: {record}")]
70    InvalidFormat {
71        /// The malformed DNS record content.
72        record: String,
73    },
74
75    /// Resolver configuration error
76    #[error("DNS resolver error: {0}")]
77    ResolverError(String),
78}
79
80/// HTTP transport error wrapper.
81///
82/// Wraps the underlying HTTP client error to avoid exposing third-party
83/// types in the public API.
84#[derive(Debug)]
85pub struct HttpError {
86    inner: reqwest::Error,
87}
88
89impl std::fmt::Display for HttpError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        self.inner.fmt(f)
92    }
93}
94
95impl std::error::Error for HttpError {
96    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
97        self.inner.source()
98    }
99}
100
101impl From<reqwest::Error> for HttpError {
102    fn from(err: reqwest::Error) -> Self {
103        Self { inner: err }
104    }
105}
106
107/// Transparency log API errors.
108///
109/// These errors map to HTTP responses from the TL API:
110/// - 404 → `NotFound`
111/// - 5xx → `ServiceUnavailable`
112/// - Parse failures → `InvalidResponse`
113/// - Network/HTTP errors → `HttpError`
114#[derive(Debug, Error)]
115#[non_exhaustive]
116pub enum TlogError {
117    /// HTTP request failed (network error, timeout, etc.)
118    #[error("HTTP request failed: {0}")]
119    HttpError(#[from] HttpError),
120
121    /// Badge not found (HTTP 404)
122    #[error("Badge not found at {url}")]
123    NotFound {
124        /// The URL that returned 404.
125        url: String,
126    },
127
128    /// Invalid or unparseable badge response
129    #[error("Invalid badge response: {0}")]
130    InvalidResponse(String),
131
132    /// Service unavailable (HTTP 5xx)
133    #[error("Transparency log service unavailable")]
134    ServiceUnavailable,
135
136    /// Invalid URL construction
137    #[error("Invalid URL: {0}")]
138    InvalidUrl(String),
139
140    /// Invalid HTTP header name or value
141    #[error("Invalid header: {0}")]
142    InvalidHeader(String),
143
144    /// Badge URL domain is not in the trusted RA domains list
145    #[error("Untrusted badge domain: {domain}")]
146    UntrustedDomain {
147        /// The domain that was not trusted.
148        domain: String,
149        /// The list of trusted domains.
150        trusted: Vec<String>,
151    },
152}
153
154/// Verification logic errors.
155#[derive(Debug, Error, Clone)]
156#[non_exhaustive]
157pub enum VerificationError {
158    /// Badge status is not valid for connections
159    #[error("Badge status {status:?} is not valid for connections")]
160    InvalidStatus {
161        /// The badge status that was rejected.
162        status: BadgeStatus,
163    },
164
165    /// Certificate fingerprint does not match badge
166    #[error("Fingerprint mismatch: expected {expected}, got {actual}")]
167    FingerprintMismatch {
168        /// The expected fingerprint from the badge.
169        expected: String,
170        /// The actual fingerprint from the certificate.
171        actual: String,
172    },
173
174    /// Hostname does not match badge
175    #[error("Hostname mismatch: expected {expected}, got {actual}")]
176    HostnameMismatch {
177        /// The expected hostname from the badge.
178        expected: String,
179        /// The actual hostname from the certificate.
180        actual: String,
181    },
182
183    /// ANS name does not match badge
184    #[error("ANS name mismatch: expected {expected}, got {actual}")]
185    AnsNameMismatch {
186        /// The expected ANS name from the badge.
187        expected: String,
188        /// The actual ANS name from the certificate.
189        actual: String,
190    },
191
192    /// No matching badge found for the presented certificate version
193    #[error("No matching badge found for version {version}")]
194    NoMatchingBadge {
195        /// The version that had no matching badge.
196        version: String,
197    },
198
199    /// Certificate does not chain to trusted CA
200    #[error("Certificate does not chain to trusted CA")]
201    UntrustedCertificate,
202
203    /// DANE/TLSA verification failed
204    #[error("DANE verification failed: {0}")]
205    DaneVerificationFailed(DaneError),
206
207    /// Multiple errors occurred
208    #[error("Multiple verification errors: {errors:?}")]
209    Multiple {
210        /// The collected verification errors.
211        errors: Vec<Self>,
212    },
213
214    /// Builder or verifier configuration error
215    #[error("Configuration error: {0}")]
216    Configuration(String),
217}
218
219/// DANE/TLSA verification errors.
220#[derive(Debug, Error, Clone)]
221#[non_exhaustive]
222pub enum DaneError {
223    /// No TLSA records found when required
224    #[error("No TLSA records found for {fqdn}:{port}")]
225    NoTlsaRecords {
226        /// The FQDN that was queried.
227        fqdn: String,
228        /// The port that was queried.
229        port: u16,
230    },
231
232    /// TLSA record fingerprint does not match certificate
233    #[error("TLSA fingerprint mismatch: certificate not bound to DNS")]
234    FingerprintMismatch,
235
236    /// DNSSEC validation failed
237    #[error("DNSSEC validation failed for {fqdn}")]
238    DnssecValidationFailed {
239        /// The FQDN that failed DNSSEC validation.
240        fqdn: String,
241    },
242
243    /// DNSSEC required but not present
244    #[error("DNSSEC required but not present for {fqdn}")]
245    DnssecNotPresent {
246        /// The FQDN missing DNSSEC.
247        fqdn: String,
248    },
249
250    /// Invalid TLSA record format
251    #[error("Invalid TLSA record: {reason}")]
252    InvalidRecord {
253        /// The reason the record is invalid.
254        reason: String,
255    },
256
257    /// DNS lookup error during TLSA query
258    #[error("DNS error during TLSA lookup: {0}")]
259    DnsError(#[from] DnsError),
260}