/* tls_negotiate: After TLS state has been initialised, attempt to negotiate * TLS over the wire, including certificate checks. */ static int tls_negotiate (CONNECTION * conn) { tlssockdata *data; int err; size_t nproto = 0; /* number of tls/ssl protocols */ data = (tlssockdata *) safe_calloc (1, sizeof (tlssockdata)); conn->sockdata = data; err = gnutls_certificate_allocate_credentials (&data->xcred); if (err < 0) { FREE(&conn->sockdata); mutt_error ("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err)); mutt_sleep (2); return -1; } gnutls_certificate_set_x509_trust_file (data->xcred, SslCertFile, GNUTLS_X509_FMT_PEM); /* ignore errors, maybe file doesn't exist yet */ if (SslCACertFile) { gnutls_certificate_set_x509_trust_file (data->xcred, SslCACertFile, GNUTLS_X509_FMT_PEM); } 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 if ((err = gnutls_init(&data->state, GNUTLS_CLIENT))) { mutt_error ("gnutls_handshake: %s", gnutls_strerror(err)); mutt_sleep (2); goto fail; } /* set socket */ gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)conn->fd); if (option(OPTTLSV1_2)) protocol_priority[nproto++] = GNUTLS_TLS1_2; if (option(OPTTLSV1_1)) protocol_priority[nproto++] = GNUTLS_TLS1_1; if (option(OPTTLSV1)) protocol_priority[nproto++] = GNUTLS_TLS1; if (option(OPTSSLV3)) protocol_priority[nproto++] = GNUTLS_SSL3; protocol_priority[nproto] = 0; /* disable TLS/SSL protocols as needed */ if (nproto == 0) { mutt_error (_("All available protocols for TLS/SSL connection disabled")); goto fail; } /* else use the list set above */ /* We use default priorities (see gnutls documentation), except for protocol version */ gnutls_set_default_priority (data->state); gnutls_protocol_set_priority (data->state, protocol_priority); if (SslDHPrimeBits > 0) { gnutls_dh_set_prime_bits (data->state, SslDHPrimeBits); } /* gnutls_set_cred (data->state, GNUTLS_ANON, NULL); */ gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred); err = gnutls_handshake(data->state); while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) { err = gnutls_handshake(data->state); } if (err < 0) { if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) { mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err), gnutls_alert_get_name(gnutls_alert_get(data->state))); } else { mutt_error("gnutls_handshake: %s", gnutls_strerror(err)); } mutt_sleep (2); goto fail; } if (!tls_check_certificate(conn)) goto fail; /* set Security Strength Factor (SSF) for SASL */ /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ conn->ssf = gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8; 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; fail: gnutls_certificate_free_credentials (data->xcred); gnutls_deinit (data->state); FREE(&conn->sockdata); return -1; }
/** * tls_negotiate - Negotiate TLS connection * @param conn Connection to a server * @retval 0 Success * @retval -1 Error * * After TLS state has been initialized, attempt to negotiate TLS over the * wire, including certificate checks. */ static int tls_negotiate(struct Connection *conn) { struct TlsSockData *data = mutt_mem_calloc(1, sizeof(struct TlsSockData)); conn->sockdata = data; int err = gnutls_certificate_allocate_credentials(&data->xcred); if (err < 0) { FREE(&conn->sockdata); mutt_error("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err)); return -1; } gnutls_certificate_set_x509_trust_file(data->xcred, C_CertificateFile, GNUTLS_X509_FMT_PEM); /* ignore errors, maybe file doesn't exist yet */ if (C_SslCaCertificatesFile) { gnutls_certificate_set_x509_trust_file(data->xcred, C_SslCaCertificatesFile, GNUTLS_X509_FMT_PEM); } if (C_SslClientCert) { mutt_debug(LL_DEBUG2, "Using client certificate %s\n", C_SslClientCert); gnutls_certificate_set_x509_key_file(data->xcred, C_SslClientCert, C_SslClientCert, GNUTLS_X509_FMT_PEM); } #ifdef 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 err = gnutls_init(&data->state, GNUTLS_CLIENT); if (err) { mutt_error("gnutls_handshake: %s", gnutls_strerror(err)); goto fail; } /* set socket */ gnutls_transport_set_ptr(data->state, (gnutls_transport_ptr_t)(long) conn->fd); if (gnutls_server_name_set(data->state, GNUTLS_NAME_DNS, conn->account.host, mutt_str_strlen(conn->account.host))) { mutt_error(_("Warning: unable to set TLS SNI host name")); } if (tls_set_priority(data) < 0) { goto fail; } if (C_SslMinDhPrimeBits > 0) { gnutls_dh_set_prime_bits(data->state, C_SslMinDhPrimeBits); } /* gnutls_set_cred (data->state, GNUTLS_ANON, NULL); */ gnutls_credentials_set(data->state, GNUTLS_CRD_CERTIFICATE, data->xcred); err = gnutls_handshake(data->state); while (err == GNUTLS_E_AGAIN) { err = gnutls_handshake(data->state); } if (err < 0) { if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) { mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err), gnutls_alert_get_name(gnutls_alert_get(data->state))); } else { mutt_error("gnutls_handshake: %s", gnutls_strerror(err)); } goto fail; } if (tls_check_certificate(conn) == 0) goto fail; /* set Security Strength Factor (SSF) for SASL */ /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ conn->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(data->state)) * 8; tls_get_client_cert(conn); if (!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; fail: gnutls_certificate_free_credentials(data->xcred); gnutls_deinit(data->state); FREE(&conn->sockdata); return -1; }
/*-------------------------------------------------------------------------*/ svalue_t * f_tls_check_certificate(svalue_t *sp) /* EFUN tls_check_certificate() * * mixed *tls_check_certificate(object obj); * mixed *tls_check_certificate(object obj, int extra); * * tls_check_certificate() checks the certificate of the secured * connection bound to <obj> (default is the current object). If * <obj> is not interactive, or if TLS is not available, an error * is thrown. * * If <obj> doesn't have a secure connection up and running, an * error is thrown. * Otherwise, the result is an array with these values: * * int [0] : Result code of SSL_get_verify_result (see man 1 verify * subsection DIAGNOSTICS for possible values) * array [1] : array with 3*n entries of extra x509 data. * structure is: * 3*i : numerical form of object name, e.g. "2.5.4.3" * 3*i + 1: long or short name if available, e.g. "commonName" * 3*i + 2: value * array [2] : if extra is set: * array with 3*n entries of x509 extension data * data structure is: * 3*i : numerical form of extension name * 3*i + 1: long or short name of extension name if available * 3*i + 2: array of strings with the data structure of [1] * * Note: a x509 certificate can have more than one object with the same name * * See associated documentation for code that generates more convient mapping * data structures */ { vector_t *v = NULL; interactive_t *ip; int more; /* more information requested */ more = sp->u.number; free_svalue(sp--); if (!tls_available()) errorf("tls_check_certificate(): TLS layer hasn't been initialized.\n"); if (!O_SET_INTERACTIVE(ip, sp->u.ob)) errorf("Bad arg 1 to tls_check_certificate(): " "object not interactive.\n"); if (ip->tls_status != TLS_ACTIVE) errorf("tls_check_certificate(): object doesn't have a secure connection.\n"); if (more < 0 || more > 1) errorf("tls_check_certificate(): invalid flag passed as second argument.\n"); v = tls_check_certificate(ip, (more == 1) ? MY_TRUE : MY_FALSE); free_svalue(sp); if (v != NULL) put_array(sp, v); else put_number(sp, 0); return sp; } /* tls_check_certificate() */
struct connection_state *initialize_gnutls(intptr_t sd, char *name, Pop3 pc, const char *remote_hostname) { static int gnutls_initialized; int zok; struct connection_state *scs = malloc(sizeof(struct connection_state)); memset(scs, 0, sizeof(struct connection_state)); /* clears the unprocessed buffer */ scs->pc = pc; assert(sd >= 0); if (gnutls_initialized == 0) { assert(gnutls_global_init() == 0); gnutls_initialized = 1; } assert(gnutls_init(&scs->tls_state, GNUTLS_CLIENT) == 0); { const char *err_pos; if (GNUTLS_E_SUCCESS != gnutls_priority_set_direct(scs->tls_state, tls, &err_pos)) { DMA(DEBUG_ERROR, "Unable to set the priorities to use on the ciphers, " "key exchange methods, macs and/or compression methods.\n" "See 'tls' parameter in config file: '%s'.\n", err_pos); exit(1); } /* no client private key */ if (gnutls_certificate_allocate_credentials(&scs->xcred) < 0) { DMA(DEBUG_ERROR, "gnutls memory error\n"); exit(1); } /* certfile seems to work. */ if (certificate_filename != NULL) { if (!exists(certificate_filename)) { DMA(DEBUG_ERROR, "Certificate file (certfile=) %s not found.\n", certificate_filename); exit(1); } zok = gnutls_certificate_set_x509_trust_file(scs->xcred, (char *) certificate_filename, GNUTLS_X509_FMT_PEM); if (zok < 0) { DMA(DEBUG_ERROR, "GNUTLS did not like your certificate file %s (%d).\n", certificate_filename, zok); gnutls_perror(zok); exit(1); } } gnutls_cred_set(scs->tls_state, GNUTLS_CRD_CERTIFICATE, scs->xcred); gnutls_transport_set_ptr(scs->tls_state, (gnutls_transport_ptr_t) sd); do { zok = gnutls_handshake(scs->tls_state); } while (zok == GNUTLS_E_INTERRUPTED || zok == GNUTLS_E_AGAIN); tls_check_certificate(scs, remote_hostname); } if (zok < 0) { TDM(DEBUG_ERROR, "%s: Handshake failed\n", name); TDM(DEBUG_ERROR, "%s: This may be a problem in gnutls, " "which is under development\n", name); TDM(DEBUG_ERROR, "%s: This copy of wmbiff was compiled with \n" " gnutls version %s.\n", name, LIBGNUTLS_VERSION); gnutls_perror(zok); if (scs->pc->u.pop_imap.serverPort != 143 /* starttls */ ) { TDM(DEBUG_ERROR, "%s: Please run 'gnutls-cli-debug -p %d %s' to test ssl directly.\n" " That tool provides a lower-level test of gnutls with your server.\n", name, scs->pc->u.pop_imap.serverPort, remote_hostname); } gnutls_deinit(scs->tls_state); free(scs); return (NULL); } else { TDM(DEBUG_INFO, "%s: Handshake was completed\n", name); if (scs->pc->debug >= DEBUG_INFO) print_info(scs->tls_state, remote_hostname); scs->sd = sd; scs->name = name; } return (scs); }