NeoMutt  2023-05-17-56-ga67199
Teaching an old dog new tricks
DOXYGEN
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 "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

static int tls_init (void)
 Set up Gnu TLS. More...
 
static int tls_verify_peers (gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
 Wrapper for gnutls_certificate_verify_peers() More...
 
static void tls_fingerprint (gnutls_digest_algorithm_t algo, char *buf, size_t buflen, const gnutls_datum_t *data)
 Create a fingerprint of a TLS Certificate. More...
 
static bool tls_check_stored_hostname (const gnutls_datum_t *cert, const char *hostname)
 Does the hostname match a stored certificate? More...
 
static int tls_compare_certificates (const gnutls_datum_t *peercert)
 Compare certificates against $certificate_file More...
 
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. More...
 
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. More...
 
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. More...
 
static int tls_check_certificate (struct Connection *conn)
 Check a connection's certificate. More...
 
static void tls_get_client_cert (struct Connection *conn)
 Get the client certificate for a TLS connection. More...
 
static int tls_set_priority (struct TlsSockData *data)
 Set the priority of various protocols. More...
 
static int tls_negotiate (struct Connection *conn)
 Negotiate TLS connection. More...
 
static int tls_socket_poll (struct Connection *conn, time_t wait_secs)
 Check whether a socket read would block - Implements Connection::poll() -. More...
 
static int tls_socket_close (struct Connection *conn)
 Close a TLS socket - Implements Connection::close() -. More...
 
static int tls_socket_open (struct Connection *conn)
 Open a TLS socket - Implements Connection::open() -. More...
 
static int tls_socket_read (struct Connection *conn, char *buf, size_t count)
 Read data from a TLS socket - Implements Connection::read() -. More...
 
static int tls_socket_write (struct Connection *conn, const char *buf, size_t count)
 Write data to a TLS socket - Implements Connection::write() -. More...
 
static int tls_starttls_close (struct Connection *conn)
 Close a TLS connection - Implements Connection::close() -. More...
 
int mutt_ssl_socket_setup (struct Connection *conn)
 Set up SSL socket mulitplexor. More...
 
int mutt_ssl_starttls (struct Connection *conn)
 Negotiate TLS over an already opened connection. More...
 

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. More...
 

Detailed Description

Handling of GnuTLS encryption.

Authors
  • Marco d'Itri
  • Andrew McDonald

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 49 of file gnutls.c.

◆ CERTERR_EXPIRED

#define CERTERR_EXPIRED   (1 << 0)

Definition at line 50 of file gnutls.c.

◆ CERTERR_NOTYETVALID

#define CERTERR_NOTYETVALID   (1 << 1)

Definition at line 51 of file gnutls.c.

◆ CERTERR_REVOKED

#define CERTERR_REVOKED   (1 << 2)

Definition at line 52 of file gnutls.c.

◆ CERTERR_NOTTRUSTED

#define CERTERR_NOTTRUSTED   (1 << 3)

Definition at line 53 of file gnutls.c.

◆ CERTERR_HOSTNAME

#define CERTERR_HOSTNAME   (1 << 4)

Definition at line 54 of file gnutls.c.

◆ CERTERR_SIGNERNOTCA

#define CERTERR_SIGNERNOTCA   (1 << 5)

Definition at line 55 of file gnutls.c.

◆ CERTERR_INSECUREALG

#define CERTERR_INSECUREALG   (1 << 6)

Definition at line 56 of file gnutls.c.

◆ CERTERR_OTHER

#define CERTERR_OTHER   (1 << 7)

Definition at line 57 of file gnutls.c.

◆ CERT_SEP

#define CERT_SEP   "-----BEGIN"

Definition at line 60 of file gnutls.c.

Function Documentation

◆ tls_init()

static int tls_init ( void  )
static

Set up Gnu TLS.

Return values
0Success
-1Error

Definition at line 88 of file gnutls.c.

89{
90 static bool init_complete = false;
91 int err;
92
93 if (init_complete)
94 return 0;
95
96 err = gnutls_global_init();
97 if (err < 0)
98 {
99 mutt_error("gnutls_global_init: %s", gnutls_strerror(err));
100 return -1;
101 }
102
103 init_complete = true;
104 return 0;
105}
#define mutt_error(...)
Definition: logging2.h:90
+ 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 118 of file gnutls.c.

119{
120 /* gnutls_certificate_verify_peers2() chains to
121 * gnutls_x509_trust_list_verify_crt2(). That function's documentation says:
122 *
123 * When a certificate chain of cert_list_size with more than one
124 * certificates is provided, the verification status will apply to
125 * the first certificate in the chain that failed
126 * verification. The verification process starts from the end of
127 * the chain(from CA to end certificate). The first certificate
128 * in the chain must be the end-certificate while the rest of the
129 * members may be sorted or not.
130 *
131 * This is why tls_check_certificate() loops from CA to host in that order,
132 * calling the menu, and recalling tls_verify_peers() for each approved
133 * cert in the chain.
134 */
135 int rc = gnutls_certificate_verify_peers2(tlsstate, certstat);
136
137 /* certstat was set */
138 if (rc == 0)
139 return 0;
140
141 if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND)
142 mutt_error(_("Unable to get certificate from peer"));
143 else
144 mutt_error(_("Certificate verification error (%s)"), gnutls_strerror(rc));
145
146 return rc;
147}
#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,
char *  buf,
size_t  buflen,
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
buflenLength of the buffer
dataCertificate

