X-Git-Url: https://git.llucax.com/software/mutt-debian.git/blobdiff_plain/14c29200cb58d3c4a0830265f2433849781858d0..20f65ac3b82b354496b5bb469ff184ba8b0bcd96:/mutt_ssl_gnutls.c diff --git a/mutt_ssl_gnutls.c b/mutt_ssl_gnutls.c index f537f8f..dbac87e 100644 --- a/mutt_ssl_gnutls.c +++ b/mutt_ssl_gnutls.c @@ -33,6 +33,16 @@ #include "mutt_ssl.h" #include "mutt_regex.h" +/* certificate error bitmap values */ +#define CERTERR_VALID 0 +#define CERTERR_EXPIRED 1 +#define CERTERR_NOTYETVALID 2 +#define CERTERR_REVOKED 4 +#define CERTERR_NOTTRUSTED 8 +#define CERTERR_HOSTNAME 16 +#define CERTERR_SIGNERNOTCA 32 +#define CERTERR_INSECUREALG 64 + typedef struct _tlssockdata { gnutls_session state; @@ -98,13 +108,17 @@ static int tls_socket_read (CONNECTION* conn, char* buf, size_t len) return -1; } - ret = gnutls_record_recv (data->state, buf, len); - if (ret < 0 && gnutls_error_is_fatal(ret) == 1) - { - mutt_error ("tls_socket_read (%s)", gnutls_strerror (ret)); - mutt_sleep (4); - return -1; + do { + ret = gnutls_record_recv (data->state, buf, len); + if (ret < 0 && gnutls_error_is_fatal(ret) == 1) + { + mutt_error ("tls_socket_read (%s)", gnutls_strerror (ret)); + mutt_sleep (4); + return -1; + } } + while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + return ret; } @@ -169,6 +183,61 @@ int mutt_ssl_starttls (CONNECTION* conn) return 0; } +static void tls_get_client_cert (CONNECTION* conn) +{ + tlssockdata *data = conn->sockdata; + const gnutls_datum_t* crtdata; + gnutls_x509_crt_t clientcrt; + char* dn; + char* cn; + char* cnend; + size_t dnlen; + + /* get our cert CN if we have one */ + if (!(crtdata = gnutls_certificate_get_ours (data->state))) + return; + + if (gnutls_x509_crt_init (&clientcrt) < 0) + { + dprint (1, (debugfile, "Failed to init gnutls crt\n")); + return; + } + if (gnutls_x509_crt_import (clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0) + { + dprint (1, (debugfile, "Failed to import gnutls client crt\n")); + goto err_crt; + } + /* get length of DN */ + dnlen = 0; + gnutls_x509_crt_get_dn (clientcrt, NULL, &dnlen); + if (!(dn = calloc (1, dnlen))) + { + dprint (1, (debugfile, "could not allocate DN\n")); + goto err_crt; + } + gnutls_x509_crt_get_dn (clientcrt, dn, &dnlen); + dprint (2, (debugfile, "client certificate DN: %s\n", dn)); + + /* extract CN to use as external user name */ + if (!(cn = strstr (dn, "CN="))) + { + dprint (1, (debugfile, "no CN found in DN\n")); + goto err_dn; + } + cn += 3; + + if ((cnend = strstr (dn, ",EMAIL="))) + *cnend = '\0'; + + /* if we are using a client cert, SASL may expect an external auth name */ + mutt_account_getuser (&conn->account); + +err_dn: + FREE (&dn); +err_crt: + gnutls_x509_crt_deinit (clientcrt); +} + static int protocol_priority[] = {GNUTLS_TLS1, GNUTLS_SSL3, 0}; /* tls_negotiate: After TLS state has been initialised, attempt to negotiate @@ -199,10 +268,18 @@ static int tls_negotiate (CONNECTION * conn) GNUTLS_X509_FMT_PEM); } -/* - gnutls_set_x509_client_key (data->xcred, "", ""); - gnutls_set_x509_cert_callback (data->xcred, cert_callback); -*/ + if (SslClientCert) + { + dprint (2, (debugfile, "Using client certificate %s\n", SslClientCert)); + gnutls_certificate_set_x509_key_file (data->xcred, SslClientCert, + SslClientCert, GNUTLS_X509_FMT_PEM); + } + +#if HAVE_DECL_GNUTLS_VERIFY_DISABLE_TIME_CHECKS + /* disable checking certificate activation/expiration times + in gnutls, we do the checks ourselves */ + gnutls_certificate_set_verify_flags(data->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS); +#endif gnutls_init(&data->state, GNUTLS_CLIENT); @@ -273,12 +350,16 @@ static int tls_negotiate (CONNECTION * conn) /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ conn->ssf = gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8; - mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"), - gnutls_protocol_get_name (gnutls_protocol_get_version (data->state)), - gnutls_kx_get_name (gnutls_kx_get (data->state)), - gnutls_cipher_get_name (gnutls_cipher_get (data->state)), - gnutls_mac_get_name (gnutls_mac_get (data->state))); - mutt_sleep (0); + tls_get_client_cert (conn); + + if (!option(OPTNOCURSES)) { + mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"), + gnutls_protocol_get_name (gnutls_protocol_get_version (data->state)), + gnutls_kx_get_name (gnutls_kx_get (data->state)), + gnutls_cipher_get_name (gnutls_cipher_get (data->state)), + gnutls_mac_get_name (gnutls_mac_get (data->state))); + mutt_sleep (0); + } return 0; @@ -343,7 +424,7 @@ static int tls_compare_certificates (const gnutls_datum *peercert) } b64_data.size = fread(b64_data.data, 1, b64_data.size, fd1); - fclose(fd1); + safe_fclose (&fd1); do { ret = gnutls_pem_base64_decode_alloc(NULL, &b64_data, &cert); @@ -431,15 +512,15 @@ static int tls_check_stored_hostname (const gnutls_datum *cert, /* try checking against names stored in stored certs file */ if ((fp = fopen (SslCertFile, "r"))) { - if (regcomp(&preg, "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$", REG_ICASE|REG_EXTENDED) != 0) + if (REGCOMP(&preg, "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$", + REG_ICASE) != 0) { - regfree(&preg); return 0; } buf[0] = '\0'; tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert); - while ((linestr = mutt_read_line(linestr, &linestrsize, fp, &linenum)) != NULL) + while ((linestr = mutt_read_line(linestr, &linestrsize, fp, &linenum, 0)) != NULL) { if(linestr[0] == '#' && linestr[1] == 'H') { @@ -452,7 +533,7 @@ static int tls_check_stored_hostname (const gnutls_datum *cert, { regfree(&preg); FREE(&linestr); - fclose(fp); + safe_fclose (&fp); return 1; } } @@ -460,182 +541,188 @@ static int tls_check_stored_hostname (const gnutls_datum *cert, } regfree(&preg); - fclose(fp); + safe_fclose (&fp); } /* not found a matching name */ return 0; } -static int tls_check_certificate (CONNECTION* conn) +static int tls_check_preauth (const gnutls_datum_t *certdata, + gnutls_certificate_status certstat, + const char *hostname, int chainidx, int* certerr, + int* savedcert) { - tlssockdata *data = conn->sockdata; - gnutls_session state = data->state; - char helpstr[LONG_STRING]; - char buf[SHORT_STRING]; - char fpbuf[SHORT_STRING]; - size_t buflen; - char dn_common_name[SHORT_STRING]; - char dn_email[SHORT_STRING]; - char dn_organization[SHORT_STRING]; - char dn_organizational_unit[SHORT_STRING]; - char dn_locality[SHORT_STRING]; - char dn_province[SHORT_STRING]; - char dn_country[SHORT_STRING]; - MUTTMENU *menu; - int done, row, i, ret; - FILE *fp; - time_t t; - const gnutls_datum *cert_list; - unsigned int cert_list_size = 0; - gnutls_certificate_status certstat; - char datestr[30]; gnutls_x509_crt cert; - gnutls_datum pemdata; - int certerr_expired = 0; - int certerr_notyetvalid = 0; - int certerr_hostname = 0; - int certerr_nottrusted = 0; - int certerr_revoked = 0; - int certerr_signernotca = 0; - - if (gnutls_auth_get_type(state) != GNUTLS_CRD_CERTIFICATE) - { - mutt_error (_("Unable to get certificate from peer")); - mutt_sleep (2); - return 0; - } - - certstat = gnutls_certificate_verify_peers(state); - - if (certstat == GNUTLS_E_NO_CERTIFICATE_FOUND) - { - mutt_error (_("Unable to get certificate from peer")); - mutt_sleep (2); - return 0; - } - if (certstat < 0) - { - mutt_error (_("Certificate verification error (%s)"), gnutls_strerror(certstat)); - mutt_sleep (2); - return 0; - } - /* We only support X.509 certificates (not OpenPGP) at the moment */ - if (gnutls_certificate_type_get(state) != GNUTLS_CRT_X509) - { - mutt_error (_("Certificate is not X.509")); - mutt_sleep (2); - return 0; - } + *certerr = CERTERR_VALID; + *savedcert = 0; - if (gnutls_x509_crt_init(&cert) < 0) + if (gnutls_x509_crt_init (&cert) < 0) { mutt_error (_("Error initialising gnutls certificate data")); mutt_sleep (2); - return 0; - } - - cert_list = gnutls_certificate_get_peers(state, &cert_list_size); - if (!cert_list) - { - mutt_error (_("Unable to get certificate from peer")); - mutt_sleep (2); - return 0; + return -1; } - /* FIXME: Currently only check first certificate in chain. */ - if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + if (gnutls_x509_crt_import (cert, certdata, GNUTLS_X509_FMT_DER) < 0) { mutt_error (_("Error processing certificate data")); mutt_sleep (2); - return 0; - } - - if (gnutls_x509_crt_get_expiration_time(cert) < time(NULL)) - { - certerr_expired = 1; + gnutls_x509_crt_deinit (cert); + return -1; } - if (gnutls_x509_crt_get_activation_time(cert) > time(NULL)) + if (option (OPTSSLVERIFYDATES) != M_NO) { - certerr_notyetvalid = 1; + if (gnutls_x509_crt_get_expiration_time (cert) < time(NULL)) + *certerr |= CERTERR_EXPIRED; + if (gnutls_x509_crt_get_activation_time (cert) > time(NULL)) + *certerr |= CERTERR_NOTYETVALID; } - if (!gnutls_x509_crt_check_hostname(cert, conn->account.host) && - !tls_check_stored_hostname (&cert_list[0], conn->account.host)) - { - certerr_hostname = 1; - } + if (chainidx == 0 && option (OPTSSLVERIFYHOST) != M_NO + && !gnutls_x509_crt_check_hostname (cert, hostname) + && !tls_check_stored_hostname (certdata, hostname)) + *certerr |= CERTERR_HOSTNAME; /* see whether certificate is in our cache (certificates file) */ - if (tls_compare_certificates (&cert_list[0])) + if (tls_compare_certificates (certdata)) { - if (certstat & GNUTLS_CERT_INVALID) + *savedcert = 1; + + if (chainidx == 0 && certstat & GNUTLS_CERT_INVALID) { /* doesn't matter - have decided is valid because server - certificate is in our trusted cache */ + certificate is in our trusted cache */ certstat ^= GNUTLS_CERT_INVALID; } - if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) + if (chainidx == 0 && certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { /* doesn't matter that we haven't found the signer, since - certificate is in our trusted cache */ + certificate is in our trusted cache */ certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; } - if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) + if (chainidx <= 1 && certstat & GNUTLS_CERT_SIGNER_NOT_CA) { /* Hmm. Not really sure how to handle this, but let's say - that we don't care if the CA certificate hasn't got the - correct X.509 basic constraints if server certificate is - in our cache. */ + that we don't care if the CA certificate hasn't got the + correct X.509 basic constraints if server or first signer + certificate is in our cache. */ certstat ^= GNUTLS_CERT_SIGNER_NOT_CA; } + if (chainidx == 0 && certstat & GNUTLS_CERT_INSECURE_ALGORITHM) + { + /* doesn't matter that it was signed using an insecure + algorithm, since certificate is in our trusted cache */ + certstat ^= GNUTLS_CERT_INSECURE_ALGORITHM; + } } if (certstat & GNUTLS_CERT_REVOKED) { - certerr_revoked = 1; + *certerr |= CERTERR_REVOKED; certstat ^= GNUTLS_CERT_REVOKED; } if (certstat & GNUTLS_CERT_INVALID) { - certerr_nottrusted = 1; + *certerr |= CERTERR_NOTTRUSTED; certstat ^= GNUTLS_CERT_INVALID; } if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) { /* NB: already cleared if cert in cache */ - certerr_nottrusted = 1; + *certerr |= CERTERR_NOTTRUSTED; certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND; } if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) { /* NB: already cleared if cert in cache */ - certerr_signernotca = 1; + *certerr |= CERTERR_SIGNERNOTCA; certstat ^= GNUTLS_CERT_SIGNER_NOT_CA; } - /* OK if signed by (or is) a trusted certificate */ - /* we've been zeroing the interesting bits in certstat - - don't return OK if there are any unhandled bits we don't - understand */ - if (!(certerr_expired || certerr_notyetvalid || - certerr_hostname || certerr_nottrusted) && certstat == 0) + if (certstat & GNUTLS_CERT_INSECURE_ALGORITHM) { - gnutls_x509_crt_deinit(cert); - return 1; + /* NB: already cleared if cert in cache */ + *certerr |= CERTERR_INSECUREALG; + certstat ^= GNUTLS_CERT_INSECURE_ALGORITHM; } + gnutls_x509_crt_deinit (cert); + + /* we've been zeroing the interesting bits in certstat - + don't return OK if there are any unhandled bits we don't + understand */ + if (*certerr == CERTERR_VALID && certstat == 0) + return 0; + + return -1; +} + +static int tls_check_one_certificate (const gnutls_datum_t *certdata, + gnutls_certificate_status certstat, + const char* hostname, int idx, int len) +{ + int certerr, savedcert; + gnutls_x509_crt cert; + char buf[SHORT_STRING]; + char fpbuf[SHORT_STRING]; + size_t buflen; + char dn_common_name[SHORT_STRING]; + char dn_email[SHORT_STRING]; + char dn_organization[SHORT_STRING]; + char dn_organizational_unit[SHORT_STRING]; + char dn_locality[SHORT_STRING]; + char dn_province[SHORT_STRING]; + char dn_country[SHORT_STRING]; + time_t t; + char datestr[30]; + MUTTMENU *menu; + char helpstr[LONG_STRING]; + char title[STRING]; + FILE *fp; + gnutls_datum pemdata; + int i, row, done, ret; + + if (!tls_check_preauth (certdata, certstat, hostname, idx, &certerr, + &savedcert)) + return 1; + + /* skip signers if insecure algorithm was used */ + if (idx && (certerr & CERTERR_INSECUREALG)) + { + if (idx == 1) + { + mutt_error (_("Warning: Server certificate was signed using an insecure algorithm")); + mutt_sleep (2); + } + return 0; + } /* interactive check from user */ - menu = mutt_new_menu (); + if (gnutls_x509_crt_init (&cert) < 0) + { + mutt_error (_("Error initialising gnutls certificate data")); + mutt_sleep (2); + return 0; + } + + if (gnutls_x509_crt_import (cert, certdata, GNUTLS_X509_FMT_DER) < 0) + { + mutt_error (_("Error processing certificate data")); + mutt_sleep (2); + gnutls_x509_crt_deinit (cert); + return -1; + } + + menu = mutt_new_menu (-1); menu->max = 25; menu->dialog = (char **) safe_calloc (1, menu->max * sizeof (char *)); for (i = 0; i < menu->max; i++) @@ -645,33 +732,33 @@ static int tls_check_certificate (CONNECTION* conn) strfcpy (menu->dialog[row], _("This certificate belongs to:"), SHORT_STRING); row++; - buflen = sizeof(dn_common_name); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, - dn_common_name, &buflen) != 0) + buflen = sizeof (dn_common_name); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, + dn_common_name, &buflen) != 0) dn_common_name[0] = '\0'; - buflen = sizeof(dn_email); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, - dn_email, &buflen) != 0) + buflen = sizeof (dn_email); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, + dn_email, &buflen) != 0) dn_email[0] = '\0'; - buflen = sizeof(dn_organization); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, - dn_organization, &buflen) != 0) + buflen = sizeof (dn_organization); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, + dn_organization, &buflen) != 0) dn_organization[0] = '\0'; - buflen = sizeof(dn_organizational_unit); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, - dn_organizational_unit, &buflen) != 0) + buflen = sizeof (dn_organizational_unit); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, + dn_organizational_unit, &buflen) != 0) dn_organizational_unit[0] = '\0'; - buflen = sizeof(dn_locality); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, - dn_locality, &buflen) != 0) + buflen = sizeof (dn_locality); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, + dn_locality, &buflen) != 0) dn_locality[0] = '\0'; - buflen = sizeof(dn_province); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, - dn_province, &buflen) != 0) + buflen = sizeof (dn_province); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, + dn_province, &buflen) != 0) dn_province[0] = '\0'; - buflen = sizeof(dn_country); - if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, - dn_country, &buflen) != 0) + buflen = sizeof (dn_country); + if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, + dn_country, &buflen) != 0) dn_country[0] = '\0'; snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name, dn_email); @@ -680,37 +767,37 @@ static int tls_check_certificate (CONNECTION* conn) snprintf (menu->dialog[row++], SHORT_STRING, " %s %s %s", dn_locality, dn_province, dn_country); row++; - + strfcpy (menu->dialog[row], _("This certificate was issued by:"), SHORT_STRING); row++; - buflen = sizeof(dn_common_name); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, - dn_common_name, &buflen) != 0) + buflen = sizeof (dn_common_name); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, + dn_common_name, &buflen) != 0) dn_common_name[0] = '\0'; - buflen = sizeof(dn_email); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, - dn_email, &buflen) != 0) + buflen = sizeof (dn_email); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, + dn_email, &buflen) != 0) dn_email[0] = '\0'; - buflen = sizeof(dn_organization); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, - dn_organization, &buflen) != 0) + buflen = sizeof (dn_organization); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, + dn_organization, &buflen) != 0) dn_organization[0] = '\0'; - buflen = sizeof(dn_organizational_unit); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, - dn_organizational_unit, &buflen) != 0) + buflen = sizeof (dn_organizational_unit); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, + dn_organizational_unit, &buflen) != 0) dn_organizational_unit[0] = '\0'; - buflen = sizeof(dn_locality); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, - dn_locality, &buflen) != 0) + buflen = sizeof (dn_locality); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, + dn_locality, &buflen) != 0) dn_locality[0] = '\0'; - buflen = sizeof(dn_province); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, - dn_province, &buflen) != 0) + buflen = sizeof (dn_province); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, + dn_province, &buflen) != 0) dn_province[0] = '\0'; - buflen = sizeof(dn_country); - if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, - dn_country, &buflen) != 0) + buflen = sizeof (dn_country); + if (gnutls_x509_crt_get_issuer_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, + dn_country, &buflen) != 0) dn_country[0] = '\0'; snprintf (menu->dialog[row++], SHORT_STRING, " %s %s", dn_common_name, dn_email); @@ -722,51 +809,56 @@ static int tls_check_certificate (CONNECTION* conn) snprintf (menu->dialog[row++], SHORT_STRING, _("This certificate is valid")); - t = gnutls_x509_crt_get_activation_time(cert); - snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), - tls_make_date(t, datestr, 30)); + t = gnutls_x509_crt_get_activation_time (cert); + snprintf (menu->dialog[row++], SHORT_STRING, _(" from %s"), + tls_make_date (t, datestr, 30)); - t = gnutls_x509_crt_get_expiration_time(cert); - snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"), - tls_make_date(t, datestr, 30)); + t = gnutls_x509_crt_get_expiration_time (cert); + snprintf (menu->dialog[row++], SHORT_STRING, _(" to %s"), + tls_make_date (t, datestr, 30)); fpbuf[0] = '\0'; - tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]); + tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), certdata); snprintf (menu->dialog[row++], SHORT_STRING, _("SHA1 Fingerprint: %s"), fpbuf); fpbuf[0] = '\0'; - tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]); + tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), certdata); snprintf (menu->dialog[row++], SHORT_STRING, _("MD5 Fingerprint: %s"), fpbuf); - if (certerr_notyetvalid) + if (certerr & CERTERR_NOTYETVALID) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate is not yet valid"), SHORT_STRING); } - if (certerr_expired) + if (certerr & CERTERR_EXPIRED) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate has expired"), SHORT_STRING); } - if (certerr_revoked) + if (certerr & CERTERR_REVOKED) { row++; strfcpy (menu->dialog[row], _("WARNING: Server certificate has been revoked"), SHORT_STRING); } - if (certerr_hostname) + if (certerr & CERTERR_HOSTNAME) { row++; strfcpy (menu->dialog[row], _("WARNING: Server hostname does not match certificate"), SHORT_STRING); } - if (certerr_signernotca) + if (certerr & CERTERR_SIGNERNOTCA) { row++; strfcpy (menu->dialog[row], _("WARNING: Signer of server certificate is not a CA"), SHORT_STRING); } - menu->title = _("TLS/SSL Certificate check"); + snprintf (title, sizeof (title), + _("SSL Certificate check (certificate %d of %d in chain)"), + len - idx, len); + menu->title = title; /* certificates with bad dates, or that are revoked, must be - accepted manually each and every time */ - if (SslCertFile && !certerr_expired && !certerr_notyetvalid && !certerr_revoked) + accepted manually each and every time */ + if (SslCertFile && !savedcert + && !(certerr & (CERTERR_EXPIRED | CERTERR_NOTYETVALID + | CERTERR_REVOKED))) { menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); menu->keys = _("roa"); @@ -776,7 +868,7 @@ static int tls_check_certificate (CONNECTION* conn) menu->prompt = _("(r)eject, accept (o)nce"); menu->keys = _("ro"); } - + helpstr[0] = '\0'; mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT); safe_strcat (helpstr, sizeof (helpstr), buf); @@ -800,26 +892,26 @@ static int tls_check_certificate (CONNECTION* conn) if ((fp = fopen (SslCertFile, "a"))) { /* save hostname if necessary */ - if (certerr_hostname) + if (certerr & CERTERR_HOSTNAME) { - fprintf(fp, "#H %s %s\n", conn->account.host, fpbuf); + fprintf(fp, "#H %s %s\n", hostname, fpbuf); done = 1; } - if (certerr_nottrusted) + if (certerr & CERTERR_NOTTRUSTED) { done = 0; - ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0], + ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", certdata, &pemdata); if (ret == 0) { - if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1) + if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) { done = 1; } - gnutls_free(pemdata.data); + gnutls_free (pemdata.data); } } - fclose (fp); + safe_fclose (&fp); } if (!done) { @@ -839,6 +931,107 @@ static int tls_check_certificate (CONNECTION* conn) } unset_option (OPTUNBUFFEREDINPUT); mutt_menuDestroy (&menu); - gnutls_x509_crt_deinit(cert); + gnutls_x509_crt_deinit (cert); + return (done == 2); } + +/* sanity-checking wrapper for gnutls_certificate_verify_peers */ +static gnutls_certificate_status tls_verify_peers (gnutls_session tlsstate) +{ + gnutls_certificate_status certstat; + + certstat = gnutls_certificate_verify_peers (tlsstate); + if (!certstat) + return certstat; + + if (certstat == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + if (certstat < 0) + { + mutt_error (_("Certificate verification error (%s)"), + gnutls_strerror (certstat)); + mutt_sleep (2); + return 0; + } + + /* We only support X.509 certificates (not OpenPGP) at the moment */ + if (gnutls_certificate_type_get (tlsstate) != GNUTLS_CRT_X509) + { + mutt_error (_("Certificate is not X.509")); + mutt_sleep (2); + return 0; + } + + return certstat; +} + +static int tls_check_certificate (CONNECTION* conn) +{ + tlssockdata *data = conn->sockdata; + gnutls_session state = data->state; + const gnutls_datum *cert_list; + unsigned int cert_list_size = 0; + gnutls_certificate_status certstat; + int certerr, i, preauthrc, savedcert, rc = 0; + + if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) + { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + + certstat = tls_verify_peers (state); + + cert_list = gnutls_certificate_get_peers (state, &cert_list_size); + if (!cert_list) + { + mutt_error (_("Unable to get certificate from peer")); + mutt_sleep (2); + return 0; + } + + /* tls_verify_peers doesn't check hostname or expiration, so walk + * from most specific to least checking these. If we see a saved certificate, + * its status short-circuits the remaining checks. */ + preauthrc = 0; + for (i = 0; i < cert_list_size; i++) { + rc = tls_check_preauth(&cert_list[i], certstat, conn->account.host, i, + &certerr, &savedcert); + preauthrc += rc; + + if (savedcert) + { + if (!preauthrc) + return 1; + else + break; + } + } + + /* then check interactively, starting from chain root */ + for (i = cert_list_size - 1; i >= 0; i--) + { + rc = tls_check_one_certificate (&cert_list[i], certstat, conn->account.host, + i, cert_list_size); + + /* add signers to trust set, then reverify */ + if (i && rc) { + rc = gnutls_certificate_set_x509_trust_mem (data->xcred, &cert_list[i], + GNUTLS_X509_FMT_DER); + if (rc != 1) + dprint (1, (debugfile, "error trusting certificate %d: %d\n", i, rc)); + + certstat = tls_verify_peers (state); + if (!certstat) + return 1; + } + } + + return rc; +}