loudmouth/lm-ssl-openssl.c
author Mikael Hallendal <micke@imendio.com>
Sun, 29 Apr 2007 22:09:44 +0200
changeset 265 ebfc8419758d
parent 263 23192c7973c0
permissions -rw-r--r--
Bumped the version to 1.2.2

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 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 <config.h>

#include <string.h>
#include <glib.h>

#include "lm-error.h"
#include "lm-ssl-base.h"
#include "lm-ssl-internals.h"

#ifdef HAVE_OPENSSL

#include <openssl/ssl.h>

struct _LmSSL {
	LmSSLBase  base;

	SSL_CTX   *ctx;

	SSL       *session;
/*	gnutls_certificate_client_credentials gnutls_xcred;*/
};

static gboolean       ssl_verify_certificate    (LmSSL       *ssl,
						 const gchar *server);
static GIOStatus      ssl_io_status_from_return (LmSSL       *ssl,
						 gint         error);

static gboolean
ssl_verify_certificate (LmSSL *ssl, const gchar *server)
{
	LmSSLBase   *base;
	int          result;
	LmSSLStatus  status;

	base = LM_SSL_BASE (ssl);

	result = SSL_get_verify_result (ssl->session);

	/* Result values from 'man verify' */
	switch (result) {
	case X509_V_OK:
		return TRUE;
	case X509_V_ERR_CERT_HAS_EXPIRED:
		status = LM_SSL_STATUS_CERT_EXPIRED;
		break;
	case X509_V_ERR_CERT_NOT_YET_VALID:
		status = LM_SSL_STATUS_CERT_NOT_ACTIVATED;
		break;
	case X509_V_ERR_CERT_UNTRUSTED:
		status = LM_SSL_STATUS_UNTRUSTED_CERT;
		break;
	case X509_V_ERR_CERT_REVOKED:
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
	case X509_V_ERR_UNABLE_TO_GET_CRL:
	case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
	case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
	case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
	case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
	case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
	case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
	case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
	case X509_V_ERR_OUT_OF_MEM:
	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
	case X509_V_ERR_APPLICATION_VERIFICATION:
	case X509_V_ERR_CERT_CHAIN_TOO_LONG:
	case X509_V_ERR_CERT_SIGNATURE_FAILURE:
	case X509_V_ERR_CRL_SIGNATURE_FAILURE:
	case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
	case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
	case X509_V_ERR_INVALID_CA:
	case X509_V_ERR_PATH_LENGTH_EXCEEDED:
	case X509_V_ERR_INVALID_PURPOSE:
	case X509_V_ERR_CERT_REJECTED:
	case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
	case X509_V_ERR_AKID_SKID_MISMATCH:
	case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
	case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
		/* FIXME: These doesn't map very well to LmSSLStatus right 
		 *        now. */
		status = LM_SSL_STATUS_GENERIC_ERROR;
		break;
	default:
		status = LM_SSL_STATUS_GENERIC_ERROR;
		g_warning ("Unmatched error code '%d' from SSL_get_verify_result", result);
		break;
	};

	if (base->func (ssl, status, base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
		return FALSE;
	}

	return TRUE;
}

static GIOStatus
ssl_io_status_from_return (LmSSL *ssl, gint ret)
{
	gint      error;
	GIOStatus status;

	if (ret > 0) {
		return G_IO_STATUS_NORMAL;
	}

	error = SSL_get_error (ssl->session, ret);

	if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
		status = G_IO_STATUS_AGAIN;
	} 
	else if (error == SSL_ERROR_ZERO_RETURN) {
		status = G_IO_STATUS_EOF;
	} else {
		status = G_IO_STATUS_ERROR;
	}

	return status;
}

/* 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) 
{
	static gboolean  initialized = FALSE;
	SSL_METHOD      *meth;

	if (!initialized) {
		SSL_library_init ();

		/* FIXME: Is this needed when we are not in debug? */
		SSL_load_error_strings ();
		initialized = TRUE;
	}

	meth = SSLv23_method ();
	ssl->ctx = SSL_CTX_new (meth);
}

gboolean
_lm_ssl_begin (LmSSL *ssl, gint fd, const gchar *server, GError **error)
{
	BIO       *sbio;
	GIOStatus  status;

	if (!ssl->ctx) {
		g_set_error (error,
			     LM_ERROR, LM_ERROR_CONNECTION_OPEN,
			     "No SSL Context for OpenSSL");
		return FALSE;
	}

	ssl->session = SSL_new (ssl->ctx);
	if (ssl->session == NULL) {
		g_set_error (error,
			     LM_ERROR, LM_ERROR_CONNECTION_OPEN,
			     "Failed to create an SSL session through OpenSSL");
		return FALSE;
	}
	
	sbio = BIO_new_socket (fd, BIO_NOCLOSE);
	SSL_set_bio (ssl->session, sbio, sbio);

	while (TRUE) {
		gint ret;

		ret = SSL_connect (ssl->session);

		if (ret > 0) {
			/* Successful */
			break;
		}
		else {
			status = ssl_io_status_from_return (ssl, ret);
			if (status == G_IO_STATUS_AGAIN) {
				/* Try again */
				continue;
			} else {
				g_set_error (error, 
					     LM_ERROR, LM_ERROR_CONNECTION_OPEN,
					     "*** OpenSSL handshake failed");
				return FALSE;
			}

		}
	}

	if (!ssl_verify_certificate (ssl, server)) {
		g_set_error (error,
			     LM_ERROR, LM_ERROR_CONNECTION_OPEN,
			     "*** OpenSSL certificate verification failed");
		return FALSE;
	}

	/* FIXME: Check creds */
	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 = SSL_read (ssl->session, buf, len);

	status = ssl_io_status_from_return (ssl, b_read);

	if (status == G_IO_STATUS_NORMAL) {
		*bytes_read = b_read;
	}

	return status;
}

gint
_lm_ssl_send (LmSSL *ssl, const gchar *str, gint len)
{
	GIOStatus status;
	gint      bytes_written;


	bytes_written = SSL_write (ssl->session, str, len);
	status = ssl_io_status_from_return (ssl, bytes_written);
	
	while (bytes_written < 0) {
		if (status != G_IO_STATUS_AGAIN) {
			return -1;
		}

		bytes_written = SSL_write (ssl->session, str, len);
		status = ssl_io_status_from_return (ssl, bytes_written);
	}

	return bytes_written;
}

void 
_lm_ssl_close (LmSSL *ssl)
{
	SSL_free (ssl->session);
	ssl->session = NULL;
}

void
_lm_ssl_free (LmSSL *ssl)
{
	_lm_ssl_base_free_fields (LM_SSL_BASE (ssl));

	SSL_CTX_free (ssl->ctx);

	g_free (ssl);
}

#endif /* HAVE_GNUTLS */