Definition at line 156 of file gnutls.c.

158{
159 unsigned char md[64];
160 size_t n;
161
162 n = 64;
163
164 if (gnutls_fingerprint(algo, data, (char *) md, &n) < 0)
165 {
166 snprintf(buf, buflen, _("[unable to calculate]"));
167 }
168 else
169 {
170 for (int i = 0; i < (int) n; i++)
171 {
172 char ch[8] = { 0 };
173 snprintf(ch, 8, "%02X%s", md[i], ((i % 2) ? " " : ""));
174 mutt_str_cat(buf, buflen, ch);
175 }
176 buf[2 * n + n / 2 - 1] = '\0'; /* don't want trailing space */
177 }
178}
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:266
+ 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 187 of file gnutls.c.

188{
189 char *linestr = NULL;
190 size_t linestrsize = 0;
191
192 /* try checking against names stored in stored certs file */
193 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
194 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
195 if (!fp)
196 return false;
197
198 char buf[80] = { 0 };
199 buf[0] = '\0';
200 tls_fingerprint(GNUTLS_DIG_MD5, buf, sizeof(buf), cert);
201 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
202 {
203 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
204 if (match)
205 {
206 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
207 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
208 linestr[mutt_regmatch_end(mhost)] = '\0';
209 linestr[mutt_regmatch_end(mhash)] = '\0';
210 if ((mutt_str_equal(linestr + mutt_regmatch_start(mhost), hostname)) &&
211 (mutt_str_equal(linestr + mutt_regmatch_start(mhash), buf)))
212 {
213 FREE(&linestr);
214 mutt_file_fclose(&fp);
215 return true;
216 }
217 }
218 }
219
220 mutt_file_fclose(&fp);
221
222 /* not found a matching name */
223 return false;
224}
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
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:738
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:634
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:39
static void tls_fingerprint(gnutls_digest_algorithm_t algo, char *buf, size_t buflen, const gnutls_datum_t *data)
Create a fingerprint of a TLS Certificate.
Definition: gnutls.c:156
#define FREE(x)
Definition: memory.h:43
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition: prex.c:289
@ PREX_GNUTLS_CERT_HOST_HASH
[#H foo.com A76D 954B EB79 1F49 5B3A 0A0E 0681 65B1]
Definition: prex.h:36
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH
#H foo.com [A76D ... 65B1]
Definition: prex.h:110
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST
#H [foo.com] A76D ... 65B1
Definition: prex.h:109
static regoff_t mutt_regmatch_end(const regmatch_t *match)
Return the end of a match.
Definition: regex3.h:70
static regoff_t mutt_regmatch_start(const regmatch_t *match)
Return the start of a match.
Definition: regex3.h:60
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
+ 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 232 of file gnutls.c.

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

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

423{
424 static const char *part[] = {
425 GNUTLS_OID_X520_COMMON_NAME, // CN
426 GNUTLS_OID_PKCS9_EMAIL, // Email
427 GNUTLS_OID_X520_ORGANIZATION_NAME, // O
428 GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, // OU
429 GNUTLS_OID_X520_LOCALITY_NAME, // L
430 GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, // ST
431 GNUTLS_OID_X520_COUNTRY_NAME, // C
432 };
433
434 char buf[128] = { 0 };
435 int rc;
436
437 // Allocate formatted strings and let the array take ownership
438 ARRAY_ADD(carr, mutt_str_dup(title));
439
440 for (size_t i = 0; i < mutt_array_size(part); i++)
441 {
442 size_t buflen = sizeof(buf);
443 if (issuer)
444 rc = gnutls_x509_crt_get_issuer_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
445 else
446 rc = gnutls_x509_crt_get_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
447 if (rc != 0)
448 continue;
449
450 char *line = NULL;
451 mutt_str_asprintf(&line, " %s", buf);
452 ARRAY_ADD(carr, line);
453 }
454}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:155
#define mutt_array_size(x)
Definition: memory.h:36
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1022
+ 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 466 of file gnutls.c.

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

610{
611 struct TlsSockData *data = conn->sockdata;
612 gnutls_session_t session = data->session;
613 const gnutls_datum_t *cert_list = NULL;
614 unsigned int cert_list_size = 0;
615 gnutls_certificate_status_t certstat;
616 int certerr, savedcert, rc = 0;
617 int max_preauth_pass = -1;
618
619 /* tls_verify_peers() calls gnutls_certificate_verify_peers2(),
620 * which verifies the auth_type is GNUTLS_CRD_CERTIFICATE
621 * and that get_certificate_type() for the server is GNUTLS_CRT_X509.
622 * If it returns 0, certstat will be set with failure codes for the first
623 * cert in the chain(from CA to host) with an error.
624 */
625 if (tls_verify_peers(session, &certstat) != 0)
626 return 0;
627
628 cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
629 if (!cert_list)
630 {
631 mutt_error(_("Unable to get certificate from peer"));
632 return 0;
633 }
634
635 /* tls_verify_peers doesn't check hostname or expiration, so walk
636 * from most specific to least checking these. If we see a saved certificate,
637 * its status short-circuits the remaining checks. */
638 int preauthrc = 0;
639 for (int i = 0; i < cert_list_size; i++)
640 {
641 rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i,
642 &certerr, &savedcert);
643 preauthrc += rc;
644 if (!preauthrc)
645 max_preauth_pass = i;
646
647 if (savedcert)
648 {
649 if (preauthrc == 0)
650 return 1;
651 break;
652 }
653 }
654
655 /* then check interactively, starting from chain root */
656 for (int i = cert_list_size - 1; i >= 0; i--)
657 {
658 rc = tls_check_one_certificate(&cert_list[i], certstat, conn->account.host,
659 i, cert_list_size);
660
661 /* Stop checking if the menu cert is aborted or rejected. */
662 if (rc == 0)
663 break;
664
665 /* add signers to trust set, then reverify */
666 if (i)
667 {
668 int rcsettrust = gnutls_certificate_set_x509_trust_mem(data->xcred, &cert_list[i],
669 GNUTLS_X509_FMT_DER);
670 if (rcsettrust != 1)
671 mutt_debug(LL_DEBUG1, "error trusting certificate %d: %d\n", i, rcsettrust);
672
673 if (tls_verify_peers(session, &certstat) != 0)
674 return 0;
675
676 /* If the cert chain now verifies, and all lower certs already
677 * passed preauth, we are done. */
678 if (!certstat && (max_preauth_pass >= (i - 1)))
679 return 1;
680 }
681 }
682
683 return rc;
684}
static int tls_verify_peers(gnutls_session_t tlsstate, gnutls_certificate_status_t *certstat)
Wrapper for gnutls_certificate_verify_peers()
Definition: gnutls.c:118
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:466
#define mutt_debug(LEVEL,...)
Definition: logging2.h:87
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
char host[128]
Server to login to.
Definition: connaccount.h:54
void * sockdata
Backend-specific socket data.
Definition: connection.h:56
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
TLS socket data -.
Definition: gnutls.c:78
gnutls_certificate_credentials_t xcred
Definition: gnutls.c:80
gnutls_session_t session
Definition: gnutls.c:79
+ 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. It does contain a call to mutt_account_getuser().

