NeoMutt  2024-04-25-89-g194907
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
gnutls.c File Reference

Handling of GnuTLS encryption. More...

#include "config.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "private.h"
#include "mutt/lib.h"
#include "config/lib.h"
#include "core/lib.h"
#include "lib.h"
#include "connaccount.h"
#include "connection.h"
#include "globals.h"
#include "muttlib.h"
#include "ssl.h"
+ Include dependency graph for gnutls.c:

Go to the source code of this file.

Data Structures

struct  TlsSockData
 TLS socket data -. More...
 

Macros

#define CERTERR_VALID   0
 
#define CERTERR_EXPIRED   (1 << 0)
 
#define CERTERR_NOTYETVALID   (1 << 1)
 
#define CERTERR_REVOKED   (1 << 2)
 
#define CERTERR_NOTTRUSTED   (1 << 3)
 
#define CERTERR_HOSTNAME   (1 << 4)
 
#define CERTERR_SIGNERNOTCA   (1 << 5)
 
#define CERTERR_INSECUREALG   (1 << 6)
 
#define CERTERR_OTHER   (1 << 7)
 
#define CERT_SEP   "-----BEGIN"
 

Functions

int gnutls_protocol_set_priority (gnutls_session_t session, const int *list)
 
static int tls_init (void)
 Set up Gnu TLS.
 
static int tls_verify_peers (gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
 Wrapper for gnutls_certificate_verify_peers()
 
static void tls_fingerprint (gnutls_digest_algorithm_t algo, struct Buffer *buf, const gnutls_datum_t *data)
 Create a fingerprint of a TLS Certificate.
 
static bool tls_check_stored_hostname (const gnutls_datum_t *cert, const char *hostname)
 Does the hostname match a stored certificate?
 
static int tls_compare_certificates (const gnutls_datum_t *peercert)
 Compare certificates against $certificate_file
 
static int tls_check_preauth (const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int chainidx, int *certerr, int *savedcert)
 Prepare a certificate for authentication.
 
static void add_cert (const char *title, gnutls_x509_crt_t cert, bool issuer, struct CertArray *carr)
 Look up certificate info and save it to a list.
 
static int tls_check_one_certificate (const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int idx, size_t len)
 Check a GnuTLS certificate.
 
static int tls_check_certificate (struct Connection *conn)
 Check a connection's certificate.
 
static void tls_get_client_cert (struct Connection *conn)
 Get the client certificate for a TLS connection.
 
static int tls_set_priority (struct TlsSockData *data)
 Set the priority of various protocols.
 
static int tls_negotiate (struct Connection *conn)
 Negotiate TLS connection.
 
static int tls_socket_poll (struct Connection *conn, time_t wait_secs)
 Check if any data is waiting on a socket - Implements Connection::poll() -.
 
static int tls_socket_close (struct Connection *conn)
 Close a TLS socket - Implements Connection::close() -.
 
static int tls_socket_open (struct Connection *conn)
 Open a TLS socket - Implements Connection::open() -.
 
static int tls_socket_read (struct Connection *conn, char *buf, size_t count)
 Read data from a TLS socket - Implements Connection::read() -.
 
static int tls_socket_write (struct Connection *conn, const char *buf, size_t count)
 Write data to a TLS socket - Implements Connection::write() -.
 
static int tls_starttls_close (struct Connection *conn)
 Close a TLS connection - Implements Connection::close() -.
 
int mutt_ssl_socket_setup (struct Connection *conn)
 Set up SSL socket mulitplexor.
 
int mutt_ssl_starttls (struct Connection *conn)
 Negotiate TLS over an already opened connection.
 

Variables

static int ProtocolPriority [] = { GNUTLS_TLS1_2, GNUTLS_TLS1_1, GNUTLS_TLS1, GNUTLS_SSL3, 0 }
 This array needs to be large enough to hold all the possible values support by NeoMutt.
 

Detailed Description

Handling of GnuTLS encryption.

Authors
  • Richard Russon
  • Pietro Cerutti

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file gnutls.c.

Macro Definition Documentation

◆ CERTERR_VALID

#define CERTERR_VALID   0

Definition at line 53 of file gnutls.c.

◆ CERTERR_EXPIRED

#define CERTERR_EXPIRED   (1 << 0)

Definition at line 54 of file gnutls.c.

◆ CERTERR_NOTYETVALID

#define CERTERR_NOTYETVALID   (1 << 1)

Definition at line 55 of file gnutls.c.

◆ CERTERR_REVOKED

#define CERTERR_REVOKED   (1 << 2)

Definition at line 56 of file gnutls.c.

◆ CERTERR_NOTTRUSTED

#define CERTERR_NOTTRUSTED   (1 << 3)

Definition at line 57 of file gnutls.c.

◆ CERTERR_HOSTNAME

#define CERTERR_HOSTNAME   (1 << 4)

Definition at line 58 of file gnutls.c.

◆ CERTERR_SIGNERNOTCA

#define CERTERR_SIGNERNOTCA   (1 << 5)

Definition at line 59 of file gnutls.c.

◆ CERTERR_INSECUREALG

#define CERTERR_INSECUREALG   (1 << 6)

Definition at line 60 of file gnutls.c.

◆ CERTERR_OTHER

#define CERTERR_OTHER   (1 << 7)

Definition at line 61 of file gnutls.c.

◆ CERT_SEP

#define CERT_SEP   "-----BEGIN"

Definition at line 64 of file gnutls.c.

Function Documentation

◆ gnutls_protocol_set_priority()

int gnutls_protocol_set_priority ( gnutls_session_t  session,
const int *  list 
)
+ Here is the caller graph for this function:

◆ tls_init()

static int tls_init ( void  )
static

Set up Gnu TLS.

Return values
0Success
-1Error

Definition at line 92 of file gnutls.c.

93{
94 static bool init_complete = false;
95 int err;
96
97 if (init_complete)
98 return 0;
99
100 err = gnutls_global_init();
101 if (err < 0)
102 {
103 mutt_error("gnutls_global_init: %s", gnutls_strerror(err));
104 return -1;
105 }
106
107 init_complete = true;
108 return 0;
109}
#define mutt_error(...)
Definition: logging2.h:92
+ Here is the caller graph for this function:

◆ tls_verify_peers()

static int tls_verify_peers ( gnutls_session_t  tlsstate,
gnutls_certificate_status_t *  certstat 
)
static

Wrapper for gnutls_certificate_verify_peers()

Parameters
tlsstateTLS state
certstatCertificate state, e.g. GNUTLS_CERT_INVALID
Return values
0Success If certstat was set. note: this does not mean success
>0Error

Wrapper with sanity-checking.

certstat is technically a bitwise-or of gnutls_certificate_status_t values.

Definition at line 122 of file gnutls.c.

123{
124 /* gnutls_certificate_verify_peers2() chains to
125 * gnutls_x509_trust_list_verify_crt2(). That function's documentation says:
126 *
127 * When a certificate chain of cert_list_size with more than one
128 * certificates is provided, the verification status will apply to
129 * the first certificate in the chain that failed
130 * verification. The verification process starts from the end of
131 * the chain(from CA to end certificate). The first certificate
132 * in the chain must be the end-certificate while the rest of the
133 * members may be sorted or not.
134 *
135 * This is why tls_check_certificate() loops from CA to host in that order,
136 * calling the menu, and recalling tls_verify_peers() for each approved
137 * cert in the chain.
138 */
139 int rc = gnutls_certificate_verify_peers2(tlsstate, certstat);
140
141 /* certstat was set */
142 if (rc == 0)
143 return 0;
144
145 if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND)
146 mutt_error(_("Unable to get certificate from peer"));
147 else
148 mutt_error(_("Certificate verification error (%s)"), gnutls_strerror(rc));
149
150 return rc;
151}
#define _(a)
Definition: message.h:28
+ Here is the caller graph for this function:

