Skip to main content

leyline/tls/
error.rs

1use std::io;
2
3#[derive(Debug, thiserror::Error)]
4#[non_exhaustive]
5pub enum TlsError {
6    #[error("ssl config: {0}")]
7    SslConfig(String),
8
9    #[error("tls profile: {0}")]
10    Profile(String),
11
12    #[error("trust store: {0}")]
13    TrustStore(String),
14
15    #[error("tls handshake: {0}")]
16    Handshake(String),
17
18    #[error("tls handshake closed or reset by peer")]
19    Rejected(#[source] io::Error),
20
21    #[error("tls handshake io")]
22    HandshakeIo(#[source] io::Error),
23
24    #[error("certificate verification failed: {detail}")]
25    Certificate {
26        verify_code: Option<i32>,
27        reason: Option<&'static str>,
28        detail: String,
29    },
30
31    #[error("hostname: {0}")]
32    Hostname(String),
33
34    #[error("certificate pin: {0}")]
35    Pinning(String),
36
37    #[error("tcp connect")]
38    TcpConnect(#[source] io::Error),
39
40    #[error("dns resolution")]
41    Dns(#[source] io::Error),
42
43    #[error("proxy: {detail}")]
44    Proxy {
45        status: Option<u16>,
46        detail: String,
47        #[source]
48        source: Option<io::Error>,
49    },
50}
51
52fn peer_closed(kind: io::ErrorKind) -> bool {
53    matches!(
54        kind,
55        io::ErrorKind::UnexpectedEof
56            | io::ErrorKind::ConnectionReset
57            | io::ErrorKind::ConnectionAborted
58            | io::ErrorKind::BrokenPipe
59    )
60}
61
62fn transport_eof(msg: &str) -> bool {
63    let lower = msg.to_ascii_lowercase();
64    lower.contains("unexpected eof")
65        || lower.contains("connection reset")
66        || lower.contains("broken pipe")
67        || lower.contains("connection aborted")
68}
69
70impl TlsError {
71    pub(crate) fn from_handshake<S>(e: &leyline_bssl_tokio::HandshakeError<S>) -> Self {
72        if let Some(io) = e.as_io_error() {
73            let copy = io::Error::new(io.kind(), io.to_string());
74            if peer_closed(io.kind()) {
75                return Self::Rejected(copy);
76            }
77            return Self::HandshakeIo(copy);
78        }
79        let msg = e.to_string();
80        if transport_eof(&msg) {
81            Self::Rejected(io::Error::new(io::ErrorKind::UnexpectedEof, msg))
82        } else {
83            Self::Handshake(msg)
84        }
85    }
86
87    pub(crate) fn from_stack(e: leyline_bssl::error::ErrorStack) -> Self {
88        Self::SslConfig(e.to_string())
89    }
90
91    pub(crate) fn proxy(detail: impl Into<String>) -> Self {
92        Self::Proxy {
93            status: None,
94            detail: detail.into(),
95            source: None,
96        }
97    }
98
99    pub(crate) fn proxy_io(source: io::Error) -> Self {
100        Self::Proxy {
101            status: None,
102            detail: "proxy connection failed".into(),
103            source: Some(source),
104        }
105    }
106
107    pub(crate) fn into_proxy(self) -> Self {
108        match self {
109            Self::Proxy { .. } => self,
110            Self::Dns(e) | Self::TcpConnect(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
111                Self::proxy_io(e)
112            }
113            other => Self::proxy(other.to_string()),
114        }
115    }
116
117    pub(crate) fn io_source(&self) -> Option<&io::Error> {
118        match self {
119            Self::TcpConnect(e) | Self::Dns(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
120                Some(e)
121            }
122            Self::Proxy { source, .. } => source.as_ref(),
123            _ => None,
124        }
125    }
126
127    pub(crate) fn duplicate(&self) -> Self {
128        let io = |e: &io::Error| io::Error::new(e.kind(), e.to_string());
129        match self {
130            Self::SslConfig(m) => Self::SslConfig(m.clone()),
131            Self::Profile(m) => Self::Profile(m.clone()),
132            Self::TrustStore(m) => Self::TrustStore(m.clone()),
133            Self::Handshake(m) => Self::Handshake(m.clone()),
134            Self::Rejected(e) => Self::Rejected(io(e)),
135            Self::HandshakeIo(e) => Self::HandshakeIo(io(e)),
136            Self::Certificate {
137                verify_code,
138                reason,
139                detail,
140            } => Self::Certificate {
141                verify_code: *verify_code,
142                reason: *reason,
143                detail: detail.clone(),
144            },
145            Self::Hostname(m) => Self::Hostname(m.clone()),
146            Self::Pinning(m) => Self::Pinning(m.clone()),
147            Self::TcpConnect(e) => Self::TcpConnect(io(e)),
148            Self::Dns(e) => Self::Dns(io(e)),
149            Self::Proxy {
150                status,
151                detail,
152                source,
153            } => Self::Proxy {
154                status: *status,
155                detail: detail.clone(),
156                source: source.as_ref().map(io),
157            },
158        }
159    }
160}