Definition at line 693 of file gnutls.c.

694{
695 struct TlsSockData *data = conn->sockdata;
696 gnutls_x509_crt_t clientcrt;
697 char *cn = NULL;
698 size_t cnlen = 0;
699 int rc;
700
701 /* get our cert CN if we have one */
702 const gnutls_datum_t *crtdata = gnutls_certificate_get_ours(data->session);
703 if (!crtdata)
704 return;
705
706 if (gnutls_x509_crt_init(&clientcrt) < 0)
707 {
708 mutt_debug(LL_DEBUG1, "Failed to init gnutls crt\n");
709 return;
710 }
711
712 if (gnutls_x509_crt_import(clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0)
713 {
714 mutt_debug(LL_DEBUG1, "Failed to import gnutls client crt\n");
715 goto err;
716 }
717
718 /* get length of CN, then grab it. */
719 rc = gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
720 0, NULL, &cnlen);
721 if (((rc >= 0) || (rc == GNUTLS_E_SHORT_MEMORY_BUFFER)) && (cnlen > 0))
722 {
723 cn = mutt_mem_calloc(1, cnlen);
724 if (gnutls_x509_crt_get_dn_by_oid(clientcrt, GNUTLS_OID_X520_COMMON_NAME, 0,
725 0, cn, &cnlen) < 0)
726 {
727 goto err;
728 }
729 mutt_debug(LL_DEBUG2, "client certificate CN: %s\n", cn);
730
731 /* if we are using a client cert, SASL may expect an external auth name */
732 if (mutt_account_getuser(&conn->account) < 0)
733 mutt_debug(LL_DEBUG1, "Couldn't get user info\n");
734 }
735
736err:
737 FREE(&cn);
738 gnutls_x509_crt_deinit(clientcrt);
739}
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:49
@ 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 820 of file gnutls.c.

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

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

