/src/wget2/libwget/ssl_gnutls.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2012-2015 Tim Ruehsen |
3 | | * Copyright (c) 2015-2024 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of libwget. |
6 | | * |
7 | | * Libwget is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation, either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * Libwget is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
19 | | * |
20 | | * |
21 | | * gnutls SSL/TLS routines |
22 | | * - some parts have been copied from GnuTLS example code |
23 | | * - OCSP code has been copied from gnutls-cli/ocsptool code |
24 | | * |
25 | | * Changelog |
26 | | * 03.08.2012 Tim Ruehsen created inspired from gnutls client example |
27 | | * 26.08.2012 wget compatibility regarding config options |
28 | | * 15.01.2015 added OCSP fix from https://gitorious.org/gnutls/gnutls/commit/11eebe14b232ec198d1446a3720e6ed78d118c4b |
29 | | * |
30 | | * Resources: |
31 | | * RFC6066 Transport Layer Security (TLS) Extensions: Extension Definitions (defines OCSP stapling) |
32 | | * RFC6960 Online Certificate Status Protocol - OCSP |
33 | | * RFC6961 TLS Multiple Certificate Status Request Extension |
34 | | * |
35 | | * Testing revocation: |
36 | | * https://revoked.grc.com/ |
37 | | * https://test-sspev.verisign.com:2443/test-SSPEV-revoked-verisign.html |
38 | | * |
39 | | */ |
40 | | |
41 | | #include <config.h> |
42 | | |
43 | | #include <unistd.h> |
44 | | #include <stdio.h> |
45 | | #include <string.h> |
46 | | #include <dirent.h> |
47 | | #include <sys/stat.h> |
48 | | #include <errno.h> |
49 | | |
50 | | #include <gnutls/gnutls.h> |
51 | | #include <gnutls/x509.h> |
52 | | #ifdef WITH_OCSP |
53 | | # include <gnutls/ocsp.h> |
54 | | #endif |
55 | | #ifdef WITH_LIBDANE |
56 | | # include <gnutls/dane.h> |
57 | | #endif |
58 | | #include <gnutls/crypto.h> |
59 | | #include <gnutls/abstract.h> |
60 | | |
61 | | #include <wget.h> |
62 | | #include "private.h" |
63 | | #include "net.h" |
64 | | |
65 | | /** |
66 | | * \file |
67 | | * \brief Functions for establishing and managing SSL/TLS connections |
68 | | * \defgroup libwget-ssl SSL/TLS engine |
69 | | * |
70 | | * @{ |
71 | | */ |
72 | | |
73 | | static wget_tls_stats_callback |
74 | | *tls_stats_callback; |
75 | | static void |
76 | | *tls_stats_ctx; |
77 | | |
78 | | static wget_ocsp_stats_callback |
79 | | *ocsp_stats_callback; |
80 | | static void |
81 | | *ocsp_stats_ctx; |
82 | | |
83 | | static struct config { |
84 | | const char |
85 | | *secure_protocol, |
86 | | *ca_directory, |
87 | | *ca_file, |
88 | | *cert_file, |
89 | | *key_file, |
90 | | *crl_file, |
91 | | *ocsp_server, |
92 | | *alpn; |
93 | | wget_ocsp_db |
94 | | *ocsp_cert_cache, |
95 | | *ocsp_host_cache; |
96 | | wget_tls_session_db |
97 | | *tls_session_cache; |
98 | | wget_hpkp_db |
99 | | *hpkp_cache; |
100 | | char |
101 | | ca_type, |
102 | | cert_type, |
103 | | key_type; |
104 | | bool |
105 | | check_certificate : 1, |
106 | | report_invalid_cert : 1, |
107 | | check_hostname : 1, |
108 | | print_info : 1, |
109 | | ocsp : 1, |
110 | | ocsp_date : 1, |
111 | | ocsp_stapling : 1, |
112 | | ocsp_nonce : 1, |
113 | | dane : 1; |
114 | | } config = { |
115 | | .check_certificate = 1, |
116 | | .report_invalid_cert = 1, |
117 | | .check_hostname = 1, |
118 | | #ifdef WITH_OCSP |
119 | | .ocsp = 0, |
120 | | .ocsp_stapling = 1, |
121 | | #endif |
122 | | .ca_type = WGET_SSL_X509_FMT_PEM, |
123 | | .cert_type = WGET_SSL_X509_FMT_PEM, |
124 | | .key_type = WGET_SSL_X509_FMT_PEM, |
125 | | .secure_protocol = "AUTO", |
126 | | .ca_directory = "system", |
127 | | .ca_file = "system", |
128 | | #ifdef WITH_LIBNGHTTP2 |
129 | | .alpn = "h2,http/1.1", |
130 | | #endif |
131 | | }; |
132 | | |
133 | | struct session_context { |
134 | | const char * |
135 | | hostname; |
136 | | wget_hpkp_stats_result |
137 | | stats_hpkp; |
138 | | uint16_t |
139 | | port; |
140 | | bool |
141 | | ocsp_stapling : 1, |
142 | | valid : 1, |
143 | | delayed_session_data : 1; |
144 | | }; |
145 | | |
146 | | static gnutls_certificate_credentials_t |
147 | | credentials; |
148 | | static gnutls_priority_t |
149 | | priority_cache; |
150 | | |
151 | 0 | #define error_printf_check(...) if (config.report_invalid_cert) wget_error_printf(__VA_ARGS__) |
152 | | |
153 | | /** |
154 | | * \param[in] key An identifier for the config parameter (starting with `WGET_SSL_`) to set |
155 | | * \param[in] value The value for the config parameter (a NULL-terminated string) |
156 | | * |
157 | | * Set a configuration parameter, as a string. |
158 | | * |
159 | | * The following parameters accept a string as their value (\p key can have any of those values): |
160 | | * |
161 | | * - WGET_SSL_SECURE_PROTOCOL: A string describing which SSL/TLS version should be used. It can have either |
162 | | * an arbitrary value, or one of the following fixed values (case does not matter): |
163 | | * - "SSL": SSLv3 will be used. Warning: this protocol is insecure and should be avoided. |
164 | | * - "TLSv1": TLS 1.0 will be used. |
165 | | * - "TLSv1_1": TLS 1.1 will be used. |
166 | | * - "TLSv1_2": TLS 1.2 will be used. |
167 | | * - "TLSv1_3": TLS 1.3 will be used. |
168 | | * - "AUTO": Let the TLS library decide. |
169 | | * - "PFS": Let the TLS library decide, but make sure only forward-secret ciphers are used. |
170 | | * |
171 | | * An arbitrary string can also be supplied (an string that's different from any of the previous ones). If that's the case |
172 | | * the string will be directly taken as the priority string and sent to the library. Priority strings provide the greatest flexibility, |
173 | | * but have a library-specific syntax. A GnuTLS priority string will not work if your libwget has been compiled with OpenSSL, for instance. |
174 | | * - WGET_SSL_CA_DIRECTORY: A path to the directory where the root certificates will be taken from |
175 | | * for server cert validation. Every file of that directory is expected to contain an X.509 certificate, |
176 | | * encoded in PEM format. If the string "system" is specified, the system's default directory will be used. |
177 | | * The default value is "system". Certificates get loaded in wget_ssl_init(). |
178 | | * - WGET_SSL_CA_FILE: A path to a file containing a single root certificate. This will be used to validate |
179 | | * the server's certificate chain. This option can be used together with `WGET_SSL_CA_DIRECTORY`. The certificate |
180 | | * can be in either PEM or DER format. The format is specified in the `WGET_SSL_CA_TYPE` option (see |
181 | | * wget_ssl_set_config_int()). |
182 | | * - WGET_SSL_CERT_FILE: Set the client certificate. It will be used for client authentication if the server requests it. |
183 | | * It can be in either PEM or DER format. The format is specified in the `WGET_SSL_CERT_TYPE` option (see |
184 | | * wget_ssl_set_config_int()). The `WGET_SSL_KEY_FILE` option specifies the private key corresponding to the cert's |
185 | | * public key. If `WGET_SSL_KEY_FILE` is not set, then the private key is expected to be in the same file as the certificate. |
186 | | * - WGET_SSL_KEY_FILE: Set the private key corresponding to the client certificate specified in `WGET_SSL_CERT_FILE`. |
187 | | * It can be in either PEM or DER format. The format is specified in the `WGET_SSL_KEY_TYPE` option (see |
188 | | * wget_ssl_set_config_int()). IF `WGET_SSL_CERT_FILE` is not set, then the certificate is expected to be in the same file |
189 | | * as the private key. |
190 | | * - WGET_SSL_CRL_FILE: Sets a CRL (Certificate Revocation List) file which will be used to verify client and server certificates. |
191 | | * A CRL file is a black list that contains the serial numbers of the certificates that should not be treated as valid. Whenever |
192 | | * a client or a server presents a certificate in the TLS handshake whose serial number is contained in the CRL, the handshake |
193 | | * will be immediately aborted. The CRL file must be in PEM format. |
194 | | * - WGET_SSL_OCSP_SERVER: Set the URL of the OCSP server that will be used to validate certificates. |
195 | | * OCSP is a protocol by which a server is queried to tell whether a given certificate is valid or not. It's an approach contrary |
196 | | * to that used by CRLs. While CRLs are black lists, OCSP takes a white list approach where a certificate can be checked for validity. |
197 | | * Whenever a client or server presents a certificate in a TLS handshake, the provided URL will be queried (using OCSP) to check whether |
198 | | * that certificate is valid or not. If the server responds the certificate is not valid, the handshake will be immediately aborted. |
199 | | * - WGET_SSL_ALPN: Sets the ALPN string to be sent to the remote host. ALPN is a TLS extension |
200 | | * ([RFC 7301](https://tools.ietf.org/html/rfc7301)) |
201 | | * that allows both the server and the client to signal which application-layer protocols they support (HTTP/2, QUIC, etc.). |
202 | | * That information can then be used for the server to ultimately decide which protocol will be used on top of TLS. |
203 | | * |
204 | | * An invalid value for \p key will not harm the operation of TLS, but will cause |
205 | | * a complain message to be printed to the error log stream. |
206 | | */ |
207 | | void wget_ssl_set_config_string(int key, const char *value) |
208 | 69.2k | { |
209 | 69.2k | switch (key) { |
210 | 9.89k | case WGET_SSL_SECURE_PROTOCOL: config.secure_protocol = value; break; |
211 | 9.89k | case WGET_SSL_CA_DIRECTORY: config.ca_directory = value; break; |
212 | 9.89k | case WGET_SSL_CA_FILE: config.ca_file = value; break; |
213 | 9.89k | case WGET_SSL_CERT_FILE: config.cert_file = value; break; |
214 | 9.89k | case WGET_SSL_KEY_FILE: config.key_file = value; break; |
215 | 9.89k | case WGET_SSL_CRL_FILE: config.crl_file = value; break; |
216 | 9.89k | case WGET_SSL_OCSP_SERVER: config.ocsp_server = value; break; |
217 | 0 | case WGET_SSL_ALPN: config.alpn = value; break; |
218 | 0 | default: error_printf(_("Unknown config key %d (or value must not be a string)\n"), key); |
219 | 69.2k | } |
220 | 69.2k | } |
221 | | |
222 | | /** |
223 | | * \param[in] key An identifier for the config parameter (starting with `WGET_SSL_`) to set |
224 | | * \param[in] value The value for the config parameter (a pointer) |
225 | | * |
226 | | * Set a configuration parameter, as a libwget object. |
227 | | * |
228 | | * The following parameters expect an already initialized libwget object as their value. |
229 | | * |
230 | | * - WGET_SSL_OCSP_CACHE: This option takes a pointer to a \ref wget_ocsp_db |
231 | | * structure as an argument. Such a pointer is returned when initializing the OCSP cache with wget_ocsp_db_init(). |
232 | | * The cache is used to store OCSP responses locally and avoid querying the OCSP server repeatedly for the same certificate. |
233 | | * - WGET_SSL_SESSION_CACHE: This option takes a pointer to a \ref wget_tls_session_db structure. |
234 | | * Such a pointer is returned when initializing the TLS session cache with wget_tls_session_db_init(). |
235 | | * This option thus sets the handle to the TLS session cache that will be used to store TLS sessions. |
236 | | * The TLS session cache is used to support TLS session resumption. It stores the TLS session parameters derived from a previous TLS handshake |
237 | | * (most importantly the session identifier and the master secret) so that there's no need to run the handshake again |
238 | | * the next time we connect to the same host. This is useful as the handshake is an expensive process. |
239 | | * - WGET_SSL_HPKP_CACHE: Set the HPKP cache to be used to verify known HPKP pinned hosts. This option takes a pointer |
240 | | * to a \ref wget_hpkp_db structure. Such a pointer is returned when initializing the HPKP cache |
241 | | * with wget_hpkp_db_init(). HPKP is a HTTP-level protocol that allows the server to "pin" its present and future X.509 |
242 | | * certificate fingerprints, to support rapid certificate change in the event that the higher level root CA |
243 | | * gets compromised ([RFC 7469](https://tools.ietf.org/html/rfc7469)). |
244 | | */ |
245 | | |
246 | | void wget_ssl_set_config_object(int key, void *value) |
247 | 29.6k | { |
248 | 29.6k | switch (key) { |
249 | 9.89k | case WGET_SSL_OCSP_CACHE: config.ocsp_cert_cache = (wget_ocsp_db *)value; break; |
250 | 9.89k | case WGET_SSL_SESSION_CACHE: config.tls_session_cache = (wget_tls_session_db *)value; break; |
251 | 9.89k | case WGET_SSL_HPKP_CACHE: config.hpkp_cache = (wget_hpkp_db *)value; break; |
252 | 0 | default: error_printf(_("Unknown config key %d (or value must not be an object)\n"), key); |
253 | 29.6k | } |
254 | 29.6k | } |
255 | | |
256 | | /** |
257 | | * \param[in] key An identifier for the config parameter (starting with `WGET_SSL_`) |
258 | | * \param[in] value The value for the config parameter |
259 | | * |
260 | | * Set a configuration parameter, as an integer. |
261 | | * |
262 | | * These are the parameters that can be set (\p key can have any of these values): |
263 | | * |
264 | | * - WGET_SSL_CHECK_CERTIFICATE: whether certificates should be verified (1) or not (0) |
265 | | * - WGET_SSL_REPORT_INVALID_CERT: whether to print (1) errors/warnings regarding certificate verification or not (0) |
266 | | * - WGET_SSL_CHECK_HOSTNAME: whether or not to check if the certificate's subject field |
267 | | * matches the peer's hostname. This check is done according to the rules in [RFC 6125](https://tools.ietf.org/html/rfc6125) |
268 | | * and typically involves checking whether the hostname and the common name (CN) field of the subject match. |
269 | | * - WGET_SSL_PRINT_INFO: whether or not information should be printed about the established SSL/TLS handshake (negotiated |
270 | | * ciphersuites, certificates, etc.). The default is no (0). |
271 | | * |
272 | | * The following three options all can take either `WGET_SSL_X509_FMT_PEM` (to specify the PEM format) or `WGET_SSL_X509_FMT_DER` |
273 | | * (for the DER format). The default in for all of them is `WGET_SSL_X509_FMT_PEM`. |
274 | | * |
275 | | * - WGET_SSL_CA_TYPE: Specifies what's the format of the root CA certificate(s) supplied with either `WGET_SSL_CA_DIRECTORY` |
276 | | * or `WGET_SSL_CA_FILE`. |
277 | | * - WGET_SSL_CERT_TYPE: Specifies what's the format of the certificate file supplied with `WGET_SSL_CERT_FILE`. **The certificate |
278 | | * and the private key supplied must both be of the same format.** |
279 | | * - WGET_SSL_KEY_TYPE: Specifies what's the format of the private key file supplied with `WGET_SSL_KEY_FILE`. **The private key |
280 | | * and the certificate supplied must both be of the same format.** |
281 | | * |
282 | | * The following two options control OCSP queries. These don't affect the CRL set with `WGET_SSL_CRL_FILE`, if any. |
283 | | * If both CRLs and OCSP are enabled, both will be used. |
284 | | * |
285 | | * - WGET_SSL_OCSP: whether or not OCSP should be used. The default is yes (1). |
286 | | * - WGET_SSL_OCSP_STAPLING: whether or not OCSP stapling should be used. The default is yes (1). |
287 | | * - WGET_SSL_OCSP_NONCE: whether or not an OCSP nonce should be sent in the request. The default is yes (1). |
288 | | * If a nonce was sent in the request, the OCSP verification will fail if the response nonce doesn't match. |
289 | | * However if the response does not include a nonce extension, verification will be allowed to continue. |
290 | | * The OCSP nonce extension is not a critical one. |
291 | | * - WGET_SSL_OCSP_DATE: Reject the OCSP response if it's older than 3 days. |
292 | | */ |
293 | | void wget_ssl_set_config_int(int key, int value) |
294 | 98.9k | { |
295 | 98.9k | switch (key) { |
296 | 9.89k | case WGET_SSL_CHECK_CERTIFICATE: config.check_certificate = (char)value; break; |
297 | 9.89k | case WGET_SSL_REPORT_INVALID_CERT: config.report_invalid_cert = (char)value; break; |
298 | 9.89k | case WGET_SSL_CHECK_HOSTNAME: config.check_hostname = (char)value; break; |
299 | 0 | case WGET_SSL_CA_TYPE: config.ca_type = (char)value; break; |
300 | 9.89k | case WGET_SSL_CERT_TYPE: config.cert_type = (char)value; break; |
301 | 0 | case WGET_SSL_DANE: config.dane = (char)value; break; |
302 | 9.89k | case WGET_SSL_KEY_TYPE: config.key_type = (char)value; break; |
303 | 9.89k | case WGET_SSL_PRINT_INFO: config.print_info = (char)value; break; |
304 | 9.89k | case WGET_SSL_OCSP: config.ocsp = (char)value; break; |
305 | 9.89k | case WGET_SSL_OCSP_DATE: config.ocsp_date = (char)value; break; |
306 | 9.89k | case WGET_SSL_OCSP_STAPLING: config.ocsp_stapling = (char)value; break; |
307 | 9.89k | case WGET_SSL_OCSP_NONCE: config.ocsp_nonce = value; break; |
308 | 0 | default: error_printf(_("Unknown config key %d (or value must not be an integer)\n"), key); |
309 | 98.9k | } |
310 | 98.9k | } |
311 | | |
312 | | static const char *safe_ctime(time_t t, char *buf, size_t size) |
313 | 0 | { |
314 | 0 | struct tm tm; |
315 | |
|
316 | 0 | #pragma GCC diagnostic push |
317 | 0 | #pragma GCC diagnostic ignored "-Wformat-y2k" |
318 | 0 | if (localtime_r(&t, &tm) && strftime(buf, size, "%c", &tm)) |
319 | 0 | return buf; |
320 | 0 | #pragma GCC diagnostic pop |
321 | | |
322 | 0 | return "[error]"; |
323 | 0 | } |
324 | | |
325 | | static void print_x509_certificate_info(gnutls_session_t session) |
326 | 0 | { |
327 | 0 | const char *name; |
328 | 0 | char dn[128], timebuf[64]; |
329 | 0 | unsigned char digest[64]; |
330 | 0 | unsigned char serial[40]; |
331 | 0 | size_t dn_size = sizeof(dn); |
332 | 0 | size_t digest_size = sizeof (digest); |
333 | 0 | size_t serial_size = sizeof(serial); |
334 | 0 | time_t expired, activated; |
335 | 0 | unsigned int bits; |
336 | 0 | int algo; |
337 | 0 | unsigned int cert_list_size = 0, ncert; |
338 | 0 | const gnutls_datum_t *cert_list; |
339 | 0 | gnutls_x509_crt_t cert; |
340 | 0 | gnutls_certificate_type_t cert_type; |
341 | |
|
342 | 0 | cert_list = gnutls_certificate_get_peers(session, &cert_list_size); |
343 | |
|
344 | 0 | for (ncert = 0; ncert < cert_list_size; ncert++) { |
345 | 0 | if ((cert_type = gnutls_certificate_type_get(session)) == GNUTLS_CRT_X509) { |
346 | |
|
347 | 0 | if (gnutls_x509_crt_init(&cert) != GNUTLS_E_SUCCESS) |
348 | 0 | continue; |
349 | | |
350 | 0 | if (gnutls_x509_crt_import(cert, &cert_list[ncert], GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) { |
351 | 0 | gnutls_x509_crt_deinit(cert); |
352 | 0 | continue; |
353 | 0 | } |
354 | | |
355 | 0 | info_printf(_("Certificate info [%u]:\n"), ncert); |
356 | |
|
357 | 0 | activated = gnutls_x509_crt_get_activation_time(cert); |
358 | 0 | info_printf(_(" Valid since: %s"), safe_ctime(activated, timebuf, sizeof(timebuf))); |
359 | |
|
360 | 0 | expired = gnutls_x509_crt_get_expiration_time(cert); |
361 | 0 | info_printf(_(" Expires: %s"), safe_ctime(expired, timebuf, sizeof(timebuf))); |
362 | |
|
363 | 0 | if (!gnutls_fingerprint(GNUTLS_DIG_MD5, &cert_list[ncert], digest, &digest_size)) { |
364 | 0 | char digest_hex[sizeof(digest) * 2 + 1]; |
365 | |
|
366 | 0 | wget_memtohex(digest, digest_size, digest_hex, sizeof(digest_hex)); |
367 | |
|
368 | 0 | info_printf(_(" Fingerprint: %s\n"), digest_hex); |
369 | 0 | } |
370 | |
|
371 | 0 | if (!gnutls_x509_crt_get_serial(cert, serial, &serial_size)) { |
372 | 0 | char serial_hex[sizeof(serial) * 2 + 1]; |
373 | |
|
374 | 0 | wget_memtohex(serial, serial_size, serial_hex, sizeof(serial_hex)); |
375 | |
|
376 | 0 | info_printf(_(" Serial number: %s\n"), serial_hex); |
377 | 0 | } |
378 | |
|
379 | 0 | algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits); |
380 | 0 | name = gnutls_pk_algorithm_get_name(algo); |
381 | 0 | info_printf(_(" Public key: %s, %s (%u bits)\n"), |
382 | 0 | name ? name : "Unknown", |
383 | 0 | gnutls_sec_param_get_name(gnutls_pk_bits_to_sec_param(algo, bits)), |
384 | 0 | bits); |
385 | |
|
386 | 0 | info_printf(_(" Version: #%d\n"), gnutls_x509_crt_get_version(cert)); |
387 | |
|
388 | 0 | dn_size = sizeof(dn); |
389 | 0 | gnutls_x509_crt_get_dn(cert, dn, &dn_size); |
390 | 0 | info_printf(_(" DN: %s\n"), dn); |
391 | |
|
392 | 0 | dn_size = sizeof(dn); |
393 | 0 | gnutls_x509_crt_get_issuer_dn(cert, dn, &dn_size); |
394 | 0 | info_printf(_(" Issuer's DN: %s\n"), dn); |
395 | |
|
396 | 0 | dn_size = sizeof(dn); |
397 | 0 | gnutls_x509_crt_get_issuer_dn_oid(cert, 0, dn, &dn_size); |
398 | 0 | info_printf(_(" Issuer's OID: %s\n"), dn); |
399 | |
|
400 | 0 | dn_size = sizeof(dn); |
401 | 0 | gnutls_x509_crt_get_issuer_unique_id(cert, dn, &dn_size); |
402 | 0 | info_printf(_(" Issuer's UID: %s\n"), dn); |
403 | | /* |
404 | | dn_size = sizeof(dn); |
405 | | gnutls_x509_crt_get_subject_key_id(cert, dn, &dn_size, NULL); |
406 | | info_printf(_(" Certificate Subject ID: %s\n"), dn); |
407 | | |
408 | | dn_size = sizeof(dn); |
409 | | gnutls_x509_crt_get_subject_unique_id(cert, dn, &dn_size); |
410 | | info_printf(_(" Certificate Subject UID: %s\n"), dn); |
411 | | */ |
412 | 0 | gnutls_x509_crt_deinit(cert); |
413 | 0 | } else { |
414 | 0 | info_printf(_(" Unknown certificate type %d\n"), (int) cert_type); |
415 | 0 | } |
416 | 0 | } |
417 | 0 | } |
418 | | |
419 | | static int print_info(gnutls_session_t session) |
420 | 0 | { |
421 | 0 | const char *tmp; |
422 | 0 | gnutls_credentials_type_t cred; |
423 | 0 | gnutls_kx_algorithm_t kx; |
424 | 0 | int dhe = 0; |
425 | 0 | #if GNUTLS_VERSION_MAJOR >= 3 |
426 | 0 | int ecdh = 0; |
427 | 0 | #endif |
428 | |
|
429 | 0 | kx = gnutls_kx_get(session); |
430 | |
|
431 | 0 | info_printf(_("----\n")); |
432 | | |
433 | | /* Check the authentication type used and switch |
434 | | * to the appropriate. |
435 | | */ |
436 | 0 | cred = gnutls_auth_get_type(session); |
437 | 0 | switch (cred) { |
438 | 0 | case GNUTLS_CRD_IA: |
439 | 0 | info_printf(_("TLS/IA session\n")); |
440 | 0 | break; |
441 | | |
442 | 0 | case GNUTLS_CRD_SRP: |
443 | 0 | #ifdef HAVE_GNUTLS_SRP_SERVER_GET_USERNAME |
444 | 0 | info_printf(_("SRP session with username %s\n"), gnutls_srp_server_get_username(session)); |
445 | 0 | #endif |
446 | 0 | break; |
447 | | |
448 | 0 | case GNUTLS_CRD_PSK: |
449 | | /* This returns NULL in server side. |
450 | | */ |
451 | 0 | if (gnutls_psk_client_get_hint(session) != NULL) |
452 | 0 | info_printf(_("PSK authentication. PSK hint '%s'\n"), gnutls_psk_client_get_hint(session)); |
453 | | |
454 | | /* This returns NULL in client side. |
455 | | */ |
456 | 0 | if (gnutls_psk_server_get_username(session) != NULL) |
457 | 0 | info_printf(_("PSK authentication. Connected as '%s'\n"), gnutls_psk_server_get_username(session)); |
458 | |
|
459 | 0 | if (kx == GNUTLS_KX_DHE_PSK) |
460 | 0 | dhe = 1; |
461 | 0 | #if GNUTLS_VERSION_MAJOR >= 3 |
462 | 0 | else if (kx == GNUTLS_KX_ECDHE_PSK) |
463 | 0 | ecdh = 1; |
464 | 0 | #endif |
465 | 0 | break; |
466 | | |
467 | 0 | case GNUTLS_CRD_ANON: /* anonymous authentication */ |
468 | |
|
469 | 0 | info_printf(_("Anonymous authentication.\n")); |
470 | 0 | if (kx == GNUTLS_KX_ANON_DH) |
471 | 0 | dhe = 1; |
472 | 0 | #if GNUTLS_VERSION_MAJOR >= 3 |
473 | 0 | else if (kx == GNUTLS_KX_ANON_ECDH) |
474 | 0 | ecdh = 1; |
475 | 0 | #endif |
476 | 0 | break; |
477 | | |
478 | 0 | case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ |
479 | | |
480 | | /* Check if we have been using ephemeral Diffie-Hellman. |
481 | | */ |
482 | 0 | if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) |
483 | 0 | dhe = 1; |
484 | 0 | #if GNUTLS_VERSION_MAJOR >= 3 |
485 | 0 | else if (kx == GNUTLS_KX_ECDHE_RSA || kx == GNUTLS_KX_ECDHE_ECDSA) |
486 | 0 | ecdh = 1; |
487 | 0 | #endif |
488 | | |
489 | | /* if the certificate list is available, then |
490 | | * print some information about it. |
491 | | */ |
492 | 0 | print_x509_certificate_info(session); |
493 | 0 | break; |
494 | | |
495 | 0 | default: |
496 | 0 | if ((int) cred == -1) |
497 | 0 | info_printf(_("Transport authentication failure\n")); |
498 | 0 | else |
499 | 0 | info_printf(_("Unsupported credential type %d.\n"), (int) cred); |
500 | 0 | break; |
501 | 0 | } /* switch */ |
502 | | |
503 | 0 | info_printf(_("----\n")); |
504 | |
|
505 | 0 | if (dhe != 0) |
506 | 0 | info_printf(_("Ephemeral DH using prime of %d bits\n"), gnutls_dh_get_prime_bits(session)); |
507 | 0 | #if GNUTLS_VERSION_MAJOR >= 3 |
508 | 0 | else if (ecdh != 0) |
509 | 0 | info_printf(_("Ephemeral ECDH using curve %s\n"), gnutls_ecc_curve_get_name(gnutls_ecc_curve_get(session))); |
510 | 0 | #endif |
511 | | |
512 | | /* print the key exchange's algorithm name */ |
513 | 0 | tmp = gnutls_kx_get_name(kx); |
514 | 0 | info_printf(_("Key Exchange: %s\n"), tmp); |
515 | | |
516 | | /* print the protocol's name (ie TLS 1.0) */ |
517 | 0 | tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(session)); |
518 | 0 | info_printf(_("Protocol: %s\n"), tmp); |
519 | | |
520 | | /* print the certificate type of the peer, ie X.509 */ |
521 | 0 | tmp = gnutls_certificate_type_get_name(gnutls_certificate_type_get(session)); |
522 | 0 | info_printf(_("Certificate Type: %s\n"), tmp); |
523 | | |
524 | | /* print the name of the cipher used, ie 3DES. */ |
525 | 0 | tmp = gnutls_cipher_get_name(gnutls_cipher_get(session)); |
526 | 0 | info_printf(_("Cipher: %s\n"), tmp); |
527 | | |
528 | | /* Print the MAC algorithms name, ie SHA1 */ |
529 | 0 | tmp = gnutls_mac_get_name(gnutls_mac_get(session)); |
530 | 0 | info_printf(_("MAC: %s\n"), tmp); |
531 | |
|
532 | 0 | info_printf(_("----\n")); |
533 | |
|
534 | 0 | return 0; |
535 | 0 | } |
536 | | |
537 | | #ifdef WITH_OCSP |
538 | | static int |
539 | | _generate_ocsp_data(gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer, |
540 | | gnutls_datum_t * rdata, gnutls_datum_t *nonce) |
541 | 0 | { |
542 | 0 | gnutls_ocsp_req_t req; |
543 | 0 | int ret = gnutls_ocsp_req_init(&req); |
544 | |
|
545 | 0 | if (ret < 0) { |
546 | 0 | debug_printf("ocsp_req_init: %s", gnutls_strerror(ret)); |
547 | 0 | return -1; |
548 | 0 | } |
549 | | |
550 | 0 | ret = gnutls_ocsp_req_add_cert(req, GNUTLS_DIG_SHA1, issuer, cert); |
551 | 0 | if (ret < 0) { |
552 | 0 | debug_printf("ocsp_req_add_cert: %s", gnutls_strerror(ret)); |
553 | 0 | goto error; |
554 | 0 | } |
555 | | |
556 | 0 | if (nonce) { |
557 | 0 | ret = gnutls_ocsp_req_set_nonce(req, 0, nonce); |
558 | 0 | if (ret < 0) { |
559 | 0 | debug_printf("ocsp_req_set_nonce: %s", gnutls_strerror(ret)); |
560 | 0 | goto error; |
561 | 0 | } |
562 | 0 | } |
563 | | |
564 | 0 | ret = gnutls_ocsp_req_export(req, rdata); |
565 | 0 | if (ret) { |
566 | 0 | debug_printf("ocsp_req_export: %s", gnutls_strerror(ret)); |
567 | 0 | goto error; |
568 | 0 | } |
569 | | |
570 | 0 | ret = 0; |
571 | 0 | error: |
572 | 0 | gnutls_ocsp_req_deinit(req); |
573 | 0 | return ret; |
574 | 0 | } |
575 | | |
576 | | /* Returns 0 on ok, and -1 on error */ |
577 | | static int send_ocsp_request(const char *server, |
578 | | gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer, |
579 | | wget_buffer **ocsp_data, gnutls_datum_t *nonce) |
580 | 0 | { |
581 | 0 | int ret = -1; |
582 | 0 | int server_allocated = 0; |
583 | 0 | gnutls_datum_t body; |
584 | 0 | wget_iri *iri; |
585 | 0 | wget_http_request *req = NULL; |
586 | |
|
587 | 0 | if (!server) { |
588 | | /* try to read URL from issuer certificate */ |
589 | 0 | gnutls_datum_t data; |
590 | 0 | unsigned i = 0; |
591 | 0 | int rc; |
592 | |
|
593 | 0 | do { |
594 | 0 | rc = gnutls_x509_crt_get_authority_info_access(cert, i++, GNUTLS_IA_OCSP_URI, &data, NULL); |
595 | 0 | } while(rc < 0 && rc != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); |
596 | |
|
597 | 0 | if (rc < 0) { |
598 | 0 | i = 0; |
599 | 0 | do { |
600 | 0 | rc = gnutls_x509_crt_get_authority_info_access(issuer, i++, GNUTLS_IA_OCSP_URI, &data, NULL); |
601 | 0 | } while(rc < 0 && rc != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); |
602 | 0 | } |
603 | |
|
604 | 0 | if (rc < 0) { |
605 | 0 | debug_printf("Cannot find URL from issuer: %s\n", gnutls_strerror(rc)); |
606 | 0 | return -1; |
607 | 0 | } |
608 | | |
609 | 0 | server = wget_strmemdup((char *)data.data, data.size); |
610 | 0 | server_allocated = 1; |
611 | |
|
612 | 0 | xfree(data.data); |
613 | 0 | } |
614 | | |
615 | 0 | iri = wget_iri_parse(server, NULL); |
616 | |
|
617 | 0 | if (server_allocated) |
618 | 0 | xfree(server); |
619 | |
|
620 | 0 | if (!iri) |
621 | 0 | return -1; |
622 | | |
623 | 0 | if (_generate_ocsp_data(cert, issuer, &body, nonce)) |
624 | 0 | goto out; |
625 | | |
626 | 0 | if (!(req = wget_http_create_request(iri, "POST"))) |
627 | 0 | goto out; |
628 | | |
629 | 0 | wget_http_add_header(req, "Accept-Encoding", "identity"); |
630 | 0 | wget_http_add_header(req, "Accept", "*/*"); |
631 | 0 | wget_http_add_header(req, "Connection", "close"); |
632 | |
|
633 | 0 | wget_http_connection *conn; |
634 | 0 | if (wget_http_open(&conn, iri) == WGET_E_SUCCESS) { |
635 | 0 | wget_http_request_set_body(req, "application/ocsp-request", wget_memdup(body.data, body.size), body.size); |
636 | 0 | req->debug_skip_body = 1; |
637 | 0 | if (wget_http_send_request(conn, req) == 0) { |
638 | 0 | wget_http_response *resp; |
639 | |
|
640 | 0 | if ((resp = wget_http_get_response(conn))) { |
641 | 0 | *ocsp_data = resp->body; |
642 | 0 | resp->body = NULL; |
643 | 0 | wget_http_free_response(&resp); |
644 | 0 | ret = 0; |
645 | 0 | } |
646 | 0 | } |
647 | 0 | wget_http_close(&conn); |
648 | 0 | } |
649 | |
|
650 | 0 | xfree(body.data); |
651 | |
|
652 | 0 | out: |
653 | 0 | wget_http_free_request(&req); |
654 | 0 | wget_iri_free(&iri); |
655 | 0 | return ret; |
656 | 0 | } |
657 | | |
658 | | static void print_ocsp_verify_res(unsigned int status) |
659 | 0 | { |
660 | 0 | debug_printf("*** Verifying OCSP Response: "); |
661 | |
|
662 | 0 | if (status) { |
663 | 0 | debug_printf("Failure"); |
664 | |
|
665 | 0 | if (status & GNUTLS_OCSP_VERIFY_SIGNER_NOT_FOUND) |
666 | 0 | debug_printf(", Signer cert not found"); |
667 | |
|
668 | 0 | if (status & GNUTLS_OCSP_VERIFY_SIGNER_KEYUSAGE_ERROR) |
669 | 0 | debug_printf(", Signer cert keyusage error"); |
670 | |
|
671 | 0 | if (status & GNUTLS_OCSP_VERIFY_UNTRUSTED_SIGNER) |
672 | 0 | debug_printf(", Signer cert is not trusted"); |
673 | |
|
674 | 0 | if (status & GNUTLS_OCSP_VERIFY_INSECURE_ALGORITHM) |
675 | 0 | debug_printf(", Insecure algorithm"); |
676 | |
|
677 | 0 | if (status & GNUTLS_OCSP_VERIFY_SIGNATURE_FAILURE) |
678 | 0 | debug_printf(", Signature failure"); |
679 | |
|
680 | 0 | if (status & GNUTLS_OCSP_VERIFY_CERT_NOT_ACTIVATED) |
681 | 0 | debug_printf(", Signer cert not yet activated"); |
682 | |
|
683 | 0 | if (status & GNUTLS_OCSP_VERIFY_CERT_EXPIRED) |
684 | 0 | debug_printf(", Signer cert expired"); |
685 | |
|
686 | 0 | debug_printf("\n"); |
687 | 0 | } else |
688 | 0 | debug_printf("Success\n"); |
689 | 0 | } |
690 | | |
691 | | /* three days */ |
692 | 0 | #define OCSP_VALIDITY_SECS (3*60*60*24) |
693 | | |
694 | | /* Returns: |
695 | | * 0: certificate is revoked |
696 | | * 1: certificate is ok |
697 | | * -1: dunno |
698 | | */ |
699 | | static int check_ocsp_response(gnutls_x509_crt_t cert, |
700 | | gnutls_x509_crt_t issuer, wget_buffer *data, |
701 | | gnutls_datum_t *nonce) |
702 | 0 | { |
703 | 0 | gnutls_ocsp_resp_t resp; |
704 | 0 | int ret = -1, rc; |
705 | 0 | unsigned int status, cert_status; |
706 | 0 | time_t rtime = 0, vtime = 0, ntime = 0, now; |
707 | 0 | char timebuf[64]; |
708 | |
|
709 | 0 | now = time(NULL); |
710 | |
|
711 | 0 | if ((rc = gnutls_ocsp_resp_init(&resp)) < 0) { |
712 | 0 | debug_printf("ocsp_resp_init: %s", gnutls_strerror(rc)); |
713 | 0 | return -1; |
714 | 0 | } |
715 | | |
716 | 0 | rc = gnutls_ocsp_resp_import(resp, &(gnutls_datum_t){ .data = (unsigned char *) data->data, .size = (unsigned) data->length }); |
717 | 0 | if (rc < 0) { |
718 | 0 | debug_printf("importing response: %s", gnutls_strerror(rc)); |
719 | 0 | goto cleanup; |
720 | 0 | } |
721 | | |
722 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030103 |
723 | 0 | if ((rc = gnutls_ocsp_resp_check_crt(resp, 0, cert)) < 0) { |
724 | 0 | if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { |
725 | 0 | debug_printf("got OCSP response with no data (ignoring)\n"); |
726 | 0 | } else { |
727 | 0 | debug_printf("got OCSP response on an unrelated certificate (ignoring)\n"); |
728 | 0 | } |
729 | 0 | goto cleanup; |
730 | 0 | } |
731 | 0 | #endif |
732 | | |
733 | 0 | if ((rc = gnutls_ocsp_resp_verify_direct(resp, issuer, &status, 0)) < 0) { |
734 | 0 | debug_printf("gnutls_ocsp_resp_verify_direct: %s", gnutls_strerror(rc)); |
735 | 0 | goto cleanup; |
736 | 0 | } |
737 | | |
738 | 0 | if (status) { |
739 | 0 | print_ocsp_verify_res(status); |
740 | 0 | goto cleanup; |
741 | 0 | } |
742 | | |
743 | 0 | rc = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL, |
744 | 0 | &cert_status, &vtime, &ntime, &rtime, NULL); |
745 | 0 | if (rc < 0) { |
746 | 0 | debug_printf("reading response: %s", gnutls_strerror(rc)); |
747 | 0 | goto cleanup; |
748 | 0 | } |
749 | | |
750 | 0 | if (cert_status == GNUTLS_OCSP_CERT_REVOKED) { |
751 | 0 | debug_printf("*** Certificate was revoked at %s", safe_ctime(rtime, timebuf, sizeof(timebuf))); |
752 | 0 | ret = 0; |
753 | 0 | goto cleanup; |
754 | 0 | } |
755 | | |
756 | 0 | debug_printf("*** OCSP issued time: %s\n", safe_ctime(vtime, timebuf, sizeof(timebuf))); |
757 | 0 | debug_printf("*** OCSP update time : %s\n", safe_ctime(ntime, timebuf, sizeof(timebuf))); |
758 | |
|
759 | 0 | if (ntime == -1) { |
760 | 0 | if (config.ocsp_date && now - vtime > OCSP_VALIDITY_SECS) { |
761 | 0 | debug_printf("*** The OCSP response is old (was issued at: %s) ignoring", safe_ctime(vtime, timebuf, sizeof(timebuf))); |
762 | 0 | goto cleanup; |
763 | 0 | } |
764 | 0 | } else { |
765 | | /* there is a newer OCSP answer, don't trust this one */ |
766 | 0 | if (ntime < now) { |
767 | 0 | debug_printf("*** The OCSP response was issued at: %s", safe_ctime(vtime, timebuf, sizeof(timebuf))); |
768 | 0 | debug_printf(" but there is a newer issue at %s", safe_ctime(ntime, timebuf, sizeof(timebuf))); |
769 | 0 | goto cleanup; |
770 | 0 | } |
771 | 0 | } |
772 | | |
773 | 0 | if (nonce) { |
774 | 0 | gnutls_datum_t rnonce; |
775 | |
|
776 | 0 | rc = gnutls_ocsp_resp_get_nonce(resp, NULL, &rnonce); |
777 | 0 | if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { |
778 | 0 | debug_printf("*** The OCSP reply did not include the requested nonce.\n"); |
779 | 0 | goto finish_ok; |
780 | 0 | } |
781 | | |
782 | 0 | if (rc < 0) { |
783 | 0 | debug_printf("could not read response's nonce: %s\n", gnutls_strerror(rc)); |
784 | 0 | goto cleanup; |
785 | 0 | } |
786 | | |
787 | 0 | if (config.ocsp_nonce && (rnonce.size != nonce->size || memcmp(nonce->data, rnonce.data, nonce->size) != 0)) { |
788 | 0 | debug_printf("nonce in the response doesn't match\n"); |
789 | 0 | xfree(rnonce.data); |
790 | 0 | goto cleanup; |
791 | 0 | } |
792 | | |
793 | 0 | xfree(rnonce.data); |
794 | 0 | } |
795 | | |
796 | 0 | finish_ok: |
797 | 0 | debug_printf("OCSP server flags certificate not revoked as of %s", safe_ctime(vtime, timebuf, sizeof(timebuf))); |
798 | 0 | ret = 1; |
799 | |
|
800 | 0 | cleanup: |
801 | 0 | gnutls_ocsp_resp_deinit(resp); |
802 | 0 | return ret; |
803 | 0 | } |
804 | | |
805 | | /* |
806 | | * Calculate fingerprint from certificate |
807 | | */ |
808 | | static char *_get_cert_fingerprint(gnutls_x509_crt_t cert, char *fingerprint_hex, size_t length) |
809 | 0 | { |
810 | 0 | unsigned char fingerprint[64]; |
811 | 0 | size_t fingerprint_size = sizeof(fingerprint); |
812 | 0 | int err; |
813 | |
|
814 | 0 | if ((err = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, fingerprint, &fingerprint_size)) < 0) { |
815 | 0 | debug_printf("Failed to get fingerprint: %s\n", gnutls_strerror(err)); |
816 | 0 | wget_strscpy(fingerprint_hex, "00", length); |
817 | 0 | } else { |
818 | 0 | wget_memtohex(fingerprint, fingerprint_size, fingerprint_hex, length); |
819 | 0 | } |
820 | |
|
821 | 0 | return fingerprint_hex; |
822 | 0 | } |
823 | | |
824 | | /* |
825 | | * Add cert to OCSP cache, being either valid or revoked (valid==0) |
826 | | */ |
827 | | static void add_cert_to_ocsp_cache(gnutls_x509_crt_t cert, bool valid) |
828 | 0 | { |
829 | 0 | if (config.ocsp_cert_cache) { |
830 | 0 | char fingerprint_hex[64 * 2 +1]; |
831 | |
|
832 | 0 | _get_cert_fingerprint(cert, fingerprint_hex, sizeof(fingerprint_hex)); |
833 | 0 | wget_ocsp_db_add_fingerprint(config.ocsp_cert_cache, fingerprint_hex, time(NULL) + 3600, valid); // 1h valid |
834 | 0 | } |
835 | 0 | } |
836 | | |
837 | | /* OCSP check for the peer's certificate. Should be called |
838 | | * only after the certificate list verification is complete. |
839 | | * Returns: |
840 | | * 0: certificate is revoked |
841 | | * 1: certificate is ok |
842 | | * -1: dunno |
843 | | */ |
844 | | //static int cert_verify_ocsp(gnutls_session_t session) |
845 | | static int cert_verify_ocsp(gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer) |
846 | 0 | { |
847 | 0 | wget_buffer *resp = NULL; |
848 | 0 | unsigned char noncebuf[23]; |
849 | 0 | gnutls_datum_t nonce = { noncebuf, sizeof(noncebuf) }; |
850 | 0 | int ret; |
851 | |
|
852 | 0 | ret = gnutls_rnd(GNUTLS_RND_NONCE, nonce.data, nonce.size); |
853 | 0 | if (ret < 0) { |
854 | 0 | debug_printf("gnutls_rnd: %s", gnutls_strerror(ret)); |
855 | 0 | return -1; |
856 | 0 | } |
857 | | |
858 | 0 | if (send_ocsp_request(config.ocsp_server, cert, issuer, &resp, &nonce) < 0) { |
859 | 0 | debug_printf("Cannot contact OCSP server\n"); |
860 | 0 | return -1; |
861 | 0 | } |
862 | | |
863 | 0 | if (!resp) { |
864 | 0 | debug_printf("Missing response from OCSP server\n"); |
865 | 0 | return -1; |
866 | 0 | } |
867 | | |
868 | | /* verify and check the response for revoked cert */ |
869 | 0 | ret = check_ocsp_response(cert, issuer, resp, &nonce); |
870 | 0 | wget_buffer_free(&resp); |
871 | |
|
872 | 0 | return ret; |
873 | 0 | } |
874 | | #endif // WITH_OCSP |
875 | | |
876 | | static int cert_verify_hpkp(gnutls_x509_crt_t cert, const char *hostname, gnutls_session_t session) |
877 | 0 | { |
878 | 0 | gnutls_pubkey_t key = NULL; |
879 | 0 | int rc, ret = -1; |
880 | 0 | struct session_context *ctx = gnutls_session_get_ptr(session); |
881 | |
|
882 | 0 | if (!config.hpkp_cache) |
883 | 0 | return 0; |
884 | | |
885 | 0 | gnutls_pubkey_init(&key); |
886 | |
|
887 | 0 | if ((rc = gnutls_pubkey_import_x509(key, cert, 0)) != GNUTLS_E_SUCCESS) { |
888 | 0 | error_printf(_("Failed to import pubkey: %s\n"), gnutls_strerror(rc)); |
889 | 0 | return 0; |
890 | 0 | } |
891 | | |
892 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030103 |
893 | 0 | gnutls_datum_t pubkey; |
894 | |
|
895 | 0 | if ((rc = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &pubkey)) != GNUTLS_E_SUCCESS) { |
896 | 0 | error_printf(_("Failed to export pubkey: %s\n"), gnutls_strerror(rc)); |
897 | 0 | ret = 0; |
898 | 0 | goto out; |
899 | 0 | } |
900 | | |
901 | 0 | rc = wget_hpkp_db_check_pubkey(config.hpkp_cache, hostname, pubkey.data, pubkey.size); |
902 | 0 | xfree(pubkey.data); |
903 | | #else |
904 | | size_t size = 0; |
905 | | void *data = NULL; |
906 | | |
907 | | if ((rc = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &size)) != GNUTLS_E_SHORT_MEMORY_BUFFER) { |
908 | | error_printf(_("Failed to export pubkey: %s\n"), gnutls_strerror(rc)); |
909 | | ret = 0; |
910 | | goto out; |
911 | | } |
912 | | |
913 | | data = wget_malloc(size); |
914 | | |
915 | | if ((rc = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, data, &size)) == GNUTLS_E_SHORT_MEMORY_BUFFER) { |
916 | | error_printf(_("Failed to export pubkey: %s\n"), gnutls_strerror(rc)); |
917 | | ret = 0; |
918 | | goto out; |
919 | | } |
920 | | |
921 | | rc = wget_hpkp_db_check_pubkey(config.hpkp_cache, hostname, data, size); |
922 | | xfree(data); |
923 | | #endif |
924 | |
|
925 | 0 | if (rc != -2) { |
926 | 0 | if (rc == 0) { |
927 | 0 | debug_printf("host has no pubkey pinnings stored in hpkp db\n"); |
928 | 0 | ctx->stats_hpkp = WGET_STATS_HPKP_NO; |
929 | 0 | } else if (rc == 1) { |
930 | 0 | debug_printf("pubkey is matching a pinning\n"); |
931 | 0 | ctx->stats_hpkp = WGET_STATS_HPKP_MATCH; |
932 | 0 | } else if (rc == -1) { |
933 | 0 | debug_printf("Error while checking pubkey pinning\n"); |
934 | 0 | ctx->stats_hpkp = WGET_STATS_HPKP_ERROR; |
935 | 0 | } |
936 | 0 | ret = 0; |
937 | 0 | } else |
938 | 0 | ctx->stats_hpkp = WGET_STATS_HPKP_NOMATCH; |
939 | |
|
940 | 0 | out: |
941 | 0 | gnutls_pubkey_deinit(key); |
942 | 0 | return ret; // Pubkey not found |
943 | 0 | } |
944 | | |
945 | 0 | static void print_verification_status(gnutls_session_t session, const char *tag, int status) { |
946 | 0 | gnutls_datum_t out; |
947 | |
|
948 | 0 | if (gnutls_certificate_verification_status_print( |
949 | 0 | status, gnutls_certificate_type_get(session), &out, 0) == GNUTLS_E_SUCCESS) |
950 | 0 | { |
951 | 0 | error_printf_check("%s: %s\n", tag, out.data); // no translation |
952 | 0 | xfree(out.data); |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | | /* This function will verify the peer's certificate, and check |
957 | | * if the hostname matches, as well as the activation, expiration dates. |
958 | | */ |
959 | | static int verify_certificate_callback(gnutls_session_t session) |
960 | 0 | { |
961 | 0 | unsigned int status, deinit_cert = 0, deinit_issuer = 0; |
962 | 0 | const gnutls_datum_t *cert_list = NULL; |
963 | 0 | unsigned int cert_list_size; |
964 | 0 | int ret = -1, err, ocsp_ok = 0, pinning_ok = 0; |
965 | 0 | gnutls_x509_crt_t cert = NULL, issuer = NULL; |
966 | 0 | const char *tag = config.check_certificate ? _("ERROR") : _("WARNING"); |
967 | 0 | #ifdef WITH_OCSP |
968 | 0 | bool skip_server_cert_check = false; |
969 | 0 | unsigned nvalid = 0, nrevoked = 0, nignored = 0; |
970 | 0 | #endif |
971 | | |
972 | | // read hostname |
973 | 0 | struct session_context *ctx = gnutls_session_get_ptr(session); |
974 | 0 | const char *hostname = ctx->hostname; |
975 | | |
976 | | /* This verification function uses the trusted CAs in the credentials |
977 | | * structure. So you must have installed one or more CA certificates. |
978 | | */ |
979 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030104 |
980 | 0 | if (gnutls_certificate_verify_peers3(session, hostname, &status) != GNUTLS_E_SUCCESS) { |
981 | | #else |
982 | | if (gnutls_certificate_verify_peers2(session, &status) != GNUTLS_E_SUCCESS) { |
983 | | #endif |
984 | | // if (wget_get_logger(WGET_LOGGER_DEBUG)) |
985 | | // _print_info(session); |
986 | 0 | error_printf_check(_("%s: Certificate verification error\n"), tag); |
987 | 0 | goto out; |
988 | 0 | } |
989 | | |
990 | | // if (wget_get_logger(WGET_LOGGER_DEBUG)) |
991 | | // _print_info(session); |
992 | | |
993 | 0 | #ifdef WITH_OCSP |
994 | 0 | if (status & GNUTLS_CERT_REVOKED) { |
995 | 0 | if (config.ocsp_cert_cache) |
996 | 0 | wget_ocsp_db_add_host(config.ocsp_cert_cache, hostname, 0); // remove entry from cache |
997 | 0 | if (ctx->ocsp_stapling) { |
998 | 0 | if (gnutls_x509_crt_init(&cert) == GNUTLS_E_SUCCESS) { |
999 | 0 | if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) { |
1000 | 0 | if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) == GNUTLS_E_SUCCESS) { |
1001 | 0 | add_cert_to_ocsp_cache(cert, false); |
1002 | 0 | } |
1003 | 0 | } |
1004 | 0 | gnutls_x509_crt_deinit(cert); |
1005 | 0 | } |
1006 | 0 | } |
1007 | 0 | } |
1008 | 0 | #endif |
1009 | |
|
1010 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030104 |
1011 | | #ifdef WITH_LIBDANE |
1012 | | // If CA cert verification failed due to missing certificates, we try DANE verification (if requested by the user). |
1013 | | if (status) { |
1014 | | if (!config.dane) { |
1015 | | print_verification_status(session, tag, status); |
1016 | | goto out; |
1017 | | } |
1018 | | if (status != (GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND)) { |
1019 | | print_verification_status(session, tag, status); |
1020 | | goto out; |
1021 | | } |
1022 | | |
1023 | | // GNUTLS_CERT_SIGNER_NOT_FOUND indicates that no matching CA cert exists. |
1024 | | |
1025 | | unsigned verify = 0; |
1026 | | |
1027 | | int rc = dane_verify_session_crt(NULL, session, hostname, "tcp", ctx->port, 0, |
1028 | | DANE_VFLAG_FAIL_IF_NOT_CHECKED, |
1029 | | &verify); |
1030 | | |
1031 | | if (rc < 0) { |
1032 | | debug_printf("DANE verification error for %s: %s\n", hostname, dane_strerror(rc)); |
1033 | | goto out; |
1034 | | } else if (verify) { |
1035 | | gnutls_datum_t out; |
1036 | | rc = dane_verification_status_print(verify, &out, 0); |
1037 | | if (rc < 0) { |
1038 | | error_printf(_("DANE verification print error for %s: %s\n"), hostname, dane_strerror(rc)); |
1039 | | } else { |
1040 | | error_printf(_("DANE verification failed for %s: %s\n"), hostname, out.data); |
1041 | | } |
1042 | | gnutls_free(out.data); |
1043 | | goto out; |
1044 | | } else { |
1045 | | debug_printf("DANE verification: %s\n", dane_strerror(rc)); |
1046 | | } |
1047 | | } |
1048 | | #else |
1049 | 0 | if (status) { |
1050 | 0 | print_verification_status(session, tag, status); |
1051 | 0 | goto out; |
1052 | 0 | } |
1053 | 0 | #endif |
1054 | | #else |
1055 | | if (status) { |
1056 | | if (status & GNUTLS_CERT_INVALID) |
1057 | | error_printf_check(_("%s: The certificate is not trusted.\n"), tag); |
1058 | | if (status & GNUTLS_CERT_REVOKED) |
1059 | | error_printf_check(_("%s: The certificate has been revoked.\n"), tag); |
1060 | | if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) |
1061 | | error_printf_check(_("%s: The certificate doesn't have a known issuer.\n"), tag); |
1062 | | if (status & GNUTLS_CERT_SIGNER_NOT_CA) |
1063 | | error_printf_check(_("%s: The certificate signer was not a CA.\n"), tag); |
1064 | | if (status & GNUTLS_CERT_INSECURE_ALGORITHM) |
1065 | | error_printf_check(_("%s: The certificate was signed using an insecure algorithm.\n"), tag); |
1066 | | if (status & GNUTLS_CERT_NOT_ACTIVATED) |
1067 | | error_printf_check(_("%s: The certificate is not yet activated.\n"), tag); |
1068 | | if (status & GNUTLS_CERT_EXPIRED) |
1069 | | error_printf_check(_("%s: The certificate has expired.\n"), tag); |
1070 | | #if GNUTLS_VERSION_NUMBER >= 0x030100 |
1071 | | if (status & GNUTLS_CERT_SIGNATURE_FAILURE) |
1072 | | error_printf_check(_("%s: The certificate signature is invalid.\n"), tag); |
1073 | | if (status & GNUTLS_CERT_UNEXPECTED_OWNER) |
1074 | | error_printf_check(_("%s: The certificate's owner does not match hostname '%s'.\n"), tag, hostname); |
1075 | | #endif |
1076 | | |
1077 | | // any other reason |
1078 | | if (status & ~(GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED|GNUTLS_CERT_SIGNER_NOT_FOUND| |
1079 | | GNUTLS_CERT_SIGNER_NOT_CA|GNUTLS_CERT_INSECURE_ALGORITHM|GNUTLS_CERT_NOT_ACTIVATED| |
1080 | | GNUTLS_CERT_EXPIRED |
1081 | | #if GNUTLS_VERSION_NUMBER >= 0x030100 |
1082 | | |GNUTLS_CERT_SIGNATURE_FAILURE |
1083 | | |GNUTLS_CERT_UNEXPECTED_OWNER |
1084 | | #endif |
1085 | | )) |
1086 | | error_printf_check(_("%s: The certificate could not be verified (0x%X).\n"), tag, status); |
1087 | | |
1088 | | goto out; |
1089 | | } |
1090 | | #endif |
1091 | | |
1092 | | /* Up to here the process is the same for X.509 certificates and |
1093 | | * OpenPGP keys. From now on X.509 certificates are assumed. This can |
1094 | | * be easily extended to work with openpgp keys as well. |
1095 | | */ |
1096 | 0 | if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) { |
1097 | 0 | error_printf_check(_("%s: Certificate must be X.509\n"), tag); |
1098 | 0 | goto out; |
1099 | 0 | } |
1100 | | |
1101 | 0 | if (gnutls_x509_crt_init(&cert) != GNUTLS_E_SUCCESS) { |
1102 | 0 | error_printf_check(_("%s: Error initializing X.509 certificate\n"), tag); |
1103 | 0 | goto out; |
1104 | 0 | } |
1105 | 0 | deinit_cert = 1; |
1106 | |
|
1107 | 0 | if (!(cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) { |
1108 | 0 | error_printf_check(_("%s: No certificate was found!\n"), tag); |
1109 | 0 | goto out; |
1110 | 0 | } |
1111 | | |
1112 | 0 | if ((err = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) { |
1113 | 0 | error_printf_check(_("%s: Failed to parse certificate: %s\n"), tag, gnutls_strerror (err)); |
1114 | 0 | goto out; |
1115 | 0 | } |
1116 | | |
1117 | 0 | if (!config.check_hostname || (config.check_hostname && hostname && gnutls_x509_crt_check_hostname(cert, hostname))) |
1118 | 0 | ret = 0; |
1119 | 0 | else |
1120 | 0 | goto out; |
1121 | | |
1122 | | // At this point, the cert chain has been found valid regarding the locally available CA certificates and CRLs. |
1123 | | // Now, we are going to check the revocation status via OCSP |
1124 | 0 | #ifdef WITH_OCSP |
1125 | 0 | if (config.ocsp_stapling) { |
1126 | 0 | if (!ctx->valid && ctx->ocsp_stapling) { |
1127 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030103 |
1128 | 0 | if (gnutls_ocsp_status_request_is_checked(session, 0)) { |
1129 | 0 | debug_printf("Server certificate is valid regarding OCSP stapling\n"); |
1130 | | // _get_cert_fingerprint(cert, fingerprint, sizeof(fingerprint)); // calc hexadecimal fingerprint string |
1131 | 0 | add_cert_to_ocsp_cache(cert, true); |
1132 | 0 | nvalid = 1; |
1133 | 0 | skip_server_cert_check = true; |
1134 | 0 | } |
1135 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030400 |
1136 | 0 | else if (gnutls_ocsp_status_request_is_checked(session, GNUTLS_OCSP_SR_IS_AVAIL)) { |
1137 | 0 | error_printf_check(_("WARNING: The certificate's (stapled) OCSP status is invalid\n")); |
1138 | 0 | skip_server_cert_check = true; |
1139 | 0 | } |
1140 | 0 | #endif |
1141 | 0 | else if (!config.ocsp) { |
1142 | 0 | debug_printf("OCSP stapling is not supported by '%s'\n", hostname); |
1143 | 0 | } else { |
1144 | 0 | error_printf_check(_("WARNING: OCSP stapling is not supported by '%s', but OCSP validation has been requested.\n"), hostname); |
1145 | 0 | error_printf_check(_("WARNING: This implies a privacy leak: the client sends the certificate serial ID over HTTP to the CA.\n")); |
1146 | 0 | } |
1147 | 0 | #endif |
1148 | 0 | } else if (ctx->valid) |
1149 | 0 | debug_printf("OCSP: Host '%s' is valid (from cache)\n", hostname); |
1150 | 0 | } |
1151 | 0 | #endif |
1152 | |
|
1153 | 0 | for (unsigned it = 0; it < cert_list_size; it++) { |
1154 | 0 | gnutls_x509_crt_deinit(cert); |
1155 | 0 | gnutls_x509_crt_init(&cert); |
1156 | |
|
1157 | 0 | if ((err = gnutls_x509_crt_import(cert, &cert_list[it], GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) { |
1158 | 0 | error_printf_check(_("%s: Failed to parse certificate[%u]: %s\n"), tag, it, gnutls_strerror (err)); |
1159 | 0 | continue; |
1160 | 0 | } |
1161 | | |
1162 | 0 | if (cert_verify_hpkp(cert, hostname, session) == 0) |
1163 | 0 | pinning_ok = 1; |
1164 | |
|
1165 | 0 | cert_verify_hpkp(cert, hostname, session); |
1166 | |
|
1167 | 0 | #ifdef WITH_OCSP |
1168 | 0 | if (!config.ocsp || (skip_server_cert_check && it == 0)) |
1169 | 0 | continue; |
1170 | | |
1171 | 0 | char fingerprint[64 * 2 +1]; |
1172 | 0 | _get_cert_fingerprint(cert, fingerprint, sizeof(fingerprint)); // calc hexadecimal fingerprint string |
1173 | |
|
1174 | 0 | int revoked; |
1175 | 0 | if (wget_ocsp_fingerprint_in_cache(config.ocsp_cert_cache, fingerprint, &revoked)) { |
1176 | | // found cert's fingerprint in cache |
1177 | 0 | if (revoked) { |
1178 | 0 | debug_printf("Certificate[%u] of '%s' has been revoked (cached)\n", it, hostname); |
1179 | 0 | nrevoked++; |
1180 | 0 | } else { |
1181 | 0 | debug_printf("Certificate[%u] of '%s' is valid (cached)\n", it, hostname); |
1182 | 0 | nvalid++; |
1183 | 0 | } |
1184 | 0 | continue; |
1185 | 0 | } |
1186 | | |
1187 | 0 | if (deinit_issuer) { |
1188 | 0 | gnutls_x509_crt_deinit(issuer); |
1189 | 0 | deinit_issuer = 0; |
1190 | 0 | } |
1191 | 0 | if ((err = gnutls_certificate_get_issuer(credentials, cert, &issuer, 0)) != GNUTLS_E_SUCCESS && it < cert_list_size - 1) { |
1192 | 0 | gnutls_x509_crt_init(&issuer); |
1193 | 0 | deinit_issuer = 1; |
1194 | 0 | if ((err = gnutls_x509_crt_import(issuer, &cert_list[it + 1], GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) { |
1195 | 0 | debug_printf("Decoding error: %s\n", gnutls_strerror(err)); |
1196 | 0 | continue; |
1197 | 0 | } |
1198 | 0 | } else if (err != GNUTLS_E_SUCCESS) { |
1199 | 0 | debug_printf("Cannot find issuer: %s\n", gnutls_strerror(err)); |
1200 | 0 | continue; |
1201 | 0 | } |
1202 | | |
1203 | 0 | ocsp_ok = cert_verify_ocsp(cert, issuer); |
1204 | 0 | debug_printf("check_ocsp_response() returned %d\n", ocsp_ok); |
1205 | |
|
1206 | 0 | if (ocsp_ok == 1) { |
1207 | 0 | debug_printf("Certificate[%u] of '%s' is valid (via OCSP)\n", it, hostname); |
1208 | 0 | wget_ocsp_db_add_fingerprint(config.ocsp_cert_cache, fingerprint, time(NULL) + 3600, true); // 1h valid |
1209 | 0 | nvalid++; |
1210 | 0 | } else if (ocsp_ok == 0) { |
1211 | 0 | debug_printf("%s: Certificate[%u] of '%s' has been revoked (via OCSP)\n", tag, it, hostname); |
1212 | 0 | wget_ocsp_db_add_fingerprint(config.ocsp_cert_cache, fingerprint, time(NULL) + 3600, false); // cert has been revoked |
1213 | 0 | nrevoked++; |
1214 | 0 | } else { |
1215 | 0 | debug_printf("WARNING: OCSP response not available or ignored\n"); |
1216 | 0 | nignored++; |
1217 | 0 | } |
1218 | 0 | #endif |
1219 | 0 | } |
1220 | |
|
1221 | 0 | #ifdef WITH_OCSP |
1222 | 0 | if (config.ocsp && ocsp_stats_callback) { |
1223 | 0 | wget_ocsp_stats_data stats; |
1224 | 0 | stats.hostname = hostname; |
1225 | 0 | stats.nvalid = nvalid; |
1226 | 0 | stats.nrevoked = nrevoked; |
1227 | 0 | stats.nignored = nignored; |
1228 | 0 | stats.stapling = ctx->ocsp_stapling; |
1229 | |
|
1230 | 0 | ocsp_stats_callback(&stats, ocsp_stats_ctx); |
1231 | 0 | } |
1232 | |
|
1233 | 0 | if (config.ocsp_stapling || config.ocsp) { |
1234 | 0 | if (nvalid == cert_list_size) { |
1235 | 0 | wget_ocsp_db_add_host(config.ocsp_cert_cache, hostname, time(NULL) + 3600); // 1h valid |
1236 | 0 | } else if (nrevoked) { |
1237 | 0 | wget_ocsp_db_add_host(config.ocsp_cert_cache, hostname, 0); // remove entry from cache |
1238 | 0 | ret = -1; |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | #endif |
1242 | |
|
1243 | 0 | if (!pinning_ok) { |
1244 | 0 | error_printf_check(_("%s: Pubkey pinning mismatch!\n"), tag); |
1245 | 0 | ret = -1; |
1246 | 0 | } |
1247 | | |
1248 | | // 0: continue handshake |
1249 | | // else: stop handshake |
1250 | 0 | out: |
1251 | 0 | if (deinit_cert) |
1252 | 0 | gnutls_x509_crt_deinit(cert); |
1253 | 0 | if (deinit_issuer) |
1254 | 0 | gnutls_x509_crt_deinit(issuer); |
1255 | |
|
1256 | 0 | return config.check_certificate ? ret : 0; |
1257 | 0 | } |
1258 | | |
1259 | | static int init; |
1260 | | static wget_thread_mutex mutex; |
1261 | | |
1262 | | static void tls_exit(void) |
1263 | 0 | { |
1264 | 0 | if (mutex) |
1265 | 0 | wget_thread_mutex_destroy(&mutex); |
1266 | 0 | } |
1267 | | |
1268 | | INITIALIZER(tls_init) |
1269 | 4 | { |
1270 | 4 | if (!mutex) { |
1271 | 4 | wget_thread_mutex_init(&mutex); |
1272 | | |
1273 | | // Initialize paths while in a thread-safe environment (mostly for _WIN32). |
1274 | 4 | wget_ssl_default_cert_dir(); |
1275 | 4 | wget_ssl_default_ca_bundle_path(); |
1276 | | |
1277 | 4 | atexit(tls_exit); |
1278 | 4 | } |
1279 | 4 | } |
1280 | | |
1281 | | static int key_type(int type) |
1282 | 0 | { |
1283 | 0 | if (type == WGET_SSL_X509_FMT_DER) |
1284 | 0 | return GNUTLS_X509_FMT_DER; |
1285 | | |
1286 | 0 | return GNUTLS_X509_FMT_PEM; |
1287 | 0 | } |
1288 | | |
1289 | | // ssl_init() is thread safe |
1290 | | |
1291 | | static void set_credentials(gnutls_certificate_credentials_t creds) |
1292 | 0 | { |
1293 | 0 | if (config.cert_file && !config.key_file) { |
1294 | | // Use the private key from the cert file unless otherwise specified. |
1295 | 0 | config.key_file = config.cert_file; |
1296 | 0 | config.key_type = config.cert_type; |
1297 | 0 | } |
1298 | 0 | else if (!config.cert_file && config.key_file) { |
1299 | | // Use the cert from the private key file unless otherwise specified. |
1300 | 0 | config.cert_file = config.key_file; |
1301 | 0 | config.cert_type = config.key_type; |
1302 | 0 | } |
1303 | |
|
1304 | 0 | if (config.cert_file && config.key_file) { |
1305 | 0 | if (config.key_type != config.cert_type) { |
1306 | | // GnuTLS can't handle this |
1307 | 0 | error_printf(_("GnuTLS requires the key and the cert to be of the same type.\n")); |
1308 | 0 | } |
1309 | |
|
1310 | 0 | if (gnutls_certificate_set_x509_key_file(creds, config.cert_file, config.key_file, key_type(config.key_type)) != GNUTLS_E_SUCCESS) |
1311 | 0 | error_printf(_("No certificates or keys were found\n")); |
1312 | 0 | } |
1313 | |
|
1314 | 0 | if (config.ca_file && !wget_strcmp(config.ca_file, "system")) |
1315 | 0 | config.ca_file = wget_ssl_default_ca_bundle_path(); |
1316 | 0 | if (config.ca_file) { |
1317 | 0 | if (gnutls_certificate_set_x509_trust_file(creds, config.ca_file, key_type(config.ca_type)) <= 0) |
1318 | 0 | error_printf(_("No CAs were found in '%s'\n"), config.ca_file); |
1319 | 0 | } |
1320 | 0 | } |
1321 | | |
1322 | | /** |
1323 | | * Initialize the SSL/TLS engine as a client. |
1324 | | * |
1325 | | * This function assumes the caller is an SSL client connecting to a server. |
1326 | | * The functions wget_ssl_open(), wget_ssl_close() and wget_ssl_deinit() can be called |
1327 | | * after this. |
1328 | | * |
1329 | | * This is where the root certificates get loaded from the folder specified in the |
1330 | | * `WGET_SSL_CA_DIRECTORY` parameter. If any of the files in that folder cannot be loaded |
1331 | | * for whatever reason, that file will be silently skipped without harm (a message will be |
1332 | | * printed to the debug log stream). |
1333 | | * |
1334 | | * CLRs and private keys and their certificates are also loaded here. |
1335 | | * |
1336 | | * On systems with automatic library constructors/destructors, this function |
1337 | | * is thread-safe. On other systems it is not thread-safe. |
1338 | | * |
1339 | | * This function may be called several times. Only the first call really |
1340 | | * takes action. |
1341 | | */ |
1342 | | void wget_ssl_init(void) |
1343 | 0 | { |
1344 | 0 | tls_init(); |
1345 | |
|
1346 | 0 | wget_thread_mutex_lock(mutex); |
1347 | |
|
1348 | 0 | if (!init) { |
1349 | 0 | int rc, ncerts = -1; |
1350 | |
|
1351 | 0 | debug_printf("GnuTLS init\n"); |
1352 | 0 | gnutls_global_init(); |
1353 | 0 | gnutls_certificate_allocate_credentials(&credentials); |
1354 | 0 | gnutls_certificate_set_verify_function(credentials, verify_certificate_callback); |
1355 | |
|
1356 | 0 | if (config.ca_directory && *config.ca_directory && config.check_certificate) { |
1357 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x03000d |
1358 | 0 | if (!strcmp(config.ca_directory, "system")) { |
1359 | 0 | ncerts = gnutls_certificate_set_x509_system_trust(credentials); |
1360 | 0 | if (ncerts < 0) |
1361 | 0 | debug_printf("GnuTLS system certificate store error %d\n", ncerts); |
1362 | 0 | else |
1363 | 0 | debug_printf("GnuTLS system certificate store is empty\n"); |
1364 | 0 | } |
1365 | 0 | #endif |
1366 | |
|
1367 | 0 | if (ncerts < 0) { |
1368 | 0 | DIR *dir; |
1369 | |
|
1370 | 0 | ncerts = 0; |
1371 | |
|
1372 | 0 | if (!strcmp(config.ca_directory, "system")) |
1373 | 0 | config.ca_directory = wget_ssl_default_cert_dir(); |
1374 | |
|
1375 | 0 | if ((dir = opendir(config.ca_directory))) { |
1376 | 0 | struct dirent *dp; |
1377 | |
|
1378 | 0 | while ((dp = readdir(dir))) { |
1379 | 0 | size_t len = strlen(dp->d_name); |
1380 | |
|
1381 | 0 | if (len >= 4 && !wget_strncasecmp_ascii(dp->d_name + len - 4, ".pem", 4)) { |
1382 | 0 | char *fname = wget_aprintf("%s/%s", config.ca_directory, dp->d_name); |
1383 | |
|
1384 | 0 | if (!fname) { |
1385 | 0 | error_printf(_("Failed to allocate file name for cert '%s/%s'\n"), config.ca_directory, dp->d_name); |
1386 | 0 | continue; |
1387 | 0 | } |
1388 | | |
1389 | 0 | struct stat st; |
1390 | 0 | if (stat(fname, &st) == 0 && S_ISREG(st.st_mode)) { |
1391 | 0 | debug_printf("GnuTLS loading %s\n", fname); |
1392 | 0 | if ((rc = gnutls_certificate_set_x509_trust_file(credentials, fname, GNUTLS_X509_FMT_PEM)) <= 0) |
1393 | 0 | debug_printf("Failed to load cert '%s': (%d)\n", fname, rc); |
1394 | 0 | else |
1395 | 0 | ncerts += rc; |
1396 | 0 | } |
1397 | |
|
1398 | 0 | xfree(fname); |
1399 | 0 | } |
1400 | 0 | } |
1401 | |
|
1402 | 0 | closedir(dir); |
1403 | 0 | } else { |
1404 | 0 | error_printf(_("Failed to opendir %s\n"), config.ca_directory); |
1405 | 0 | } |
1406 | 0 | } |
1407 | 0 | } |
1408 | |
|
1409 | 0 | if (config.crl_file) { |
1410 | 0 | if ((rc = gnutls_certificate_set_x509_crl_file(credentials, config.crl_file, GNUTLS_X509_FMT_PEM)) <= 0) |
1411 | 0 | error_printf(_("Failed to load CRL '%s': (%d)\n"), config.crl_file, rc); |
1412 | 0 | } |
1413 | |
|
1414 | 0 | set_credentials(credentials); |
1415 | |
|
1416 | 0 | debug_printf("Certificates loaded: %d\n", ncerts); |
1417 | |
|
1418 | 0 | if (config.secure_protocol) { |
1419 | 0 | const char *priorities = NULL; |
1420 | |
|
1421 | 0 | if (!wget_strcasecmp_ascii(config.secure_protocol, "PFS")) { |
1422 | 0 | priorities = "PFS:-VERS-SSL3.0"; |
1423 | | // -RSA to force DHE/ECDHE key exchanges to have Perfect Forward Secrecy (PFS)) |
1424 | 0 | if ((rc = gnutls_priority_init(&priority_cache, priorities, NULL)) != GNUTLS_E_SUCCESS) { |
1425 | 0 | priorities = "NORMAL:-RSA:-VERS-SSL3.0"; |
1426 | 0 | rc = gnutls_priority_init(&priority_cache, priorities, NULL); |
1427 | 0 | } |
1428 | 0 | } else { |
1429 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030603 |
1430 | 0 | #define TLS13_PRIO ":+VERS-TLS1.3" |
1431 | | #else |
1432 | | #define TLS13_PRIO "" |
1433 | | #endif |
1434 | 0 | if (!wget_strncasecmp_ascii(config.secure_protocol, "SSL", 3)) |
1435 | 0 | priorities = "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0"; |
1436 | 0 | else if (!wget_strcasecmp_ascii(config.secure_protocol, "TLSv1")) |
1437 | 0 | priorities = "NORMAL:-VERS-SSL3.0" TLS13_PRIO; |
1438 | 0 | else if (!wget_strcasecmp_ascii(config.secure_protocol, "TLSv1_1")) |
1439 | 0 | priorities = "NORMAL:-VERS-SSL3.0:-VERS-TLS1.0" TLS13_PRIO; |
1440 | 0 | else if (!wget_strcasecmp_ascii(config.secure_protocol, "TLSv1_2")) |
1441 | 0 | priorities = "NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1" TLS13_PRIO; |
1442 | 0 | else if (!wget_strcasecmp_ascii(config.secure_protocol, "TLSv1_3")) |
1443 | 0 | priorities = "NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2" TLS13_PRIO; |
1444 | 0 | else if (!wget_strcasecmp_ascii(config.secure_protocol, "auto")) { |
1445 | | /* use system default, priorities = NULL */ |
1446 | 0 | } else if (*config.secure_protocol) |
1447 | 0 | priorities = config.secure_protocol; |
1448 | |
|
1449 | 0 | rc = gnutls_priority_init(&priority_cache, priorities, NULL); |
1450 | 0 | } |
1451 | |
|
1452 | 0 | if (rc != GNUTLS_E_SUCCESS) |
1453 | 0 | error_printf(_("GnuTLS: Unsupported priority string '%s': %s\n"), priorities ? priorities : "(null)", gnutls_strerror(rc)); |
1454 | 0 | } else { |
1455 | | // use GnuTLS defaults, which might hold insecure ciphers |
1456 | 0 | if ((rc = gnutls_priority_init(&priority_cache, NULL, NULL))) |
1457 | 0 | error_printf(_("GnuTLS: Unsupported default priority 'NULL': %s\n"), gnutls_strerror(rc)); |
1458 | 0 | } |
1459 | |
|
1460 | 0 | init++; |
1461 | |
|
1462 | 0 | debug_printf("GnuTLS init done\n"); |
1463 | 0 | } |
1464 | |
|
1465 | 0 | wget_thread_mutex_unlock(mutex); |
1466 | 0 | } |
1467 | | |
1468 | | /** |
1469 | | * Deinitialize the SSL/TLS engine, after it has been initialized |
1470 | | * with wget_ssl_init(). |
1471 | | * |
1472 | | * This function unloads everything that was loaded in wget_ssl_init(). |
1473 | | * |
1474 | | * On systems with automatic library constructors/destructors, this function |
1475 | | * is thread-safe. On other systems it is not thread-safe. |
1476 | | * |
1477 | | * This function may be called several times. Only the last deinit really |
1478 | | * takes action. |
1479 | | */ |
1480 | | void wget_ssl_deinit(void) |
1481 | 0 | { |
1482 | 0 | wget_thread_mutex_lock(mutex); |
1483 | |
|
1484 | 0 | if (init == 1) { |
1485 | 0 | gnutls_certificate_free_credentials(credentials); |
1486 | 0 | gnutls_priority_deinit(priority_cache); |
1487 | 0 | gnutls_global_deinit(); |
1488 | 0 | } |
1489 | |
|
1490 | 0 | if (init > 0) init--; |
1491 | |
|
1492 | 0 | wget_thread_mutex_unlock(mutex); |
1493 | 0 | } |
1494 | | |
1495 | | static int do_handshake(gnutls_session_t session, int sockfd, int timeout) |
1496 | 0 | { |
1497 | 0 | int ret; |
1498 | | |
1499 | | // Wait for socket being ready before we call gnutls_handshake(). |
1500 | | // I had problems on a KVM Win7 + CygWin (gnutls 3.2.4-1). |
1501 | 0 | int rc = wget_ready_2_write(sockfd, timeout); |
1502 | |
|
1503 | 0 | if (rc == 0) |
1504 | 0 | ret = WGET_E_TIMEOUT; |
1505 | 0 | else |
1506 | 0 | ret = WGET_E_HANDSHAKE; |
1507 | | |
1508 | | // Perform the TLS handshake |
1509 | 0 | while (rc > 0) { |
1510 | 0 | rc = gnutls_handshake(session); |
1511 | |
|
1512 | 0 | if (rc == GNUTLS_E_SUCCESS) { |
1513 | 0 | ret = WGET_E_SUCCESS; |
1514 | 0 | break; |
1515 | 0 | } |
1516 | | |
1517 | 0 | if (gnutls_error_is_fatal(rc)) { |
1518 | 0 | debug_printf("gnutls_handshake: (%d) %s (errno=%d)\n", rc, gnutls_strerror(rc),errno); |
1519 | |
|
1520 | 0 | if (rc == GNUTLS_E_CERTIFICATE_ERROR) { |
1521 | 0 | ret = WGET_E_CERTIFICATE; |
1522 | 0 | } else if (rc == GNUTLS_E_PUSH_ERROR && (errno == ECONNREFUSED || errno == ENOTCONN)) { |
1523 | | /* |
1524 | | * ECONNREFUSED: on Linux |
1525 | | * ENOTCONN: MinGW (in out Gitlab CI runner) |
1526 | | */ |
1527 | 0 | ret = WGET_E_CONNECT; |
1528 | 0 | } else if (rc == GNUTLS_E_PULL_ERROR && errno == 61 /* ENODATA, but not on OSX/Travis ? */) { |
1529 | | // We see this with older versions of GnuTLS, e.g. on TravisCI. (Tim, 11.4.2018) |
1530 | | // It happens when trying to connect to a port without a listener |
1531 | 0 | ret = WGET_E_CONNECT; |
1532 | 0 | #ifdef GNUTLS_E_PREMATURE_TERMINATION |
1533 | 0 | } else if (rc == GNUTLS_E_PREMATURE_TERMINATION && errno == EAGAIN) { |
1534 | | // It happens when trying to connect to a closed port |
1535 | 0 | ret = WGET_E_CONNECT; |
1536 | 0 | #endif |
1537 | 0 | } else if (rc == GNUTLS_E_UNEXPECTED_PACKET_LENGTH && errno == EAGAIN) { |
1538 | | // We see this with older versions of GnuTLS, e.g. on TravisCI. (Tim, 11.4.2018) |
1539 | | // It happens when trying to connect to a port without a listener |
1540 | 0 | ret = WGET_E_CONNECT; |
1541 | 0 | } else |
1542 | 0 | ret = WGET_E_HANDSHAKE; |
1543 | |
|
1544 | 0 | break; |
1545 | 0 | } |
1546 | | |
1547 | 0 | if (gnutls_record_get_direction(session)) { |
1548 | | // wait for writeability |
1549 | 0 | rc = wget_ready_2_write(sockfd, timeout); |
1550 | 0 | } else { |
1551 | | // wait for readability |
1552 | 0 | rc = wget_ready_2_read(sockfd, timeout); |
1553 | 0 | } |
1554 | 0 | } |
1555 | |
|
1556 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030500 |
1557 | 0 | if (ret == WGET_E_SUCCESS) |
1558 | 0 | debug_printf("TLS False Start: %s\n", |
1559 | 0 | (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_FALSE_START) ? "on" : "off"); |
1560 | 0 | #endif |
1561 | |
|
1562 | 0 | return ret; |
1563 | 0 | } |
1564 | | |
1565 | | #ifdef MSG_FASTOPEN |
1566 | | #include <sys/socket.h> |
1567 | | #include <sys/uio.h> // writev |
1568 | | #include <netdb.h> |
1569 | | #include <errno.h> |
1570 | | static ssize_t ssl_writev(gnutls_transport_ptr_t *p, const giovec_t *iov, int iovcnt) |
1571 | 0 | { |
1572 | 0 | wget_tcp *tcp = (wget_tcp *) p; |
1573 | 0 | ssize_t ret; |
1574 | | |
1575 | | // info_printf("%s: %d %zu\n", __func__, iovcnt, iov[0].iov_len); |
1576 | 0 | if (tcp->first_send) { |
1577 | 0 | struct msghdr hdr = { |
1578 | 0 | .msg_name = tcp->connect_addrinfo->ai_addr, |
1579 | 0 | .msg_namelen = tcp->connect_addrinfo->ai_addrlen, |
1580 | 0 | .msg_iov = (struct iovec *) iov, |
1581 | 0 | .msg_iovlen = iovcnt, |
1582 | 0 | }; |
1583 | | |
1584 | | // ret = sendto(tcp->sockfd, iov[0].iov_base, iov[0].iov_len, MSG_FASTOPEN, |
1585 | | // tcp->connect_addrinfo->ai_addr, tcp->connect_addrinfo->ai_addrlen); |
1586 | 0 | ret = sendmsg(tcp->sockfd, &hdr, MSG_FASTOPEN); |
1587 | 0 | if (ret < 0) { |
1588 | 0 | if (errno == EINPROGRESS) { |
1589 | 0 | errno = EAGAIN; // GnuTLS does not handle EINPROGRESS |
1590 | 0 | } else if (errno == EOPNOTSUPP) { |
1591 | | // fallback from fastopen, e.g. when fastopen is disabled in system |
1592 | 0 | debug_printf("Fallback from TCP Fast Open... TFO is disabled at system level\n"); |
1593 | 0 | tcp->tcp_fastopen = 0; |
1594 | 0 | ret = connect(tcp->sockfd, tcp->connect_addrinfo->ai_addr, tcp->connect_addrinfo->ai_addrlen); |
1595 | 0 | if (errno == ENOTCONN || errno == EINPROGRESS) |
1596 | 0 | errno = EAGAIN; |
1597 | 0 | } |
1598 | 0 | } |
1599 | |
|
1600 | 0 | tcp->first_send = 0; |
1601 | 0 | } else { |
1602 | 0 | ret = writev(tcp->sockfd, (struct iovec *) iov, iovcnt); |
1603 | 0 | } |
1604 | | // info_printf("errno=%d ret=%d\n", errno, ret); |
1605 | | |
1606 | | // after the first write we set back the transport push function and the transport pointer to standard functions |
1607 | | #ifdef HAVE_GNUTLS_TRANSPORT_GET_INT |
1608 | | // since GnuTLS 3.1.9, avoid warnings about illegal pointer conversion |
1609 | | gnutls_transport_set_int(tcp->ssl_session, tcp->sockfd); |
1610 | | #else |
1611 | 0 | gnutls_transport_set_ptr(tcp->ssl_session, (gnutls_transport_ptr_t)(ptrdiff_t)tcp->sockfd); |
1612 | 0 | #endif |
1613 | |
|
1614 | 0 | #if defined __clang__ |
1615 | 0 | #pragma clang diagnostic push |
1616 | 0 | #pragma clang diagnostic ignored "-Wcast-function-type" |
1617 | 0 | #endif |
1618 | 0 | gnutls_transport_set_vec_push_function(tcp->ssl_session, (ssize_t (*) (gnutls_transport_ptr_t, const giovec_t * iov, int iovcnt)) writev); |
1619 | 0 | #if defined __clang__ |
1620 | 0 | #pragma clang diagnostic pop |
1621 | 0 | #endif |
1622 | |
|
1623 | 0 | return ret; |
1624 | 0 | } |
1625 | | #endif |
1626 | | |
1627 | | #ifdef _WIN32 |
1628 | | static ssize_t win32_send(gnutls_transport_ptr_t p, const void *buf, size_t size) |
1629 | | { |
1630 | | int sockfd = (int) (ptrdiff_t) p; |
1631 | | |
1632 | | return send(sockfd, buf, size, 0); |
1633 | | } |
1634 | | static ssize_t win32_recv(gnutls_transport_ptr_t p, void *buf, size_t size) |
1635 | | { |
1636 | | int sockfd = (int) (ptrdiff_t) p; |
1637 | | |
1638 | | return recv(sockfd, buf, size, 0); |
1639 | | } |
1640 | | #endif |
1641 | | |
1642 | | /** |
1643 | | * \param[in] tcp A TCP connection (see wget_tcp_init()) |
1644 | | * \return `WGET_E_SUCCESS` on success or an error code (`WGET_E_*`) on failure |
1645 | | * |
1646 | | * Run an SSL/TLS handshake. |
1647 | | * |
1648 | | * This functions establishes an SSL/TLS tunnel (performs an SSL/TLS handshake) |
1649 | | * over an active TCP connection. A pointer to the (internal) SSL/TLS session context |
1650 | | * can be found in `tcp->ssl_session` after successful execution of this function. This pointer |
1651 | | * has to be passed to wget_ssl_close() to close the SSL/TLS tunnel. |
1652 | | * |
1653 | | * If the handshake cannot be completed in the specified timeout for the provided TCP connection |
1654 | | * this function fails and returns `WGET_E_TIMEOUT`. You can set the timeout with wget_tcp_set_timeout(). |
1655 | | */ |
1656 | | int wget_ssl_open(wget_tcp *tcp) |
1657 | 0 | { |
1658 | 0 | gnutls_session_t session; |
1659 | 0 | wget_tls_stats_data stats = { |
1660 | 0 | .alpn_protocol = NULL, |
1661 | 0 | .version = -1, |
1662 | 0 | .false_start = -1, |
1663 | 0 | .tfo = -1, |
1664 | 0 | .resumed = 0, |
1665 | 0 | .http_protocol = WGET_PROTOCOL_HTTP_1_1, |
1666 | 0 | .cert_chain_size = 0 |
1667 | 0 | }; |
1668 | |
|
1669 | 0 | int ret = WGET_E_UNKNOWN; |
1670 | 0 | int rc, sockfd, connect_timeout; |
1671 | 0 | const char *hostname; |
1672 | 0 | long long before_millisecs = 0; |
1673 | |
|
1674 | 0 | if (!tcp) |
1675 | 0 | return WGET_E_INVALID; |
1676 | | |
1677 | 0 | struct session_context *ctx = wget_calloc(1, sizeof(struct session_context)); |
1678 | 0 | if (!ctx) |
1679 | 0 | return WGET_E_MEMORY; |
1680 | | |
1681 | 0 | if (!init) |
1682 | 0 | wget_ssl_init(); |
1683 | |
|
1684 | 0 | hostname = tcp->ssl_hostname; |
1685 | 0 | sockfd= tcp->sockfd; |
1686 | 0 | connect_timeout = tcp->connect_timeout; |
1687 | |
|
1688 | 0 | unsigned int flags = GNUTLS_CLIENT; |
1689 | |
|
1690 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030500 |
1691 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030605 |
1692 | 0 | flags |= GNUTLS_AUTO_REAUTH | GNUTLS_POST_HANDSHAKE_AUTH; |
1693 | 0 | #endif |
1694 | |
|
1695 | 0 | if (tcp->tls_false_start) { |
1696 | 0 | debug_printf("TLS False Start requested\n"); |
1697 | |
|
1698 | 0 | flags |= GNUTLS_NONBLOCK | GNUTLS_ENABLE_FALSE_START; |
1699 | |
|
1700 | 0 | gnutls_init(&session, flags); |
1701 | 0 | } else { |
1702 | 0 | flags |= GNUTLS_NONBLOCK; |
1703 | |
|
1704 | 0 | gnutls_init(&session, flags); |
1705 | 0 | } |
1706 | | #elif defined GNUTLS_NONBLOCK |
1707 | | if (tcp->tls_false_start) |
1708 | | error_printf(_("TLS False Start requested but libwget built with insufficient GnuTLS version\n")); |
1709 | | flags |= GNUTLS_NONBLOCK; |
1710 | | gnutls_init(&session, flags); |
1711 | | #else |
1712 | | // very old gnutls version, likely to not work. |
1713 | | if (tcp->tls_false_start) |
1714 | | error_printf(_("TLS False Start requested but libwget built with insufficient GnuTLS version\n")); |
1715 | | gnutls_init(&session, flags); |
1716 | | #endif |
1717 | |
|
1718 | 0 | if ((rc = gnutls_priority_set(session, priority_cache)) != GNUTLS_E_SUCCESS) |
1719 | 0 | error_printf(_("GnuTLS: Failed to set priorities: %s\n"), gnutls_strerror(rc)); |
1720 | |
|
1721 | 0 | if (!wget_strcasecmp_ascii(config.secure_protocol, "auto")) |
1722 | 0 | gnutls_session_enable_compatibility_mode(session); |
1723 | | |
1724 | | // RFC 6066 SNI Server Name Indication |
1725 | 0 | if (hostname) { |
1726 | 0 | gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname, strlen(hostname)); |
1727 | 0 | debug_printf("SNI %s\n", hostname); |
1728 | 0 | } |
1729 | 0 | gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials); |
1730 | |
|
1731 | 0 | ctx->hostname = wget_strdup(hostname); |
1732 | 0 | ctx->port = tcp->remote_port; |
1733 | |
|
1734 | 0 | #ifdef WITH_OCSP |
1735 | | // If we know the cert chain for the hostname being valid at the moment, |
1736 | | // we don't ask for OCSP stapling to avoid unneeded IP traffic. |
1737 | | // In the unlikely case that the server's certificate chain changed right now, |
1738 | | // we fallback to OCSP responder request later (if enabled). |
1739 | 0 | if (hostname) { |
1740 | 0 | if (!(ctx->valid = wget_ocsp_hostname_is_valid(config.ocsp_host_cache, hostname))) { |
1741 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030103 |
1742 | 0 | if ((rc = gnutls_ocsp_status_request_enable_client(session, NULL, 0, NULL)) == GNUTLS_E_SUCCESS) { |
1743 | 0 | debug_printf("OCSP stapling requested for %s\n", hostname); |
1744 | 0 | ctx->ocsp_stapling = 1; |
1745 | 0 | } else |
1746 | 0 | error_printf("GnuTLS: %s\n", gnutls_strerror(rc)); // no translation |
1747 | 0 | #endif |
1748 | 0 | } |
1749 | 0 | } |
1750 | | #else |
1751 | | if (config.ocsp || config.ocsp_stapling) |
1752 | | error_printf(_("WARNING: OCSP is not available in this version of GnuTLS.\n")); |
1753 | | #endif |
1754 | |
|
1755 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030200 |
1756 | 0 | if (config.alpn) { |
1757 | 0 | unsigned nprot; |
1758 | 0 | const char *e, *s; |
1759 | |
|
1760 | 0 | for (nprot = 0, s = e = config.alpn; *e; s = e + 1) |
1761 | 0 | if ((e = strchrnul(s, ',')) != s) |
1762 | 0 | nprot++; |
1763 | |
|
1764 | 0 | if (nprot) { |
1765 | 0 | gnutls_datum_t data[16]; |
1766 | |
|
1767 | 0 | for (nprot = 0, s = e = config.alpn; *e && nprot < countof(data); s = e + 1) { |
1768 | 0 | if ((e = strchrnul(s, ',')) != s) { |
1769 | 0 | data[nprot].data = (unsigned char *) s; |
1770 | 0 | data[nprot].size = (unsigned) (e - s); |
1771 | 0 | debug_printf("ALPN offering %.*s\n", (int) data[nprot].size, data[nprot].data); |
1772 | 0 | nprot++; |
1773 | 0 | } |
1774 | 0 | } |
1775 | |
|
1776 | 0 | if ((rc = gnutls_alpn_set_protocols(session, data, nprot, 0))) |
1777 | 0 | debug_printf("GnuTLS: Set ALPN: %s\n", gnutls_strerror(rc)); |
1778 | 0 | } |
1779 | 0 | } |
1780 | 0 | #endif |
1781 | |
|
1782 | 0 | tcp->ssl_session = session; |
1783 | 0 | gnutls_session_set_ptr(session, ctx); |
1784 | |
|
1785 | 0 | #ifdef MSG_FASTOPEN |
1786 | 0 | if ((rc = wget_tcp_get_tcp_fastopen(tcp))) { |
1787 | 0 | if (tls_stats_callback) |
1788 | 0 | stats.tfo = (char)rc; |
1789 | | |
1790 | | // prepare for TCP FASTOPEN... sendmsg() instead of connect/write on first write |
1791 | 0 | gnutls_transport_set_vec_push_function(session, (ssize_t (*)(gnutls_transport_ptr_t, const giovec_t *iov, int iovcnt)) ssl_writev); |
1792 | 0 | gnutls_transport_set_ptr(session, tcp); |
1793 | 0 | } else { |
1794 | 0 | #endif |
1795 | |
|
1796 | | #ifdef _WIN32 |
1797 | | gnutls_transport_set_push_function(session, (gnutls_push_func) win32_send); |
1798 | | gnutls_transport_set_pull_function(session, (gnutls_pull_func) win32_recv); |
1799 | | #endif |
1800 | |
|
1801 | | #ifdef HAVE_GNUTLS_TRANSPORT_GET_INT |
1802 | | // since GnuTLS 3.1.9, avoid warnings about illegal pointer conversion |
1803 | | gnutls_transport_set_int(session, sockfd); |
1804 | | #else |
1805 | 0 | gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(ptrdiff_t)sockfd); |
1806 | 0 | #endif |
1807 | |
|
1808 | 0 | #ifdef MSG_FASTOPEN |
1809 | 0 | } |
1810 | 0 | #endif |
1811 | |
|
1812 | 0 | { |
1813 | 0 | void *data; |
1814 | 0 | size_t size; |
1815 | |
|
1816 | 0 | if (wget_tls_session_get(config.tls_session_cache, ctx->hostname, &data, &size) == 0) { |
1817 | 0 | debug_printf("found cached session data for %s\n", ctx->hostname); |
1818 | 0 | if ((rc = gnutls_session_set_data(session, data, size)) != GNUTLS_E_SUCCESS) |
1819 | 0 | error_printf(_("GnuTLS: Failed to set session data: %s\n"), gnutls_strerror(rc)); |
1820 | 0 | xfree(data); |
1821 | 0 | } |
1822 | 0 | } |
1823 | |
|
1824 | 0 | if (tls_stats_callback) |
1825 | 0 | before_millisecs = wget_get_timemillis(); |
1826 | |
|
1827 | 0 | ret = do_handshake(session, sockfd, connect_timeout); |
1828 | |
|
1829 | 0 | if (tls_stats_callback) { |
1830 | 0 | long long after_millisecs = wget_get_timemillis(); |
1831 | 0 | stats.tls_secs = after_millisecs - before_millisecs; |
1832 | 0 | stats.tls_con = 1; |
1833 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030500 |
1834 | 0 | stats.false_start = (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_FALSE_START) != 0; |
1835 | 0 | #endif |
1836 | 0 | } |
1837 | |
|
1838 | 0 | #if GNUTLS_VERSION_NUMBER >= 0x030200 |
1839 | 0 | if (config.alpn) { |
1840 | 0 | gnutls_datum_t protocol; |
1841 | 0 | if ((rc = gnutls_alpn_get_selected_protocol(session, &protocol))) { |
1842 | 0 | debug_printf("GnuTLS: Get ALPN: %s\n", gnutls_strerror(rc)); |
1843 | 0 | if (!strstr(config.alpn,"http/1.1")) |
1844 | 0 | ret = WGET_E_CONNECT; |
1845 | 0 | } else { |
1846 | 0 | debug_printf("ALPN: Server accepted protocol '%.*s'\n", (int) protocol.size, protocol.data); |
1847 | 0 | if (tls_stats_callback) |
1848 | 0 | stats.alpn_protocol = wget_strmemdup(protocol.data, protocol.size); |
1849 | |
|
1850 | 0 | if (!memcmp(protocol.data, "h2", 2)) { |
1851 | 0 | tcp->protocol = WGET_PROTOCOL_HTTP_2_0; |
1852 | 0 | if (tls_stats_callback) |
1853 | 0 | stats.http_protocol = WGET_PROTOCOL_HTTP_2_0; |
1854 | 0 | } |
1855 | 0 | } |
1856 | 0 | } |
1857 | 0 | #endif |
1858 | |
|
1859 | 0 | if (config.print_info) |
1860 | 0 | print_info(session); |
1861 | |
|
1862 | 0 | if (ret == WGET_E_SUCCESS) { |
1863 | 0 | int resumed = gnutls_session_is_resumed(session); |
1864 | |
|
1865 | 0 | if (tls_stats_callback) { |
1866 | 0 | stats.resumed = resumed; |
1867 | 0 | stats.version = gnutls_protocol_get_version(session); |
1868 | 0 | gnutls_certificate_get_peers(session, (unsigned int *)&(stats.cert_chain_size)); |
1869 | 0 | } |
1870 | |
|
1871 | 0 | debug_printf("Handshake completed%s\n", resumed ? " (resumed session)" : ""); |
1872 | |
|
1873 | 0 | if (!resumed && config.tls_session_cache) { |
1874 | 0 | if (tcp->tls_false_start) { |
1875 | 0 | ctx->delayed_session_data = 1; |
1876 | 0 | } else { |
1877 | 0 | gnutls_datum_t session_data; |
1878 | |
|
1879 | 0 | if ((rc = gnutls_session_get_data2(session, &session_data)) == GNUTLS_E_SUCCESS) { |
1880 | 0 | wget_tls_session_db_add(config.tls_session_cache, |
1881 | 0 | wget_tls_session_new(ctx->hostname, 18 * 3600, session_data.data, session_data.size)); // 18h valid |
1882 | 0 | xfree(session_data.data); |
1883 | 0 | } else |
1884 | 0 | debug_printf("Failed to get session data: %s", gnutls_strerror(rc)); |
1885 | 0 | } |
1886 | 0 | } |
1887 | 0 | } |
1888 | |
|
1889 | 0 | if (tls_stats_callback) { |
1890 | 0 | stats.hostname = hostname; |
1891 | 0 | tls_stats_callback(&stats, tls_stats_ctx); |
1892 | 0 | xfree(stats.alpn_protocol); |
1893 | 0 | } |
1894 | |
|
1895 | 0 | tcp->hpkp = ctx->stats_hpkp; |
1896 | |
|
1897 | 0 | if (ret != WGET_E_SUCCESS) { |
1898 | 0 | if (ret == WGET_E_TIMEOUT) |
1899 | 0 | debug_printf("Handshake timed out\n"); |
1900 | 0 | xfree(ctx->hostname); |
1901 | 0 | xfree(ctx); |
1902 | 0 | gnutls_deinit(session); |
1903 | 0 | tcp->ssl_session = NULL; |
1904 | 0 | } |
1905 | |
|
1906 | 0 | return ret; |
1907 | 0 | } |
1908 | | |
1909 | | /** |
1910 | | * \param[in] session The SSL/TLS session (a pointer to it), which is located at the `ssl_session` field |
1911 | | * of the TCP connection (see wget_ssl_open()). |
1912 | | * |
1913 | | * Close an active SSL/TLS tunnel, which was opened with wget_ssl_open(). |
1914 | | * |
1915 | | * The underlying TCP connection is kept open. |
1916 | | */ |
1917 | | void wget_ssl_close(void **session) |
1918 | 3.66k | { |
1919 | 3.66k | if (session && *session) { |
1920 | 0 | gnutls_session_t s = *session; |
1921 | 0 | struct session_context *ctx = gnutls_session_get_ptr(s); |
1922 | 0 | int ret; |
1923 | |
|
1924 | 0 | do |
1925 | 0 | ret = gnutls_bye(s, GNUTLS_SHUT_WR); |
1926 | 0 | while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); |
1927 | |
|
1928 | 0 | if (ret < 0) |
1929 | 0 | debug_printf("TLS shutdown failed: %s\n", gnutls_strerror(ret)); |
1930 | |
|
1931 | 0 | gnutls_deinit(s); |
1932 | 0 | *session = NULL; |
1933 | |
|
1934 | 0 | xfree(ctx->hostname); |
1935 | 0 | xfree(ctx); |
1936 | 0 | } |
1937 | 3.66k | } |
1938 | | |
1939 | | /** |
1940 | | * \param[in] session An opaque pointer to the SSL/TLS session (obtained with wget_ssl_open() or wget_ssl_server_open()) |
1941 | | * \param[in] buf Destination buffer where the read data will be placed |
1942 | | * \param[in] count Length of the buffer \p buf |
1943 | | * \param[in] timeout The amount of time to wait until data becomes available (in milliseconds) |
1944 | | * \return The number of bytes read, or a negative value on error. |
1945 | | * |
1946 | | * Read data from the SSL/TLS tunnel. |
1947 | | * |
1948 | | * This function will read at most \p count bytes, which will be stored |
1949 | | * in the buffer \p buf. |
1950 | | * |
1951 | | * The \p timeout parameter tells how long to wait until some data becomes |
1952 | | * available to read. A \p timeout value of zero causes this function to return |
1953 | | * immediately, whereas a negative value will cause it to wait indefinitely. |
1954 | | * This function returns the number of bytes read, which may be zero if the timeout elapses |
1955 | | * without any data having become available. |
1956 | | * |
1957 | | * If a rehandshake is needed, this function does it automatically and tries |
1958 | | * to read again. |
1959 | | */ |
1960 | | ssize_t wget_ssl_read_timeout(void *session, char *buf, size_t count, int timeout) |
1961 | 0 | { |
1962 | | #ifdef HAVE_GNUTLS_TRANSPORT_GET_INT |
1963 | | // since GnuTLS 3.1.9, avoid warnings about illegal pointer conversion |
1964 | | int sockfd = gnutls_transport_get_int(session); |
1965 | | #else |
1966 | 0 | int sockfd = (int)(ptrdiff_t)gnutls_transport_get_ptr(session); |
1967 | 0 | #endif |
1968 | | |
1969 | | // #if GNUTLS_VERSION_NUMBER >= 0x030107 |
1970 | | #if 0 |
1971 | | // GnuTLS <= 3.4.5 becomes slow with large timeouts (see loop in gnutls_system_recv_timeout()). |
1972 | | // A fix is proposed for 3.5.x, as well as a value for indefinite timeouts (-1). |
1973 | | ssize_t nbytes; |
1974 | | |
1975 | | gnutls_record_set_timeout(session, timeout); |
1976 | | |
1977 | | for (;;) { |
1978 | | if ((nbytes = gnutls_record_recv(session, buf, count)) >= 0) |
1979 | | return nbytes; |
1980 | | |
1981 | | if (nbytes == GNUTLS_E_REHANDSHAKE) { |
1982 | | debug_printf("*** REHANDSHAKE while reading\n"); |
1983 | | if ((nbytes = do_handshake(session, sockfd, timeout)) == 0) |
1984 | | continue; /* restart reading */ |
1985 | | } |
1986 | | |
1987 | | if (nbytes == GNUTLS_E_AGAIN) |
1988 | | return 0; // indicate timeout |
1989 | | |
1990 | | return -1; |
1991 | | } |
1992 | | |
1993 | | return -1; |
1994 | | #else |
1995 | 0 | ssize_t nbytes; |
1996 | |
|
1997 | 0 | for (;;) { |
1998 | 0 | int rc; |
1999 | |
|
2000 | 0 | if (gnutls_record_check_pending(session) <= 0 && (rc = wget_ready_2_read(sockfd, timeout)) <= 0) |
2001 | 0 | return rc; |
2002 | | |
2003 | 0 | nbytes = gnutls_record_recv(session, buf, count); |
2004 | | |
2005 | | // If False Start + Session Resumption are enabled, we get the session data after the first read() |
2006 | 0 | struct session_context *ctx = gnutls_session_get_ptr(session); |
2007 | 0 | if (ctx && ctx->delayed_session_data) { |
2008 | 0 | gnutls_datum_t session_data; |
2009 | |
|
2010 | 0 | if ((rc = gnutls_session_get_data2(session, &session_data)) == GNUTLS_E_SUCCESS) { |
2011 | 0 | debug_printf("Got delayed session data\n"); |
2012 | 0 | ctx->delayed_session_data = 0; |
2013 | 0 | wget_tls_session_db_add(config.tls_session_cache, |
2014 | 0 | wget_tls_session_new(ctx->hostname, 18 * 3600, session_data.data, session_data.size)); // 18h valid |
2015 | 0 | xfree(session_data.data); |
2016 | 0 | } else |
2017 | 0 | debug_printf("No delayed session data%s\n", gnutls_strerror(rc)); |
2018 | 0 | } |
2019 | |
|
2020 | 0 | if (nbytes == GNUTLS_E_REHANDSHAKE) { |
2021 | 0 | debug_printf("*** REHANDSHAKE while reading\n"); |
2022 | 0 | if ((nbytes = do_handshake(session, sockfd, timeout)) == 0) |
2023 | 0 | nbytes = GNUTLS_E_AGAIN; /* restart reading */ |
2024 | 0 | } |
2025 | 0 | if (nbytes >= 0 || nbytes != GNUTLS_E_AGAIN) |
2026 | 0 | break; |
2027 | 0 | } |
2028 | | |
2029 | 0 | return nbytes < -1 ? -1 : nbytes; |
2030 | 0 | #endif |
2031 | 0 | } |
2032 | | |
2033 | | /** |
2034 | | * \param[in] session An opaque pointer to the SSL/TLS session (obtained with wget_ssl_open() or wget_ssl_server_open()) |
2035 | | * \param[in] buf Buffer with the data to be sent |
2036 | | * \param[in] count Length of the buffer \p buf |
2037 | | * \param[in] timeout The amount of time to wait until data can be sent to the wire (in milliseconds) |
2038 | | * \return The number of bytes written, or a negative value on error. |
2039 | | * |
2040 | | * Send data through the SSL/TLS tunnel. |
2041 | | * |
2042 | | * This function will write \p count bytes from \p buf. |
2043 | | * |
2044 | | * The \p timeout parameter tells how long to wait until data can be finally sent |
2045 | | * over the SSL/TLS tunnel. A \p timeout value of zero causes this function to return |
2046 | | * immediately, whereas a negative value will cause it to wait indefinitely. |
2047 | | * This function returns the number of bytes sent, which may be zero if the timeout elapses |
2048 | | * before any data could be sent. |
2049 | | * |
2050 | | * If a rehandshake is needed, this function does it automatically and tries |
2051 | | * to write again. |
2052 | | */ |
2053 | | ssize_t wget_ssl_write_timeout(void *session, const char *buf, size_t count, int timeout) |
2054 | 0 | { |
2055 | | #ifdef HAVE_GNUTLS_TRANSPORT_GET_INT |
2056 | | // since GnuTLS 3.1.9, avoid warnings about illegal pointer conversion |
2057 | | int sockfd = gnutls_transport_get_int(session); |
2058 | | #else |
2059 | 0 | int sockfd = (int)(ptrdiff_t)gnutls_transport_get_ptr(session); |
2060 | 0 | #endif |
2061 | |
|
2062 | 0 | for (;;) { |
2063 | 0 | ssize_t nbytes; |
2064 | 0 | int rc; |
2065 | |
|
2066 | 0 | if ((rc = wget_ready_2_write(sockfd, timeout)) <= 0) |
2067 | 0 | return rc; |
2068 | | |
2069 | 0 | if ((nbytes = gnutls_record_send(session, buf, count)) >= 0) |
2070 | 0 | return nbytes; |
2071 | | |
2072 | 0 | if (nbytes == GNUTLS_E_REHANDSHAKE) { |
2073 | 0 | debug_printf("*** REHANDSHAKE while writing\n"); |
2074 | 0 | if ((nbytes = do_handshake(session, sockfd, timeout)) == 0) |
2075 | 0 | continue; /* restart writing */ |
2076 | 0 | } |
2077 | 0 | if (nbytes == GNUTLS_E_AGAIN) |
2078 | 0 | return 0; // indicate timeout |
2079 | | |
2080 | 0 | return -1; |
2081 | 0 | } |
2082 | 0 | } |
2083 | | |
2084 | | /** |
2085 | | * \param[in] fn A `wget_ssl_stats_callback_tls_t` callback function to receive TLS statistics data |
2086 | | * \param[in] ctx Context data given to \p fn |
2087 | | * |
2088 | | * Set callback function to be called when TLS statistics are available |
2089 | | */ |
2090 | | void wget_ssl_set_stats_callback_tls(wget_tls_stats_callback *fn, void *ctx) |
2091 | 25 | { |
2092 | 25 | tls_stats_callback = fn; |
2093 | 25 | tls_stats_ctx = ctx; |
2094 | 25 | } |
2095 | | |
2096 | | /** |
2097 | | * \param[in] fn A `wget_ssl_stats_callback_ocsp_t` callback function to receive OCSP statistics data |
2098 | | * \param[in] ctx Context data given to \p fn |
2099 | | * |
2100 | | * Set callback function to be called when OCSP statistics are available |
2101 | | */ |
2102 | | void wget_ssl_set_stats_callback_ocsp(wget_ocsp_stats_callback *fn, void *ctx) |
2103 | 7 | { |
2104 | 7 | ocsp_stats_callback = fn; |
2105 | 7 | ocsp_stats_ctx = ctx; |
2106 | 7 | } |
2107 | | |
2108 | | /** @} */ |