◆ tls_fingerprint()

static void tls_fingerprint ( gnutls_digest_algorithm_t  algo,
struct Buffer buf,
const gnutls_datum_t *  data 
)
static

Create a fingerprint of a TLS Certificate.

Parameters
algoFingerprint algorithm, e.g. GNUTLS_MAC_SHA256
bufBuffer for the fingerprint
dataCertificate

Definition at line 159 of file gnutls.c.

161{
162 unsigned char md[128] = { 0 };
163 size_t n = 64;
164
165 if (gnutls_fingerprint(algo, data, (char *) md, &n) < 0)
166 {
167 buf_strcpy(buf, _("[unable to calculate]"));
168 return;
169 }
170
171 for (size_t i = 0; i < n; i++)
172 {
173 buf_add_printf(buf, "%02X", md[i]);
174
175 // Put a space after a pair of bytes (except for the last one)
176 if (((i % 2) == 1) && (i < (n - 1)))
177 buf_addch(buf, ' ');
178 }
179}
int buf_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition: buffer.c:204
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:241
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_check_stored_hostname()

static bool tls_check_stored_hostname ( const gnutls_datum_t *  cert,
const char *  hostname 
)
static

Does the hostname match a stored certificate?

Parameters
certCertificate
hostnameHostname
Return values
trueHostname match found
falseError, or no match

Definition at line 188 of file gnutls.c.

