NeoMutt  2022-04-29-215-gc12b98
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 "muttlib.h"
#include "options.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

const int dialog_row_len = 128
 
static int protocol_priority []
 

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

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

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

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

191{
192 char *linestr = NULL;
193 size_t linestrsize = 0;
194
195 /* try checking against names stored in stored certs file */
196 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
197 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
198 if (!fp)
199 return false;
200
201 char buf[80] = { 0 };
202 buf[0] = '\0';
203 tls_fingerprint(GNUTLS_DIG_MD5, buf, sizeof(buf), cert);
204 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
205 {
206 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
207 if (match)
208 {
209 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
210 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
211 linestr[mutt_regmatch_end(mhost)] = '\0';
212 linestr[mutt_regmatch_end(mhash)] = '\0';
213 if ((strcmp(linestr + mutt_regmatch_start(mhost), hostname) == 0) &&
214 (strcmp(linestr + mutt_regmatch_start(mhash), buf) == 0))
215 {
216 FREE(&linestr);
217 mutt_file_fclose(&fp);
218 return true;
219 }
220 }
221 }
222
223 mutt_file_fclose(&fp);
224
225 /* not found a matching name */
226 return false;
227}
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:720
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:38
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:159
#define FREE(x)
Definition: memory.h:43
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition: prex.c:305
@ 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:111
@ PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST
#H [foo.com] A76D ... 65B1
Definition: prex.h:110
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 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:62
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 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_epoch())
342 *certerr |= CERTERR_EXPIRED;
343 if (gnutls_x509_crt_get_activation_time(cert) > mutt_date_epoch())
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:73
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:428
#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:190
#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:235
#define CERTERR_OTHER
Definition: gnutls.c:57
#define CERTERR_SIGNERNOTCA
Definition: gnutls.c:55
#define CERTERR_REVOKED
Definition: gnutls.c:52
@ 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:155
#define mutt_array_size(x)
Definition: memory.h:36
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1031
+ 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 char fpbuf[128] = { 0 };
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 /* interactive check from user */
486 if (gnutls_x509_crt_init(&cert) < 0)
487 {
488 mutt_error(_("Error initialising gnutls certificate data"));
489 return 0;
490 }
491
492 if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0)
493 {
494 mutt_error(_("Error processing certificate data"));
495 gnutls_x509_crt_deinit(cert);
496 return 0;
497 }
498
499 add_cert(_("This certificate belongs to:"), cert, false, &carr);
500 ARRAY_ADD(&carr, NULL);
501 add_cert(_("This certificate was issued by:"), cert, true, &carr);
502
503 ARRAY_ADD(&carr, NULL);
504 ARRAY_ADD(&carr, mutt_str_dup(_("This certificate is valid")));
505
506 char *line = NULL;
507 t = gnutls_x509_crt_get_activation_time(cert);
508 mutt_date_make_tls(datestr, sizeof(datestr), t);
509 mutt_str_asprintf(&line, _(" from %s"), datestr);
510 ARRAY_ADD(&carr, line);
511
512 t = gnutls_x509_crt_get_expiration_time(cert);
513 mutt_date_make_tls(datestr, sizeof(datestr), t);
514 mutt_str_asprintf(&line, _(" to %s"), datestr);
515 ARRAY_ADD(&carr, line);
516 ARRAY_ADD(&carr, NULL);
517
518 fpbuf[0] = '\0';
519 tls_fingerprint(GNUTLS_DIG_SHA, fpbuf, sizeof(fpbuf), certdata);
520 mutt_str_asprintf(&line, _("SHA1 Fingerprint: %s"), fpbuf);
521 ARRAY_ADD(&carr, line);
522 fpbuf[0] = '\0';
523 fpbuf[40] = '\0'; /* Ensure the second printed line is null terminated */
524 tls_fingerprint(GNUTLS_DIG_SHA256, fpbuf, sizeof(fpbuf), certdata);
525 fpbuf[39] = '\0'; /* Divide into two carr of output */
526 mutt_str_asprintf(&line, "%s%s", _("SHA256 Fingerprint: "), fpbuf);
527 ARRAY_ADD(&carr, line);
528 mutt_str_asprintf(&line, "%*s%s", (int) mutt_str_len(_("SHA256 Fingerprint: ")),
529 "", fpbuf + 40);
530 ARRAY_ADD(&carr, line);
531
532 if (certerr)
533 ARRAY_ADD(&carr, NULL);
534
535 if (certerr & CERTERR_NOTYETVALID)
536 {
537 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate is not yet valid")));
538 }
539 if (certerr & CERTERR_EXPIRED)
540 {
541 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has expired")));
542 }
543 if (certerr & CERTERR_REVOKED)
544 {
545 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server certificate has been revoked")));
546 }
547 if (certerr & CERTERR_HOSTNAME)
548 {
549 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Server hostname does not match certificate")));
550 }
551 if (certerr & CERTERR_SIGNERNOTCA)
552 {
553 ARRAY_ADD(&carr, mutt_str_dup(_("WARNING: Signer of server certificate is not a CA")));
554 }
555 if (certerr & CERTERR_INSECUREALG)
556 {
557 ARRAY_ADD(&carr, mutt_str_dup(_("Warning: Server certificate was signed using an insecure algorithm")));
558 }
559
560 snprintf(title, sizeof(title),
561 _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len);
562
563 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
564 const bool allow_always = (c_certificate_file && !savedcert &&
566 int rc = dlg_verify_certificate(title, &carr, allow_always, false);
567 if (rc == 3) // Accept always
568 {
569 bool saved = false;
570 FILE *fp = mutt_file_fopen(c_certificate_file, "a");
571 if (fp)
572 {
573 if (certerr & CERTERR_HOSTNAME) // Save hostname if necessary
574 {
575 fpbuf[0] = '\0';
576 tls_fingerprint(GNUTLS_DIG_MD5, fpbuf, sizeof(fpbuf), certdata);
577 fprintf(fp, "#H %s %s\n", hostname, fpbuf);
578 saved = true;
579 }
580 if (certerr ^ CERTERR_HOSTNAME) // Save the cert for all other errors
581 {
582 int rc2 = gnutls_pem_base64_encode_alloc("CERTIFICATE", certdata, &pemdata);
583 if (rc2 == 0)
584 {
585 if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1)
586 {
587 saved = true;
588 }
589 gnutls_free(pemdata.data);
590 }
591 }
592 mutt_file_fclose(&fp);
593 }
594 if (!saved)
595 mutt_message(_("Certificate saved"));
596 else
597 mutt_error(_("Warning: Couldn't save certificate"));
598 }
599
600 cert_array_clear(&carr);
601 ARRAY_FREE(&carr);
602 gnutls_x509_crt_deinit(cert);
603 return (rc > 1);
604}
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:203
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition: array.h:57
int mutt_date_make_tls(char *buf, size_t buflen, time_t timestamp)
Format date in TLS certificate verification style.
Definition: date.c:584
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: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
#define mutt_message(...)
Definition: logging.h:86
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
+ 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 612 of file gnutls.c.

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

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

