Added SSL changes from Senko on SSL branch
authorMikael Hallendal <micke@imendio.com>
Fri, 23 Feb 2007 02:01:37 +0100
changeset 209 1fd5e3004816
parent 208 79974b436e7d
child 213 27567fb5d37b
Added SSL changes from Senko on SSL branch
loudmouth/lm-ssl-openssl.c
--- a/loudmouth/lm-ssl-openssl.c	Fri Feb 23 00:19:31 2007 +0100
+++ b/loudmouth/lm-ssl-openssl.c	Fri Feb 23 02:01:37 2007 +0100
@@ -1,6 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2006 Imendio AB
+ * Copyright (C) 2006 Nokia Corporation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public License as
@@ -20,7 +21,9 @@
 
 #include <config.h>
 
+#include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 #include <glib.h>
 
 #include "lm-error.h"
@@ -30,87 +33,207 @@
 #ifdef HAVE_OPENSSL
 
 #include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#define LM_SSL_CN_MAX		63
 
 struct _LmSSL {
-	LmSSLBase  base;
+	LmSSLBase base;
 
-	SSL_CTX   *ctx;
-
-	SSL       *session;
-/*	gnutls_certificate_client_credentials gnutls_xcred;*/
+	SSL_METHOD *ssl_method;
+	SSL_CTX *ssl_ctx;
+	SSL *ssl;
+	/*BIO *bio;*/
 };
 