189{
190 char *linestr = NULL;
191 size_t linestrsize = 0;
192
193 /* try checking against names stored in stored certs file */
194 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
195 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
196 if (!fp)
197 return false;
198
199 struct Buffer *buf = buf_pool_get();
200
201 tls_fingerprint(GNUTLS_DIG_MD5, buf, cert);
202 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
203 {
204 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
205 if (match)
206 {
207 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
208 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
209 linestr[mutt_regmatch_end(mhost)] = '\0';
210 linestr[mutt_regmatch_end(mhash)] = '\0';
211 if ((mutt_str_equal(linestr + mutt_regmatch_start(mhost), hostname)) &&
212 (mutt_str_equal(linestr + mutt_regmatch_start(mhash), buf_string(buf))))
213 {
214 FREE(&linestr);
215 mutt_file_fclose(&fp);
216 buf_pool_release(&buf);
217 return true;
218 }
219 }
220 }
221
222 mutt_file_fclose(&fp);
223 buf_pool_release(&buf);
224
225 /* not found a matching name */
226 return false;
227}
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:168
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition: file.c:808
#define mutt_file_fclose(FP)
Definition: file.h:149
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:148
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:40
static void tls_fingerprint(gnutls_digest_algorithm_t algo, struct Buffer *buf, const gnutls_datum_t *data)
Create a fingerprint of a TLS Certificate.
Definition: gnutls.c:159
#define FREE(x)
Definition: memory.h:45
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition: prex.c:296
@ PREX_GNUTLS_CERT_HOST_HASH
[#H foo.com A76D 954B EB79 1F49 5B3A 0A0E 0681 65B1]
Definition: prex.h:37
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH
#H foo.com [A76D ... 65B1]
Definition: prex.h:112
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST
#H [foo.com] A76D ... 65B1
Definition: prex.h:111
static regoff_t mutt_regmatch_end(const regmatch_t *match)
Return the end of a match.
Definition: regex3.h:66
static regoff_t mutt_regmatch_start(const regmatch_t *match)
Return the start of a match.
Definition: regex3.h:56
String manipulation buffer.
Definition: buffer.h:36
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_compare_certificates()

static int tls_compare_certificates ( const gnutls_datum_t *  peercert)
static

Compare certificates against $certificate_file

Parameters
peercertCertificate
Return values
1Certificate matches file
0Error, or no match

Definition at line 235 of file gnutls.c.

236{
237 gnutls_datum_t cert = { 0 };
238 unsigned char *ptr = NULL;
239 gnutls_datum_t b64_data = { 0 };
240 unsigned char *b64_data_data = NULL;
241 struct stat st = { 0 };
242
243 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
244 if (stat(c_certificate_file, &st) == -1)
245 return 0;
246
247 b64_data.size = st.st_size;
248 b64_data_data = mutt_mem_calloc(1, b64_data.size + 1);
249 b64_data.data = b64_data_data;
250
251 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
252 if (!fp)
253 return 0;
254
255 b64_data.size = fread(b64_data.data, 1, b64_data.size, fp);
256 b64_data.data[b64_data.size] = '\0';
257 mutt_file_fclose(&fp);
258
259 do
260 {
261 const int rc = gnutls_pem_base64_decode_alloc(NULL, &b64_data, &cert);
262 if (rc != 0)
263 {
264 FREE(&b64_data_data);
265 return 0;
266 }
267
268 /* find start of cert, skipping junk */
269 ptr = (unsigned char *) strstr((char *) b64_data.data, CERT_SEP);
270 if (!ptr)
271 {
272 gnutls_free(cert.data);
273 FREE(&b64_data_data);
274 return 0;
275 }
276 /* find start of next cert */
277 ptr = (unsigned char *) strstr((char *) ptr + 1, CERT_SEP);
278
279 b64_data.size = b64_data.size - (ptr - b64_data.data);
280 b64_data.data = ptr;
281
282 if (cert.size == peercert->size)
283 {
284 if (memcmp(cert.data, peercert->data, cert.size) == 0)
285 {
286 /* match found */
287 gnutls_free(cert.data);
288 FREE(&b64_data_data);
289 return 1;
290 }
291 }
292
293 gnutls_free(cert.data);
294 } while (ptr);
295
296 /* no match found */
297 FREE(&b64_data_data);
298 return 0;
299}
#define CERT_SEP
Definition: gnutls.c:64
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:51
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_check_preauth()

static int tls_check_preauth ( const gnutls_datum_t *  certdata,
gnutls_certificate_status_t  certstat,
const char *  hostname,
int  chainidx,
int *  certerr,
int *  savedcert 
)
static

Prepare a certificate for authentication.

Parameters
[in]certdataList of GnuTLS certificates
[in]certstatGnuTLS certificate status
[in]hostnameHostname
[in]chainidxIndex in the certificate chain
[out]certerrResult, e.g. CERTERR_VALID
[out]savedcert1 if certificate has been saved
Return values
0Success
-1Error

Definition at line 312 of file gnutls.c.

315{
316 gnutls_x509_crt_t cert;
317
318 *certerr = CERTERR_VALID;
319 *savedcert = 0;
320
321 if (gnutls_x509_crt_init(&cert) < 0)
322 {
323 mutt_error(_("Error initialising gnutls certificate data"));
324 return -1;
325 }
326
327 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
328 {
329 mutt_error(_("Error processing certificate data"));
330 gnutls_x509_crt_deinit(cert);
331 return -1;
332 }
333
334 /* Note: tls_negotiate() contains a call to
335 * gnutls_certificate_set_verify_flags() with a flag disabling
336 * GnuTLS checking of the dates. So certstat shouldn't have the
337 * GNUTLS_CERT_EXPIRED and GNUTLS_CERT_NOT_ACTIVATED bits set. */
338 const bool c_ssl_verify_dates = cs_subset_bool(NeoMutt->sub, "ssl_verify_dates");
339 if (c_ssl_verify_dates != MUTT_NO)
340 {
341 if (gnutls_x509_crt_get_expiration_time(cert) < mutt_date_now())
342 *certerr |= CERTERR_EXPIRED;
343 if (gnutls_x509_crt_get_activation_time(cert) > mutt_date_now())
344 *certerr |= CERTERR_NOTYETVALID;
345 }
346
347 const bool c_ssl_verify_host = cs_subset_bool(NeoMutt->sub, "ssl_verify_host");
348 if ((chainidx == 0) && (c_ssl_verify_host != MUTT_NO) &&
349 !gnutls_x509_crt_check_hostname(cert, hostname) &&
350 !tls_check_stored_hostname(certdata, hostname))
351 {
352 *certerr |= CERTERR_HOSTNAME;
353 }
354
355 if (certstat & GNUTLS_CERT_REVOKED)
356 {
357 *certerr |= CERTERR_REVOKED;
358 certstat ^= GNUTLS_CERT_REVOKED;
359 }
360
361 /* see whether certificate is in our cache (certificates file) */
362 if (tls_compare_certificates(certdata))
363 {
364 *savedcert = 1;
365
366 /* We check above for certs with bad dates or that are revoked.
367 * These must be accepted manually each time. Otherwise, we
368 * accept saved certificates as valid. */
369 if (*certerr == CERTERR_VALID)
370 {
371 gnutls_x509_crt_deinit(cert);
372 return 0;
373 }
374 }
375
376 if (certstat & GNUTLS_CERT_INVALID)
377 {
378 *certerr |= CERTERR_NOTTRUSTED;
379 certstat ^= GNUTLS_CERT_INVALID;
380 }
381
382 if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND)
383 {
384 /* NB: already cleared if cert in cache */
385 *certerr |= CERTERR_NOTTRUSTED;
386 certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
387 }
388
389 if (certstat & GNUTLS_CERT_SIGNER_NOT_CA)
390 {
391 /* NB: already cleared if cert in cache */
392 *certerr |= CERTERR_SIGNERNOTCA;
393 certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
394 }
395
396 if (certstat & GNUTLS_CERT_INSECURE_ALGORITHM)
397 {
398 /* NB: already cleared if cert in cache */
399 *certerr |= CERTERR_INSECUREALG;
400 certstat ^= GNUTLS_CERT_INSECURE_ALGORITHM;
401 }
402
403 /* we've been zeroing the interesting bits in certstat -
404 * don't return OK if there are any unhandled bits we don't
405 * understand */
406 if (certstat != 0)
407 *certerr |= CERTERR_OTHER;
408
409 gnutls_x509_crt_deinit(cert);
410
411 if (*certerr == CERTERR_VALID)
412 return 0;
413
414 return -1;
415}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
#define CERTERR_INSECUREALG
Definition: gnutls.c:60
#define CERTERR_VALID
Definition: gnutls.c:53
#define CERTERR_HOSTNAME
Definition: gnutls.c:58
#define CERTERR_EXPIRED
Definition: gnutls.c:54
static bool tls_check_stored_hostname(const gnutls_datum_t *cert, const char *hostname)
Does the hostname match a stored certificate?
Definition: gnutls.c:188
#define CERTERR_NOTTRUSTED
Definition: gnutls.c:57
#define CERTERR_NOTYETVALID
Definition: gnutls.c:55
static int tls_compare_certificates(const gnutls_datum_t *peercert)
Compare certificates against $certificate_file
Definition: gnutls.c:235
#define CERTERR_OTHER
Definition: gnutls.c:61
#define CERTERR_SIGNERNOTCA
Definition: gnutls.c:59
#define CERTERR_REVOKED
Definition: gnutls.c:56
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
@ MUTT_NO
User answered 'No', or assume 'No'.
Definition: quad.h:38
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ add_cert()

