diff -r 000000000000 -r d0f3a028347a loudmouth/src/lm-ssl-gnutls.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/loudmouth/src/lm-ssl-gnutls.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,312 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003-2006 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include + +#include "lm-debug.h" +#include "lm-error.h" +#include "lm-ssl-base.h" +#include "lm-ssl-internals.h" + +#ifdef HAVE_GNUTLS + +#include + +#define CA_PEM_FILE "/etc/ssl/certs/ca-certificates.crt" + +struct _LmSSL { + LmSSLBase base; + + gnutls_session gnutls_session; + gnutls_certificate_credentials gnutls_xcred; + gboolean started; +}; + +static gboolean ssl_verify_certificate (LmSSL *ssl, + const gchar *server); + +static gboolean +ssl_verify_certificate (LmSSL *ssl, const gchar *server) +{ + LmSSLBase *base; + unsigned int status; + int rc; + + base = LM_SSL_BASE (ssl); + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + rc = gnutls_certificate_verify_peers2 (ssl->gnutls_session, &status); + + if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) { + if (base->func (ssl, + LM_SSL_STATUS_NO_CERT_FOUND, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (rc != 0) { + if (base->func (ssl, + LM_SSL_STATUS_GENERIC_ERROR, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) { + if (base->func (ssl, + LM_SSL_STATUS_NO_CERT_FOUND, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (status & GNUTLS_CERT_INVALID + || status & GNUTLS_CERT_REVOKED) { + if (base->func (ssl, LM_SSL_STATUS_UNTRUSTED_CERT, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (gnutls_certificate_expiration_time_peers (ssl->gnutls_session) < time (0)) { + if (base->func (ssl, LM_SSL_STATUS_CERT_EXPIRED, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (gnutls_certificate_activation_time_peers (ssl->gnutls_session) > time (0)) { + if (base->func (ssl, LM_SSL_STATUS_CERT_NOT_ACTIVATED, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (gnutls_certificate_type_get (ssl->gnutls_session) == GNUTLS_CRT_X509) { + const gnutls_datum* cert_list; + guint cert_list_size; + size_t digest_size; + gnutls_x509_crt cert; + + cert_list = gnutls_certificate_get_peers (ssl->gnutls_session, &cert_list_size); + if (cert_list == NULL) { + if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + gnutls_x509_crt_init (&cert); + + if (gnutls_x509_crt_import (cert, &cert_list[0], + GNUTLS_X509_FMT_DER) != 0) { + if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + if (!gnutls_x509_crt_check_hostname (cert, server)) { + if (base->func (ssl, LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + gnutls_x509_crt_deinit (cert); + + digest_size = sizeof (base->fingerprint); + + if (gnutls_fingerprint (GNUTLS_DIG_MD5, &cert_list[0], + base->fingerprint, + &digest_size) >= 0) { + if (base->expected_fingerprint && + memcmp (base->expected_fingerprint, + base->fingerprint, + digest_size) && + base->func (ssl, + LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + else if (base->func (ssl, LM_SSL_STATUS_GENERIC_ERROR, + base->func_data) != LM_SSL_RESPONSE_CONTINUE) { + return FALSE; + } + } + + return TRUE; +} + +/* From lm-ssl-protected.h */ + +LmSSL * +_lm_ssl_new (const gchar *expected_fingerprint, + LmSSLFunction ssl_function, + gpointer user_data, + GDestroyNotify notify) +{ + LmSSL *ssl; + + ssl = g_new0 (LmSSL, 1); + + _lm_ssl_base_init ((LmSSLBase *) ssl, + expected_fingerprint, + ssl_function, user_data, notify); + + return ssl; +} + +void +_lm_ssl_initialize (LmSSL *ssl) +{ + gnutls_global_init (); + gnutls_certificate_allocate_credentials (&ssl->gnutls_xcred); + gnutls_certificate_set_x509_trust_file(ssl->gnutls_xcred, + CA_PEM_FILE, + GNUTLS_X509_FMT_PEM); +} + +gboolean +_lm_ssl_begin (LmSSL *ssl, gint fd, const gchar *server, GError **error) +{ + int ret; + gboolean auth_ok = TRUE; + const int cert_type_priority[] = + { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 }; + const int compression_priority[] = + { GNUTLS_COMP_DEFLATE, GNUTLS_COMP_NULL, 0 }; + + gnutls_init (&ssl->gnutls_session, GNUTLS_CLIENT); + gnutls_set_default_priority (ssl->gnutls_session); + gnutls_certificate_type_set_priority (ssl->gnutls_session, + cert_type_priority); + gnutls_compression_set_priority (ssl->gnutls_session, + compression_priority); + gnutls_credentials_set (ssl->gnutls_session, + GNUTLS_CRD_CERTIFICATE, + ssl->gnutls_xcred); + + gnutls_transport_set_ptr (ssl->gnutls_session, + (gnutls_transport_ptr_t) fd); + + ret = gnutls_handshake (ssl->gnutls_session); + + if (ret >= 0) { + auth_ok = ssl_verify_certificate (ssl, server); + } + + if (ret < 0 || !auth_ok) { + char *errmsg; + + gnutls_perror (ret); + + if (!auth_ok) { + errmsg = "*** GNUTLS authentication error"; + } else { + errmsg = "*** GNUTLS handshake failed"; + } + + g_set_error (error, + LM_ERROR, LM_ERROR_CONNECTION_OPEN, + errmsg); + + return FALSE; + } + lm_verbose ("GNUTLS negotiated compression: %s", + gnutls_compression_get_name (gnutls_compression_get + (ssl->gnutls_session))); + + ssl->started = TRUE; + + return TRUE; +} + +GIOStatus +_lm_ssl_read (LmSSL *ssl, gchar *buf, gint len, gsize *bytes_read) +{ + GIOStatus status; + gint b_read; + + *bytes_read = 0; + b_read = gnutls_record_recv (ssl->gnutls_session, buf, len); + + if (b_read == GNUTLS_E_AGAIN) { + status = G_IO_STATUS_AGAIN; + } + else if (b_read == 0) { + status = G_IO_STATUS_EOF; + } + else if (b_read < 0) { + status = G_IO_STATUS_ERROR; + } else { + *bytes_read = (guint) b_read; + status = G_IO_STATUS_NORMAL; + } + + return status; +} + +gint +_lm_ssl_send (LmSSL *ssl, const gchar *str, gint len) +{ + gint bytes_written; + + bytes_written = gnutls_record_send (ssl->gnutls_session, str, len); + + while (bytes_written < 0) { + if (bytes_written != GNUTLS_E_INTERRUPTED && + bytes_written != GNUTLS_E_AGAIN) { + return -1; + } + + bytes_written = gnutls_record_send (ssl->gnutls_session, + str, len); + } + + return bytes_written; +} + +void +_lm_ssl_close (LmSSL *ssl) +{ + if (!ssl->started) + return; + + gnutls_deinit (ssl->gnutls_session); + gnutls_certificate_free_credentials (ssl->gnutls_xcred); + gnutls_global_deinit (); +} + +void +_lm_ssl_free (LmSSL *ssl) +{ + _lm_ssl_base_free_fields (LM_SSL_BASE (ssl)); + g_free (ssl); +} + +#endif /* HAVE_GNUTLS */