-static gboolean       ssl_verify_certificate    (LmSSL       *ssl,
-						 const gchar *server);
-static GIOStatus      ssl_io_status_from_return (LmSSL       *ssl,
-						 gint         error);
+int ssl_verify_cb (int preverify_ok, X509_STORE_CTX *x509_ctx);
+
+static gboolean ssl_verify_certificate (LmSSL *ssl, const gchar *server);
+static GIOStatus ssl_io_status_from_return (LmSSL *ssl, gint error);
+
+/*static char _ssl_error_code[11];*/
+
+static void
+ssl_print_state (LmSSL *ssl, const char *func, int val)
+{
+	unsigned long errid;
+	const char *errmsg;
+
+	switch (SSL_get_error(ssl->ssl, val)) {
+		case SSL_ERROR_NONE:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_NONE\n",
+				func, val);
+			break;
+		case SSL_ERROR_ZERO_RETURN:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_ZERO_RETURN\n",
+				func, val);
+			break;
+		case SSL_ERROR_WANT_READ:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_WANT_READ\n",
+				func, val);
+			break;
+		case SSL_ERROR_WANT_WRITE:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_WANT_WRITE\n",
+				func, val);
+			break;
+		case SSL_ERROR_WANT_X509_LOOKUP:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_WANT_X509_LOOKUP\n",
+				func, val);
+			break;
+		case SSL_ERROR_SYSCALL:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_SYSCALL\n",
+				func, val);
+			break;
+		case SSL_ERROR_SSL:
+			fprintf(stderr,
+				"%s(): %i / SSL_ERROR_SSL\n",
+				func, val);
+			break;
+	}
+	do {
+		errid = ERR_get_error();
+		if (errid) {
+			errmsg = ERR_error_string(errid, NULL);
+			fprintf(stderr, "\t%s\n", errmsg);
+		}
+	} while (errid != 0);
+}
+
+/*static const char *
+ssl_get_x509_err (long verify_res)
+{
+	sprintf(_ssl_error_code, "%ld", verify_res);
+	return _ssl_error_code;
+}*/
+
+	
+int
+ssl_verify_cb (int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+	/* As this callback doesn't get auxiliary pointer parameter we
+	 * cannot really use this. However, we can retrieve results later. */
+	return 1;
+}
 
 static gboolean
 ssl_verify_certificate (LmSSL *ssl, const gchar *server)
 {
-	LmSSLBase   *base;
-	int          result;
-	LmSSLStatus  status;
+	gboolean retval = TRUE;
+	LmSSLBase *base;
+	long verify_res;
+	unsigned int digest_len;
+	X509 *srv_crt;
+	gchar *cn;
+	X509_NAME *crt_subj;
 
-	base = LM_SSL_BASE (ssl);
-
-	result = SSL_get_verify_result (ssl->session);
+	base = LM_SSL_BASE(ssl);
 
-	/* 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;
+	fprintf(stderr, "%s: Cipher: %s/%s/%i\n",
+		__FILE__,
+		SSL_get_cipher_version(ssl->ssl),
+		SSL_get_cipher_name(ssl->ssl),
+		SSL_get_cipher_bits(ssl->ssl, NULL));
+	verify_res = SSL_get_verify_result(ssl->ssl);
+	srv_crt = SSL_get_peer_certificate(ssl->ssl);
+	if (base->expected_fingerprint != NULL) {
+		X509_digest(srv_crt, EVP_md5(), (guchar *) base->fingerprint,
+			&digest_len);
+		if (memcmp(base->expected_fingerprint, base->fingerprint,
+			digest_len) != 0) {
+			if (base->func(ssl,
+				LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				return FALSE;
+			}
+		}
 	}
-
-	return TRUE;
+	fprintf(stderr, "%s: SSL_get_verify_result() = %ld\n",
+		__FILE__,
+		verify_res);
+	switch (verify_res) {
+		case X509_V_OK:
+			break;
+		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+			/* special case for self signed certificates? */
+		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+		case X509_V_ERR_UNABLE_TO_GET_CRL:
+		case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+			if (base->func(ssl,
+				LM_SSL_STATUS_NO_CERT_FOUND,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+			break;
+		case X509_V_ERR_INVALID_CA:
+		case X509_V_ERR_CERT_UNTRUSTED:
+		case X509_V_ERR_CERT_REVOKED:
+			if (base->func(ssl,
+				LM_SSL_STATUS_UNTRUSTED_CERT,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+			break;
+		case X509_V_ERR_CERT_NOT_YET_VALID:
+		case X509_V_ERR_CRL_NOT_YET_VALID:
+			if (base->func(ssl,
+				LM_SSL_STATUS_CERT_NOT_ACTIVATED,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+			break;
+		case X509_V_ERR_CERT_HAS_EXPIRED:
+		case X509_V_ERR_CRL_HAS_EXPIRED:
+			if (base->func(ssl,
+				LM_SSL_STATUS_CERT_EXPIRED,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+			break;
+		default:
+			if (base->func(ssl, LM_SSL_STATUS_GENERIC_ERROR,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+	}
+	/*if (retval == FALSE) {
+		g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN,
+			ssl_get_x509_err(verify_res), NULL);
+	}*/
+	crt_subj = X509_get_subject_name(srv_crt);
+	cn = (gchar *) g_malloc0(LM_SSL_CN_MAX + 1);
+	if (cn == NULL) {
+		fprintf(stderr, "g_malloc0() out of memory @ %s:%d\n",
+			__FILE__, __LINE__);
+		abort();
+	}
+	if (X509_NAME_get_text_by_NID(crt_subj, NID_commonName, cn,
+		LM_SSL_CN_MAX) > 0) {
+	fprintf(stderr, "%s: server = '%s', cn = '%s'\n",
+		__FILE__, server, cn);
+		if (strncmp(server, cn, LM_SSL_CN_MAX) != 0) {
+			if (base->func(ssl,
+				LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH,
+				base->func_data) != LM_SSL_RESPONSE_CONTINUE) {
+				retval = FALSE;
+			}
+		}
+	} else {
+		fprintf(stderr, "X509_NAME_get_text_by_NID() failed\n");
+	}
+	fprintf(stderr, "%s:\n\tIssuer: %s\n\tSubject: %s\n\tFor: %s\n",
+		__FILE__,
+		X509_NAME_oneline(X509_get_issuer_name(srv_crt), NULL, 0),
+		X509_NAME_oneline(X509_get_subject_name(srv_crt), NULL, 0),
+		cn);
+	g_free(cn);
+	
+	return retval;
 }
 
 static GIOStatus
@@ -119,19 +242,19 @@
 	gint      error;
 	GIOStatus status;
 
-	if (ret > 0) {
-		return G_IO_STATUS_NORMAL;
-	}
-
-	error = SSL_get_error (ssl->session, ret);
+	if (ret > 0) return G_IO_STATUS_NORMAL;
 
-	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;
+	error = SSL_get_error(ssl->ssl, ret);
+	switch (error) {
+		case SSL_ERROR_WANT_READ:
+		case SSL_ERROR_WANT_WRITE:
+			status = G_IO_STATUS_AGAIN;
+			break;
+		case SSL_ERROR_ZERO_RETURN:
+			status = G_IO_STATUS_EOF;
+			break;
+		default:
+			status = G_IO_STATUS_ERROR;
 	}
 
 	return status;
@@ -159,63 +282,86 @@
 void
 _lm_ssl_initialize (LmSSL *ssl) 
 {
-	static gboolean  initialized = FALSE;
-	SSL_METHOD      *meth;
+	static gboolean initialized = FALSE;
+	/*const char *cert_file = NULL;*/
 
 	if (!initialized) {
-		SSL_library_init ();
-
+		SSL_library_init();
 		/* FIXME: Is this needed when we are not in debug? */
-		SSL_load_error_strings ();
+		SSL_load_error_strings();
 		initialized = TRUE;
 	}
 
-	meth = SSLv23_method ();
-	ssl->ctx = SSL_CTX_new (meth);
+	ssl->ssl_method = TLSv1_client_method();
+	if (ssl->ssl_method == NULL) {
+		fprintf(stderr, "TLSv1_client_method() == NULL\n");
+		abort();
+	}
+	ssl->ssl_ctx = SSL_CTX_new(ssl->ssl_method);
+	if (ssl->ssl_ctx == NULL) {
+		fprintf(stderr, "SSL_CTX_new() == NULL\n");
+		abort();
+	}
+	/*if (access("/etc/ssl/cert.pem", R_OK) == 0)
+		cert_file = "/etc/ssl/cert.pem";
+	if (!SSL_CTX_load_verify_locations(ssl->ssl_ctx,
+		cert_file, "/etc/ssl/certs")) {
+		fprintf(stderr, "SSL_CTX_load_verify_locations() failed\n");
+	}*/
+	SSL_CTX_set_default_verify_paths(ssl->ssl_ctx);
+	SSL_CTX_set_verify(ssl->ssl_ctx, SSL_VERIFY_PEER, ssl_verify_cb);
 }
 
 gboolean
 _lm_ssl_begin (LmSSL *ssl, gint fd, const gchar *server, GError **error)
 {
-	BIO       *sbio;
-	GIOStatus  status;
-
-	ssl->session = SSL_new (ssl->ctx);
-	sbio = BIO_new_socket (fd, BIO_NOCLOSE);
-	SSL_set_bio (ssl->session, sbio, sbio);
-
-	while (TRUE) {
-		gint ret;
-
-		ret = SSL_connect (ssl->session);
+	gint ssl_ret;
+	GIOStatus status;
 
-		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");
+	ssl->ssl = SSL_new(ssl->ssl_ctx);
+	if (ssl->ssl == NULL) {
+		fprintf(stderr, "SSL_new() == NULL\n");
+		g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN,
+			"SSL_new()");
+		return FALSE;
+	}
+	if (!SSL_set_fd(ssl->ssl, fd)) {
+		fprintf(stderr, "SSL_set_fd() failed\n");
+		g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN,
+			"SSL_set_fd()");
+		return FALSE;
+	}
+	/*ssl->bio = BIO_new_socket (fd, BIO_NOCLOSE);
+	if (ssl->bio == NULL) {
+		fprintf(stderr, "BIO_new_socket() failed\n");
+		g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN,
+			"BIO_new_socket()");
+		return FALSE;
+	}
+	SSL_set_bio(ssl->ssl, ssl->bio, ssl->bio);*/
+
+	do {
+		ssl_ret = SSL_connect(ssl->ssl);
+		if (ssl_ret <= 0) {
+			status = ssl_io_status_from_return(ssl, ssl_ret);
+			if (status != G_IO_STATUS_AGAIN) {
+				ssl_print_state(ssl, "SSL_connect",
+					ssl_ret);
+				g_set_error(error, LM_ERROR,
+					LM_ERROR_CONNECTION_OPEN,
+					"SSL_connect()");
 				return FALSE;
 			}
 
 		}
-	}
+	} while (ssl_ret <= 0);
 
 	if (!ssl_verify_certificate (ssl, server)) {
-		g_set_error (error,
-			     LM_ERROR, LM_ERROR_CONNECTION_OPEN,
-			     "*** OpenSSL certificate verification failed");
+		g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN,
+			"*** SSL certificate verification failed");
 		return FALSE;
 	}
-
-	/* FIXME: Check creds */
+	
 	return TRUE; 
 }
 
@@ -223,17 +369,15 @@
 _lm_ssl_read (LmSSL *ssl, gchar *buf, gint len, gsize *bytes_read)
 {
 	GIOStatus status;
-	gint      b_read;
+	gint ssl_ret;
 
 	*bytes_read = 0;
-	b_read = SSL_read (ssl->session, buf, len);
-
-	status = ssl_io_status_from_return (ssl, b_read);
-
+	ssl_ret = SSL_read(ssl->ssl, buf, len);
+	status = ssl_io_status_from_return(ssl, ssl_ret);
 	if (status == G_IO_STATUS_NORMAL) {
-		*bytes_read = b_read;
+		*bytes_read = ssl_ret;
 	}
-
+	
 	return status;
 }
 
@@ -241,38 +385,35 @@
 _lm_ssl_send (LmSSL *ssl, const gchar *str, gint len)
 {
 	GIOStatus status;
-	gint      bytes_written;
-
+	gint ssl_ret;
 
-	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;
+	do {
+		ssl_ret = SSL_write(ssl->ssl, str, len);
+		if (ssl_ret <= 0) {
+			status = ssl_io_status_from_return(ssl, ssl_ret);
+			if (status != G_IO_STATUS_AGAIN)
+				return -1;
 		}
+	} while (ssl_ret <= 0);
 
-		bytes_written = SSL_write (ssl->session, str, len);
-		status = ssl_io_status_from_return (ssl, bytes_written);
-	}
-
-	return bytes_written;
+	return ssl_ret;
 }
 
 void 
 _lm_ssl_close (LmSSL *ssl)
 {
-	SSL_free (ssl->session);
-	ssl->session = NULL;
+	SSL_shutdown(ssl->ssl);
+	SSL_free(ssl->ssl);
+	ssl->ssl = NULL;
 }
 
 void
 _lm_ssl_free (LmSSL *ssl)
 {
-	_lm_ssl_base_free_fields (LM_SSL_BASE (ssl));
+	SSL_CTX_free(ssl->ssl_ctx);
+	ssl->ssl_ctx = NULL;
 
-	SSL_CTX_free (ssl->ctx);
-
+	_lm_ssl_base_free_fields (LM_SSL_BASE(ssl));
 	g_free (ssl);
 }