static void add_cert ( const char *  title,
gnutls_x509_crt_t  cert,
bool  issuer,
struct CertArray *  carr 
)
static

Look up certificate info and save it to a list.

Parameters
titleTitle for this block of certificate info
certCertificate
issuerIf true, look up the issuer rather than owner details
carrArray to save info to

Definition at line 424 of file gnutls.c.

426{
427 static const char *part[] = {
428 GNUTLS_OID_X520_COMMON_NAME, // CN
429 GNUTLS_OID_PKCS9_EMAIL, // Email
430 GNUTLS_OID_X520_ORGANIZATION_NAME, // O
431 GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, // OU
432 GNUTLS_OID_X520_LOCALITY_NAME, // L
433 GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, // ST
434 GNUTLS_OID_X520_COUNTRY_NAME, // C
435 };
436
437 char buf[128] = { 0 };
438 int rc;
439
440 // Allocate formatted strings and let the array take ownership
441 ARRAY_ADD(carr, mutt_str_dup(title));
442
443 for (size_t i = 0; i < mutt_array_size(part); i++)
444 {
445 size_t buflen = sizeof(buf);
446 if (issuer)
447 rc = gnutls_x509_crt_get_issuer_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
448 else
449 rc = gnutls_x509_crt_get_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
450 if (rc != 0)
451 continue;
452
453 char *line = NULL;
454 mutt_str_asprintf(&line, " %s", buf);
455 ARRAY_ADD(carr, line);
456 }
457}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:156
#define mutt_array_size(x)
Definition: memory.h:38
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:803
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_check_one_certificate()

static int tls_check_one_certificate ( const gnutls_datum_t *  certdata,
gnutls_certificate_status_t  certstat,
const char *  hostname,
int  idx,
size_t  len 
)
static

Check a GnuTLS certificate.

Parameters
certdataList of GnuTLS certificates
certstatGnuTLS certificate status
hostnameHostname
idxIndex into certificate list
lenLength of certificate list
Return values
1Success
0Failure

Definition at line 469 of file gnutls.c.

