]>
Commit | Line | Data |
---|---|---|
83b053a0 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 The Squid Software Foundation and contributors |
83b053a0 CT |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
9 | /* DEBUG: section 83 TLS I/O */ | |
10 | ||
11 | #include "squid.h" | |
12 | #include "fde.h" | |
13 | #include "security/Io.h" | |
14 | ||
15 | namespace Security { | |
16 | ||
17 | template <typename Fun> | |
18 | static IoResult Handshake(Comm::Connection &, ErrorCode, Fun); | |
19 | static void PrepForIo(); | |
20 | ||
21 | typedef SessionPointer::element_type *ConnectionPointer; | |
22 | ||
23 | } // namespace Security | |
24 | ||
800967af CT |
25 | void |
26 | Security::IoResult::print(std::ostream &os) const | |
27 | { | |
28 | const char *strCat = "unknown"; | |
29 | switch (category) { | |
30 | case ioSuccess: | |
31 | strCat = "success"; | |
32 | break; | |
33 | case ioWantRead: | |
34 | strCat = "want-read"; | |
35 | break; | |
36 | case ioWantWrite: | |
37 | strCat = "want-write"; | |
38 | break; | |
39 | case ioError: | |
40 | strCat = "error"; | |
41 | break; | |
42 | } | |
43 | os << strCat; | |
44 | ||
45 | if (errorDescription) | |
46 | os << ", " << errorDescription; | |
47 | ||
48 | if (important) | |
49 | os << ", important"; | |
50 | } | |
51 | ||
83b053a0 CT |
52 | // TODO: Replace high-level ERR_get_error() calls with a new std::ostream |
53 | // ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call. | |
54 | void | |
55 | Security::ForgetErrors() | |
56 | { | |
57 | #if USE_OPENSSL | |
58 | unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary | |
59 | while (const auto errorToForget = ERR_get_error()) | |
60 | debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget)); | |
61 | #endif | |
62 | } | |
63 | ||
64 | /// the steps necessary to perform before the upcoming TLS I/O | |
65 | /// to correctly interpret/detail the outcome of that I/O | |
66 | static void | |
67 | Security::PrepForIo() | |
68 | { | |
69 | // flush earlier errors that some call forgot to extract, so that we will | |
70 | // only get the error(s) specific to the upcoming I/O operation | |
71 | ForgetErrors(); | |
72 | ||
73 | // as the last step, reset errno to know when the I/O operation set it | |
74 | errno = 0; | |
75 | } | |
76 | ||
77 | /// Calls the given TLS handshake function and analysis its outcome. | |
78 | /// Handles alert logging and being called without adequate TLS library support. | |
79 | template <typename Fun> | |
80 | static Security::IoResult | |
81 | Security::Handshake(Comm::Connection &transport, const ErrorCode topError, Fun ioCall) | |
82 | { | |
83 | assert(transport.isOpen()); | |
84 | const auto fd = transport.fd; | |
85 | auto connection = fd_table[fd].ssl.get(); | |
86 | ||
87 | PrepForIo(); | |
88 | const auto callResult = ioCall(connection); | |
89 | const auto xerrno = errno; | |
90 | ||
91 | debugs(83, 5, callResult << '/' << xerrno << " for TLS connection " << | |
92 | static_cast<void*>(connection) << " over " << transport); | |
93 | ||
94 | #if USE_OPENSSL | |
95 | if (callResult > 0) | |
96 | return IoResult(IoResult::ioSuccess); | |
97 | ||
98 | const auto ioError = SSL_get_error(connection, callResult); | |
99 | ||
100 | // quickly handle common, non-erroneous outcomes | |
101 | switch (ioError) { | |
102 | ||
103 | case SSL_ERROR_WANT_READ: | |
104 | return IoResult(IoResult::ioWantRead); | |
105 | ||
106 | case SSL_ERROR_WANT_WRITE: | |
107 | return IoResult(IoResult::ioWantWrite); | |
108 | ||
109 | default: | |
110 | ; // fall through to handle the problem | |
111 | } | |
112 | ||
113 | // now we know that we are dealing with a real problem; detail it | |
d2f0c106 CT |
114 | ErrorDetail::Pointer errorDetail; |
115 | if (const auto oldDetail = SSL_get_ex_data(connection, ssl_ex_index_ssl_error_detail)) { | |
116 | errorDetail = *static_cast<ErrorDetail::Pointer*>(oldDetail); | |
117 | } else { | |
118 | errorDetail = new ErrorDetail(topError, ioError, xerrno); | |
119 | if (const auto serverCert = SSL_get_peer_certificate(connection)) | |
120 | errorDetail->setPeerCertificate(CertPointer(serverCert)); | |
121 | } | |
83b053a0 CT |
122 | IoResult ioResult(errorDetail); |
123 | ||
124 | // collect debugging-related details | |
125 | switch (ioError) { | |
126 | case SSL_ERROR_SYSCALL: | |
127 | if (callResult == 0) { | |
128 | ioResult.errorDescription = "peer aborted"; | |
129 | } else { | |
130 | ioResult.errorDescription = "system call failure"; | |
131 | ioResult.important = (xerrno == ECONNRESET); | |
132 | } | |
133 | break; | |
134 | ||
135 | case SSL_ERROR_ZERO_RETURN: | |
136 | // peer sent a "close notify" alert, closing TLS connection for writing | |
137 | ioResult.errorDescription = "peer closed"; | |
138 | ioResult.important = true; | |
139 | break; | |
140 | ||
141 | default: | |
142 | // an ever-increasing number of possible cases but usually SSL_ERROR_SSL | |
143 | ioResult.errorDescription = "failure"; | |
144 | ioResult.important = true; | |
145 | } | |
146 | ||
147 | return ioResult; | |
148 | ||
149 | #elif USE_GNUTLS | |
150 | if (callResult == GNUTLS_E_SUCCESS) { | |
151 | // TODO: Avoid gnutls_*() calls if debugging is off. | |
152 | const auto desc = gnutls_session_get_desc(connection); | |
153 | debugs(83, 2, "TLS session info: " << desc); | |
154 | gnutls_free(desc); | |
155 | return IoResult(IoResult::ioSuccess); | |
156 | } | |
157 | ||
158 | // Debug the TLS connection state so far. | |
159 | // TODO: Avoid gnutls_*() calls if debugging is off. | |
160 | const auto descIn = gnutls_handshake_get_last_in(connection); | |
161 | debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn)); | |
162 | const auto descOut = gnutls_handshake_get_last_out(connection); | |
163 | debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut)); | |
164 | ||
165 | if (callResult == GNUTLS_E_WARNING_ALERT_RECEIVED) { | |
166 | const auto alert = gnutls_alert_get(connection); | |
167 | debugs(83, DBG_IMPORTANT, "WARNING: TLS alert: " << gnutls_alert_get_name(alert)); | |
168 | // fall through to retry | |
169 | } | |
170 | ||
171 | if (!gnutls_error_is_fatal(callResult)) { | |
172 | const auto reading = gnutls_record_get_direction(connection) == 0; | |
173 | return IoResult(reading ? IoResult::ioWantRead : IoResult::ioWantWrite); | |
174 | } | |
175 | ||
176 | // now we know that we are dealing with a real problem; detail it | |
177 | const ErrorDetail::Pointer errorDetail = | |
178 | new ErrorDetail(topError, callResult, xerrno); | |
179 | ||
180 | IoResult ioResult(errorDetail); | |
181 | ioResult.errorDescription = "failure"; | |
182 | return ioResult; | |
183 | ||
184 | #else | |
8b082ed9 | 185 | (void)topError; |
83b053a0 | 186 | // TLS I/O code path should never be reachable without a TLS/SSL library. |
d816f28d | 187 | debugs(1, DBG_CRITICAL, ForceAlert << "ERROR: Squid BUG: " << |
83b053a0 CT |
188 | "Unexpected TLS I/O in Squid built without a TLS/SSL library"); |
189 | assert(false); // we want a stack trace which fatal() does not produce | |
190 | return IoResult(nullptr); // not reachable | |
191 | #endif | |
192 | } | |
193 | ||
194 | // TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can | |
195 | // be simplified further by using SSL_do_handshake() and eliminating lambdas. | |
196 | Security::IoResult | |
197 | Security::Accept(Comm::Connection &transport) | |
198 | { | |
199 | return Handshake(transport, SQUID_TLS_ERR_ACCEPT, [] (ConnectionPointer tlsConn) { | |
200 | #if USE_OPENSSL | |
201 | return SSL_accept(tlsConn); | |
202 | #elif USE_GNUTLS | |
203 | return gnutls_handshake(tlsConn); | |
204 | #else | |
205 | return sizeof(tlsConn); // the value is unused; should be unreachable | |
206 | #endif | |
207 | }); | |
208 | } | |
209 | ||
210 | /// establish a TLS connection over the specified from-Squid transport connection | |
211 | Security::IoResult | |
212 | Security::Connect(Comm::Connection &transport) | |
213 | { | |
214 | return Handshake(transport, SQUID_TLS_ERR_CONNECT, [] (ConnectionPointer tlsConn) { | |
215 | #if USE_OPENSSL | |
216 | return SSL_connect(tlsConn); | |
217 | #elif USE_GNUTLS | |
218 | return gnutls_handshake(tlsConn); | |
219 | #else | |
220 | return sizeof(tlsConn); // the value is unused; should be unreachable | |
221 | #endif | |
222 | }); | |
223 | } | |
224 |