824{
825 size_t nproto = 0; /* number of tls/ssl protocols */
826
827 const bool c_ssl_use_tlsv1_2 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_2");
828 if (c_ssl_use_tlsv1_2)
829 protocol_priority[nproto++] = GNUTLS_TLS1_2;
830 const bool c_ssl_use_tlsv1_1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1_1");
831 if (c_ssl_use_tlsv1_1)
832 protocol_priority[nproto++] = GNUTLS_TLS1_1;
833 const bool c_ssl_use_tlsv1 = cs_subset_bool(NeoMutt->sub, "ssl_use_tlsv1");
834 if (c_ssl_use_tlsv1)
835 protocol_priority[nproto++] = GNUTLS_TLS1;
836 const bool c_ssl_use_sslv3 = cs_subset_bool(NeoMutt->sub, "ssl_use_sslv3");
837 if (c_ssl_use_sslv3)
838 protocol_priority[nproto++] = GNUTLS_SSL3;
839 protocol_priority[nproto] = 0;
840
841 if (nproto == 0)
842 {
843 mutt_error(_("All available protocols for TLS/SSL connection disabled"));
844 return -1;
845 }
846
847 const char *const c_ssl_ciphers = cs_subset_string(NeoMutt->sub, "ssl_ciphers");
848 if (c_ssl_ciphers)
849 {
850 mutt_error(_("Explicit ciphersuite selection via $ssl_ciphers not supported"));
851 }
852
853 /* We use default priorities (see gnutls documentation),
854 * except for protocol version */
855 gnutls_set_default_priority(data->state);
856 gnutls_protocol_set_priority(data->state, protocol_priority);
857 return 0;
858}
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 protocol_priority[]
Definition: gnutls.c:73
+ 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 state has been initialized, attempt to negotiate TLS over the wire, including certificate checks.

Definition at line 870 of file gnutls.c.

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

1124{
1125 if (tls_init() < 0)
1126 return -1;
1127
1128 conn->open = tls_socket_open;
1129 conn->read = tls_socket_read;
1130 conn->write = tls_socket_write;
1131 conn->close = tls_socket_close;
1132 conn->poll = tls_socket_poll;
1133
1134 return 0;
1135}
static int tls_init(void)
Set up Gnu TLS.
Definition: gnutls.c:91
static int tls_socket_close(struct Connection *conn)
Close a TLS socket - Implements Connection::close() -.
Definition: gnutls.c:1001
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition: gnutls.c:1026
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:986
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:1043
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:1070
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 1143 of file gnutls.c.

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

Variable Documentation

◆ dialog_row_len

const int dialog_row_len = 128

Definition at line 60 of file gnutls.c.

◆ protocol_priority

int protocol_priority[]
static
Initial value:
= { GNUTLS_TLS1_2, GNUTLS_TLS1_1, GNUTLS_TLS1,
GNUTLS_SSL3, 0 }

Definition at line 73 of file gnutls.c.