472{
473 struct CertArray carr = ARRAY_HEAD_INITIALIZER;
474 int certerr, savedcert;
475 gnutls_x509_crt_t cert;
476 struct Buffer *fpbuf = NULL;
477 time_t t;
478 char datestr[30] = { 0 };
479 char title[256] = { 0 };
480 gnutls_datum_t pemdata = { 0 };
481
482 if (tls_check_preauth(certdata, certstat, hostname, idx, &certerr, &savedcert) == 0)
483 return 1;
484
485 if (OptNoCurses)
486 {
487 mutt_debug(LL_DEBUG1, "unable to prompt for certificate in batch mode\n");
488 mutt_error(_("Untrusted server certificate"));
489 return 0;
490 }
491
492 /* interactive check from user */
493 if (gnutls_x509_crt_init(&cert) < 0)
494 {
495 mutt_error(_("Error initialising gnutls certificate data"));
496 return 0;
497 }
498
499 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
500 {
501 mutt_error(_("Error processing certificate data"));
502 gnutls_x509_crt_deinit(cert);
503 return 0;
504 }
505
506 add_cert(_("This certificate belongs to:"), cert, false, &carr);
507 ARRAY_ADD(&carr, NULL);
508 add_cert(_("This certificate was issued by:"), cert, true, &carr);
509
510 ARRAY_ADD(&carr, NULL);
511 ARRAY_ADD(&carr, mutt_str_dup(_("This certificate is valid")));
512
513 char *line = NULL;
514 t = gnutls_x509_crt_get_activation_time(cert);
515 mutt_date_make_tls(datestr, sizeof(datestr), t);
516 mutt_str_asprintf(&line, _(" from %s"), datestr);
517 ARRAY_ADD(&carr, line);
518
519 t = gnutls_x509_crt_get_expiration_time(cert);
520 mutt_date_make_tls(datestr, sizeof(datestr), t);
521 mutt_str_asprintf(&line, _(" to %s"), datestr);
522 ARRAY_ADD(&carr, line);
523 ARRAY_ADD(&carr, NULL);
524
525 fpbuf = buf_pool_get();
526 tls_fingerprint(GNUTLS_DIG_SHA, fpbuf, certdata);
527 mutt_str_asprintf(&line, _("SHA1 Fingerprint: %s"), buf_string(fpbuf));
528 ARRAY_ADD(&carr, line);
529
530 tls_fingerprint(GNUTLS_DIG_SHA256, fpbuf, certdata);
531 fpbuf->data[39] = '\0'; // Divide into two lines of output
532 mutt_str_asprintf(&line, "%s%s", _("SHA256 Fingerprint: "), buf_string(fpbuf));
533 ARRAY_ADD(&carr, line);
534 mutt_str_asprintf(&line, "%*s%s", (int) mutt_str_len(_("SHA256 Fingerprint: ")),
535 "", fpbuf->data + 40);
536 ARRAY_ADD(&carr, line);
537
538 if (certerr)
539 ARRAY_ADD(&carr, NULL);
540
541 if (certerr & CERTERR_NOTYETVALID)
542 {
543 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate is not yet valid")));
544 }
545 if (certerr & CERTERR_EXPIRED)
546 {
547 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has expired")));
548 }
549 if (certerr & CERTERR_REVOKED)
550 {
551 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has been revoked")));
552 }
553 if (certerr & CERTERR_HOSTNAME)
554 {
555 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server hostname does not match certificate")));
556 }
557 if (certerr & CERTERR_SIGNERNOTCA)
558 {
559 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Signer of server certificate is not a CA")));
560 }
561 if (certerr & CERTERR_INSECUREALG)
562 {
563 ARRAY_ADD(&carr, mutt_str_dup(_("Warning: Server certificate was signed using an insecure algorithm")));
564 }
565
566 snprintf(title, sizeof(title),
567 _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len);
568
569 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
570 const bool allow_always = (c_certificate_file && !savedcert &&
572 int rc = dlg_certificate(title, &carr, allow_always, false);
573 if (rc == 3) // Accept always
574 {
575 bool saved = false;
576 FILE *fp = mutt_file_fopen(c_certificate_file, "a");
577 if (fp)
578 {
579 if (certerr & CERTERR_HOSTNAME) // Save hostname if necessary
580 {
581 tls_fingerprint(GNUTLS_DIG_MD5, fpbuf, certdata);
582 fprintf(fp, "#H %s %s\n", hostname, buf_string(fpbuf));
583 saved = true;
584 }
585 if (certerr ^ CERTERR_HOSTNAME) // Save the cert for all other errors
586 {
587 int rc2 = gnutls_pem_base64_encode_alloc("CERTIFICATE", certdata, &pemdata);
588 if (rc2 == 0)
589 {
590 if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1)
591 {
592 saved = true;
593 }
594 gnutls_free(pemdata.data);
595 }
596 }
597 mutt_file_fclose(&fp);
598 }
599 if (saved)
600 mutt_message(_("Certificate saved"));
601 else
602 mutt_error(_("Warning: Couldn't save certificate"));
603 }
604
605 buf_pool_release(&fpbuf);
606 cert_array_clear(&carr);
607 ARRAY_FREE(&carr);
608 gnutls_x509_crt_deinit(cert);
609 return (rc > 1);
610}
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:204
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition: array.h:58
void cert_array_clear(struct CertArray *carr)
Free all memory of a CertArray.
bool OptNoCurses
(pseudo) when sending in batch mode
Definition: globals.c:72
static int tls_check_preauth(const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int chainidx, int *certerr, int *savedcert)
Prepare a certificate for authentication.
Definition: gnutls.c:312
static void add_cert(const char *title, gnutls_x509_crt_t cert, bool issuer, struct CertArray *carr)
Look up certificate info and save it to a list.
Definition: gnutls.c:424
int dlg_certificate(const char *title, struct CertArray *carr, bool allow_always, bool allow_skip)
Ask the user to validate the certificate -.
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
Format date in TLS certificate verification style.
Definition: date.c:837
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:496
char * data
Pointer to data.
Definition: buffer.h:37
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_check_certificate()