1121{
1122 if (tls_init() < 0)
1123 return -1;
1124
1125 conn->open = tls_socket_open;
1126 conn->read = tls_socket_read;
1127 conn->write = tls_socket_write;
1128 conn->close = tls_socket_close;
1129 conn->poll = tls_socket_poll;
1130
1131 return 0;
1132}
static int tls_init(void)
Set up Gnu TLS.
Definition: gnutls.c:88
static int tls_socket_close(struct Connection *conn)
Close a TLS socket - Implements Connection::close() -.
Definition: gnutls.c:998
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition: gnutls.c:1023
static int tls_socket_poll(struct Connection *conn, time_t wait_secs)
Check whether a socket read would block - Implements Connection::poll() -.
Definition: gnutls.c:983
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:1040
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:1067
int(* poll)(struct Connection *conn, time_t wait_secs)
Definition: connection.h:106
int(* write)(struct Connection *conn, const char *buf, size_t count)
Definition: connection.h:93
int(* close)(struct Connection *conn)
Definition: connection.h:117
int(* open)(struct Connection *conn)
Definition: connection.h:67
int(* read)(struct Connection *conn, char *buf, size_t count)
Definition: connection.h:80
+ 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 1140 of file gnutls.c.

1141{
1142 if (tls_init() < 0)
1143 return -1;
1144
1145 if (tls_negotiate(conn) < 0)
1146 return -1;
1147
1148 conn->read = tls_socket_read;
1149 conn->write = tls_socket_write;
1150 conn->close = tls_starttls_close;
1151 conn->poll = tls_socket_poll;
1152
1153 return 0;
1154}
static int tls_negotiate(struct Connection *conn)
Negotiate TLS connection.
Definition: gnutls.c:867
static int tls_starttls_close(struct Connection *conn)
Close a TLS connection - Implements Connection::close() -.
Definition: gnutls.c:1101
+ 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 71 of file gnutls.c.