--- 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);
}