static int tls_check_certificate ( struct Connection conn)
static

Check a connection's certificate.

Parameters
connConnection to a server
Return values
1Certificate is valid
0Error, or certificate is invalid

Definition at line 618 of file gnutls.c.

619{
620 struct TlsSockData *data = conn->sockdata;
621 gnutls_session_t session = data->session;
622 const gnutls_datum_t *cert_list = NULL;
623 unsigned int cert_list_size = 0;
624 gnutls_certificate_status_t certstat;
625 int certerr, savedcert, rc = 0;
626 int max_preauth_pass = -1;
627
628 /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
629 * which verifies the auth_type is GNUTLS_CRD_CERTIFICATE
630 * and that get_certificate_type() for the server is GNUTLS_CRT_X509.
631 * If it returns 0, certstat will be set with failure codes for the first
632 * cert in the chain(from CA to host) with an error.
633 */
634 if (tls_verify_peers(session, &certstat) != 0)
635 return 0;
636
637 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
638 if (!cert_list)
639 {
640 mutt_error(_("Unable to get certificate from peer"));
641 return 0;
642 }
643
644 /* tls_verify_peers doesn't check hostname or expiration, so walk
645 * from most specific to least checking these. If we see a saved certificate,
646 * its status short-circuits the remaining checks. */
647 int preauthrc = 0;
648 for (int i = 0; i < cert_list_size; i++)
649 {
650 rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
651 &certerr, &savedcert);
652 preauthrc += rc;
653 if (!preauthrc)
654 max_preauth_pass = i;
655
656 if (savedcert)
657 {
658 if (preauthrc == 0)
659 return 1;
660 break;
661 }
662 }
663
664 /* then check interactively, starting from chain root */
665 for (int i = cert_list_size - 1; i >= 0; i--)
666 {
667 rc = tls_check_one_certificate(&cert_list[i], certstat, conn->account.host,
668 i, cert_list_size);
669
670 /* Stop checking if the menu cert is aborted or rejected. */
671 if (rc == 0)
672 break;
673
674 /* add signers to trust set, then reverify */
675 if (i)
676 {
677 int rcsettrust = gnutls_certificate_set_x509_trust_mem(data->xcred, &cert_list[i],
678 GNUTLS_X509_FMT_DER);
679 if (rcsettrust != 1)
680 mutt_debug(LL_DEBUG1, "error trusting certificate %d: %d\n", i, rcsettrust);
681
682 if (tls_verify_peers(session, &certstat) != 0)
683 return 0;
684
685 /* If the cert chain now verifies, and all lower certs already
686 * passed preauth, we are done. */
687 if (!certstat && (max_preauth_pass >= (i - 1)))
688 return 1;
689 }
690 }
691
692 return rc;
693}
static int tls_verify_peers(gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
Wrapper for gnutls_certificate_verify_peers()
Definition: gnutls.c:122
static int tls_check_one_certificate(const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int idx, size_t len)
Check a GnuTLS certificate.
Definition: gnutls.c:469
char host[128]
Server to login to.
Definition: connaccount.h:54
void * sockdata
Backend-specific socket data.
Definition: connection.h:55
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:49
TLS socket data -.
Definition: gnutls.c:82
gnutls_certificate_credentials_t xcred
Definition: gnutls.c:84
gnutls_session_t session
Definition: gnutls.c:83
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_get_client_cert()

static void tls_get_client_cert ( struct Connection conn)
static

Get the client certificate for a TLS connection.

Parameters
connConnection to a server
Note
This function grabs the CN out of the client cert but appears to do nothing with it.

Definition at line 702 of file gnutls.c.

703{
704 struct TlsSockData *data = conn->sockdata;
705 gnutls_x509_crt_t clientcrt;
706 char *cn = NULL;
707 size_t cnlen = 0;
708 int rc;
709
710 /* get our cert CN if we have one */
711 const gnutls_datum_t *crtdata = gnutls_certificate_get_ours(data->session);
712 if (!crtdata)
713 return;
714
715 if (gnutls_x509_crt_init(&clientcrt) < 0)
716 {
717 mutt_debug(LL_DEBUG1, "Failed to init gnutls crt\n");
718 return;
719 }
720
721 if (gnutls_x509_crt_import(clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0)
722 {
723 mutt_debug(LL_DEBUG1, "Failed to import gnutls client crt\n");
724 goto err;
725 }
726
727 /* get length of CN, then grab it. */
728 rc = gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
729 0, NULL, &cnlen);
730 if (((rc >= 0) || (rc == GNUTLS_E_SHORT_MEMORY_BUFFER)) && (cnlen > 0))
731 {
732 cn = mutt_mem_calloc(1, cnlen);
733 if (gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
734 0, cn, &cnlen) < 0)
735 {
736 goto err;
737 }
738 mutt_debug(LL_DEBUG2, "client certificate CN: %s\n", cn);
739 }
740
741err:
742 FREE(&cn);
743 gnutls_x509_crt_deinit(clientcrt);
744}
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_set_priority()

