NeoMutt  2024-03-23-23-gec7045
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, char *buf, size_t buflen, 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,
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 160 of file gnutls.c.

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

192{
193 char *linestr = NULL;
194 size_t linestrsize = 0;
195
196 /* try checking against names stored in stored certs file */
197 const char *const c_certificate_file = cs_subset_path(NeoMutt->sub, "certificate_file");
198 FILE *fp = mutt_file_fopen(c_certificate_file, "r");
199 if (!fp)
200 return false;
201
202 char buf[80] = { 0 };
203 buf[0] = '\0';
204 tls_fingerprint(GNUTLS_DIG_MD5, buf, sizeof(buf), cert);
205 while ((linestr = mutt_file_read_line(linestr, &linestrsize, fp, NULL, MUTT_RL_NO_FLAGS)))
206 {
207 regmatch_t *match = mutt_prex_capture(PREX_GNUTLS_CERT_HOST_HASH, linestr);
208 if (match)
209 {
210 regmatch_t *mhost = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HOST];
211 regmatch_t *mhash = &match[PREX_GNUTLS_CERT_HOST_HASH_MATCH_HASH];
212 linestr[mutt_regmatch_end(mhost)] = '\0';
213 linestr[mutt_regmatch_end(mhash)] = '\0';
214 if ((mutt_str_equal(linestr + mutt_regmatch_start(mhost), hostname)) &&
215 (mutt_str_equal(linestr + mutt_regmatch_start(mhash), buf)))
216 {
217 FREE(&linestr);
218 mutt_file_fclose(&fp);
219 return true;
220 }
221 }
222 }
223
224 mutt_file_fclose(&fp);
225
226 /* not found a matching name */
227 return false;
228}
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:169
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:801
#define mutt_file_fclose(FP)
Definition: file.h:148
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:147
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:40
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:160
#define FREE(x)
Definition: memory.h:45
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:709
regmatch_t * mutt_prex_capture(enum Prex which, const char *str)
Match a precompiled regex against a string.
Definition: prex.c:295
@ 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
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
+ 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 236 of file gnutls.c.

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

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

427{
428 static const char *part[] = {
429 GNUTLS_OID_X520_COMMON_NAME, // CN
430 GNUTLS_OID_PKCS9_EMAIL, // Email
431 GNUTLS_OID_X520_ORGANIZATION_NAME, // O
432 GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, // OU
433 GNUTLS_OID_X520_LOCALITY_NAME, // L
434 GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, // ST
435 GNUTLS_OID_X520_COUNTRY_NAME, // C
436 };
437
438 char buf[128] = { 0 };
439 int rc;
440
441 // Allocate formatted strings and let the array take ownership
442 ARRAY_ADD(carr, mutt_str_dup(title));
443
444 for (size_t i = 0; i < mutt_array_size(part); i++)
445 {
446 size_t buflen = sizeof(buf);
447 if (issuer)
448 rc = gnutls_x509_crt_get_issuer_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
449 else
450 rc = gnutls_x509_crt_get_dn_by_oid(cert, part[i], 0, 0, buf, &buflen);
451 if (rc != 0)
452 continue;
453
454 char *line = NULL;
455 mutt_str_asprintf(&line, " %s", buf);
456 ARRAY_ADD(carr, line);
457 }
458}
#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:852
+ 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 470 of file gnutls.c.

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

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

Definition at line 697 of file gnutls.c.

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

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

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

1125{
1126 if (tls_init() < 0)
1127 return -1;
1128
1129 conn->open = tls_socket_open;
1130 conn->read = tls_socket_read;
1131 conn->write = tls_socket_write;
1132 conn->close = tls_socket_close;
1133 conn->poll = tls_socket_poll;
1134
1135 return 0;
1136}
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:1002
static int tls_socket_open(struct Connection *conn)
Open a TLS socket - Implements Connection::open() -.
Definition: gnutls.c:1027
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:987
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:1044
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:1071
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 1144 of file gnutls.c.

1145{
1146 if (tls_init() < 0)
1147 return -1;
1148
1149 if (tls_negotiate(conn) < 0)
1150 return -1;
1151
1152 conn->read = tls_socket_read;
1153 conn->write = tls_socket_write;
1154 conn->close = tls_starttls_close;
1155 conn->poll = tls_socket_poll;
1156
1157 return 0;
1158}
static int tls_negotiate(struct Connection *conn)
Negotiate TLS connection.
Definition: gnutls.c:871
static int tls_starttls_close(struct Connection *conn)
Close a TLS connection - Implements Connection::close() -.
Definition: gnutls.c:1105
+ 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.