static int tls_set_priority ( struct TlsSockData data)
static

Set the priority of various protocols.

Parameters
dataTLS socket data
Return values
0Success
-1Error

Definition at line 825 of file gnutls.c.

826{
827 size_t nproto = 0; /* number of tls/ssl protocols */
828
829 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
830 if (c_ssl_use_tlsv1_2)
831 ProtocolPriority[nproto++] = GNUTLS_TLS1_2;
832 const bool c_ssl_use_tlsv1_1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_1");
833 if (c_ssl_use_tlsv1_1)
834 ProtocolPriority[nproto++] = GNUTLS_TLS1_1;
835 const bool c_ssl_use_tlsv1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1");
836 if (c_ssl_use_tlsv1)
837 ProtocolPriority[nproto++] = GNUTLS_TLS1;
838 const bool c_ssl_use_sslv3 = cs_subset_bool(NeoMutt->sub, "ssl_use_sslv3");
839 if (c_ssl_use_sslv3)
840 ProtocolPriority[nproto++] = GNUTLS_SSL3;
841 ProtocolPriority[nproto] = 0;
842
843 if (nproto == 0)
844 {
845 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
846 return -1;
847 }
848
849 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
850 if (c_ssl_ciphers)
851 {
852 mutt_error(_("Explicit ciphersuite selection via $ssl_ciphers not supported"));
853 }
854
855 /* We use default priorities (see gnutls documentation),
856 * except for protocol version */
857 gnutls_set_default_priority(data->session);
859 return 0;
860}
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
static int ProtocolPriority[]
This array needs to be large enough to hold all the possible values support by NeoMutt.
Definition: gnutls.c:75
int gnutls_protocol_set_priority(gnutls_session_t session, const int *list)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tls_negotiate()

static int tls_negotiate ( struct Connection conn)
static

Negotiate TLS connection.

Parameters
connConnection to a server
Return values
0Success
-1Error

After TLS session has been initialized, attempt to negotiate TLS over the wire, including certificate checks.

Definition at line 872 of file gnutls.c.

873{
874 struct TlsSockData *data = mutt_mem_calloc(1, sizeof(struct TlsSockData));
875 conn->sockdata = data;
876 int err = gnutls_certificate_allocate_credentials(&data->xcred);
877 if (err < 0)
878 {
879 FREE(&conn->sockdata);
880 mutt_error("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err));
881 return -1;
882 }
883
884 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
885 gnutls_certificate_set_x509_trust_file(data->xcred, c_certificate_file, GNUTLS_X509_FMT_PEM);
886 /* ignore errors, maybe file doesn't exist yet */
887
888 const char *const c_ssl_ca_certificates_file = cs_subset_path(NeoMutt->sub, "ssl_ca_certificates_file");
889 if (c_ssl_ca_certificates_file)
890 {
891 gnutls_certificate_set_x509_trust_file(data->xcred, c_ssl_ca_certificates_file,
892 GNUTLS_X509_FMT_PEM);
893 }
894
895 const char *const c_ssl_client_cert = cs_subset_path(NeoMutt->sub, "ssl_client_cert");
896 if (c_ssl_client_cert)
897 {
898 mutt_debug(LL_DEBUG2, "Using client certificate %s\n", c_ssl_client_cert);
899 gnutls_certificate_set_x509_key_file(data->xcred, c_ssl_client_cert,
900 c_ssl_client_cert, GNUTLS_X509_FMT_PEM);
901 }
902
903#ifdef HAVE_DECL_GNUTLS_VERIFY_DISABLE_TIME_CHECKS
904 /* disable checking certificate activation/expiration times
905 * in gnutls, we do the checks ourselves */
906 gnutls_certificate_set_verify_flags(data->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS);
907#endif
908
909 err = gnutls_init(&data->session, GNUTLS_CLIENT);
910 if (err)
911 {
912 mutt_error("gnutls_init: %s", gnutls_strerror(err));
913 goto fail;
914 }
915
916 /* set socket */
917 gnutls_transport_set_ptr(data->session, (gnutls_transport_ptr_t) (long) conn->fd);
918
919 if (gnutls_server_name_set(data->session, GNUTLS_NAME_DNS, conn->account.host,
920 mutt_str_len(conn->account.host)))
921 {
922 mutt_error(_("Warning: unable to set TLS SNI host name"));
923 }
924
925 if (tls_set_priority(data) < 0)
926 {
927 goto fail;
928 }
929
930 const short c_ssl_min_dh_prime_bits = cs_subset_number(NeoMutt->sub, "ssl_min_dh_prime_bits");
931 if (c_ssl_min_dh_prime_bits > 0)
932 {
933 gnutls_dh_set_prime_bits(data->session, c_ssl_min_dh_prime_bits);
934 }
935
936 gnutls_credentials_set(data->session, GNUTLS_CRD_CERTIFICATE, data->xcred);
937
938 do
939 {
940 err = gnutls_handshake(data->session);
941 } while ((err == GNUTLS_E_AGAIN) || (err == GNUTLS_E_INTERRUPTED));
942
943 if (err < 0)
944 {
945 if (err == GNUTLS_E_FATAL_ALERT_RECEIVED)
946 {
947 mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err),
948 gnutls_alert_get_name(gnutls_alert_get(data->session)));
949 }
950 else
951 {
952 mutt_error("gnutls_handshake: %s", gnutls_strerror(err));
953 }
954 goto fail;
955 }
956
957 if (tls_check_certificate(conn) == 0)
958 goto fail;
959
960 /* set Security Strength Factor (SSF) for SASL */
961 /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
962 conn->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(data->session)) * 8;
963
965
966 if (!OptNoCurses)
967 {
968 mutt_message(_("SSL/TLS connection using %s (%s/%s/%s)"),
969 gnutls_protocol_get_name(gnutls_protocol_get_version(data->session)),
970 gnutls_kx_get_name(gnutls_kx_get(data->session)),
971 gnutls_cipher_get_name(gnutls_cipher_get(data->session)),
972 gnutls_mac_get_name(gnutls_mac_get(data->session)));
973 mutt_sleep(0);
974 }
975
976 return 0;
977
978fail:
979 gnutls_certificate_free_credentials(data->xcred);
980 gnutls_deinit(data->session);
981 FREE(&conn->sockdata);
982 return -1;
983}
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:143
static int tls_check_certificate(struct Connection *conn)
Check a connection's certificate.
Definition: gnutls.c:618
static void tls_get_client_cert(struct Connection *conn)
Get the client certificate for a TLS connection.
Definition: gnutls.c:702
static int tls_set_priority(struct TlsSockData *data)
Set the priority of various protocols.
Definition: gnutls.c:825
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:843
unsigned int ssf
Security strength factor, in bits (see notes)
Definition: connection.h:50
int fd
Socket file descriptor.
Definition: connection.h:53
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_ssl_socket_setup()

int mutt_ssl_socket_setup ( struct Connection conn)

Set up SSL socket mulitplexor.

Parameters
connConnection to a server
Return values
0Success
-1Error

Definition at line 1125 of file gnutls.c.

1126{
1127 if (tls_init() < 0)
1128 return -1;
1129
1130 conn->open = tls_socket_open;
1131 conn->read = tls_socket_read;
1132 conn->write = tls_socket_write;
1133 conn->close = tls_socket_close;
1134 conn->poll = tls_socket_poll;
1135
1136 return 0;
1137}
static int tls_init(void)
Set up Gnu TLS.
Definition: gnutls.c:92
static int tls_socket_close(struct Connection *conn)
Close a TLS socket - Implements Connection::close() -.
Definition: gnutls.c:1003
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition: gnutls.c:1028
static int tls_socket_poll(struct Connection *conn, time_t wait_secs)
Check if any data is waiting on a socket - Implements Connection::poll() -.
Definition: gnutls.c:988
static int tls_socket_read(struct Connection *conn, char *buf, size_t count)
Read data from a TLS socket - Implements Connection::read() -.
Definition: gnutls.c:1045
static int tls_socket_write(struct Connection *conn, const char *buf, size_t count)
Write data to a TLS socket - Implements Connection::write() -.
Definition: gnutls.c:1072
int(* poll)(struct Connection *conn, time_t wait_secs)
Definition: connection.h:105
int(* write)(struct Connection *conn, const char *buf, size_t count)
Definition: connection.h:92
int(* close)(struct Connection *conn)
Definition: connection.h:116
int(* open)(struct Connection *conn)
Definition: connection.h:66
int(* read)(struct Connection *conn, char *buf, size_t count)
Definition: connection.h:79
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_ssl_starttls()

int mutt_ssl_starttls ( struct Connection conn)

Negotiate TLS over an already opened connection.

Parameters
connConnection to a server
Return values
0Success
-1Error

Definition at line 1145 of file gnutls.c.

1146{
1147 if (tls_init() < 0)
1148 return -1;
1149
1150 if (tls_negotiate(conn) < 0)
1151 return -1;
1152
1153 conn->read = tls_socket_read;
1154 conn->write = tls_socket_write;
1155 conn->close = tls_starttls_close;
1156 conn->poll = tls_socket_poll;
1157
1158 return 0;
1159}
static int tls_negotiate(struct Connection *conn)
Negotiate TLS connection.
Definition: gnutls.c:872
static int tls_starttls_close(struct Connection *conn)
Close a TLS connection - Implements Connection::close() -.
Definition: gnutls.c:1106
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ ProtocolPriority

int ProtocolPriority[] = { GNUTLS_TLS1_2, GNUTLS_TLS1_1, GNUTLS_TLS1, GNUTLS_SSL3, 0 }
static

This array needs to be large enough to hold all the possible values support by NeoMutt.

The initialized values are just placeholders–the array gets overwrriten in tls_negotiate() depending on the $ssl_use_* options.

Note: gnutls_protocol_set_priority() was removed in GnuTLS version 3.4 (2015-04). TLS 1.3 support wasn't added until version 3.6.5. Therefore, no attempt is made to support $ssl_use_tlsv1_3 in this code.

Definition at line 75 of file gnutls.c.