Commit a27b4fd9 authored by Dan Winship's avatar Dan Winship
Browse files

gnutls: Add SNI support

Implement the new GTlsServerConnection:server-identity property, and
test that it can be used to adjust the server certificate
appropriately during the handshake.

Based on a patch from Marcin Lewandowski
https://bugzilla.gnome.org/show_bug.cgi?id=681312
parent 745c9034
......@@ -29,10 +29,13 @@
#include "gtlscertificate-gnutls.h"
#include <glib/gi18n-lib.h>
#define MAX_SERVER_NAME_LEN 255
enum
{
PROP_0,
PROP_AUTHENTICATION_MODE
PROP_AUTHENTICATION_MODE,
PROP_SERVER_IDENTITY
};
static void g_tls_server_connection_gnutls_initable_interface_init (GInitableIface *iface);
......@@ -46,6 +49,8 @@ static int g_tls_server_connection_gnutls_retrieve_function (gnutls_session_t
int pk_algos_length,
gnutls_retr2_st *st);
static int g_tls_server_connection_gnutls_server_name_cb (gnutls_session_t session);
static int g_tls_server_connection_gnutls_db_store (void *user_data,
gnutls_datum_t key,
gnutls_datum_t data);
......@@ -66,6 +71,7 @@ G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnu
struct _GTlsServerConnectionGnutlsPrivate
{
GTlsAuthenticationMode authentication_mode;
char *server_identity;
};
static void
......@@ -121,6 +127,10 @@ g_tls_server_connection_gnutls_get_property (GObject *object,
g_value_set_enum (value, gnutls->priv->authentication_mode);
break;
case PROP_SERVER_IDENTITY:
g_value_set_string (value, gnutls->priv->server_identity);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
......@@ -140,11 +150,26 @@ g_tls_server_connection_gnutls_set_property (GObject *object,
gnutls->priv->authentication_mode = g_value_get_enum (value);
break;
case PROP_SERVER_IDENTITY:
g_clear_pointer (&gnutls->priv->server_identity, g_free);
gnutls->priv->server_identity = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_tls_server_connection_gnutls_finalize (GObject *object)
{
GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (object);
g_free (gnutls->priv->server_identity);
G_OBJECT_CLASS (g_tls_server_connection_gnutls_parent_class)->finalize (object);
}
static int
g_tls_server_connection_gnutls_retrieve_function (gnutls_session_t session,
const gnutls_datum_t *req_ca_rdn,
......@@ -185,7 +210,11 @@ g_tls_server_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
}
session = g_tls_connection_gnutls_get_session (conn);
gnutls_certificate_server_set_request (session, req_mode);
gnutls_handshake_set_post_client_hello_function (session, g_tls_server_connection_gnutls_server_name_cb);
g_clear_pointer (&gnutls->priv->server_identity, g_free);
}
static void
......@@ -194,6 +223,37 @@ g_tls_server_connection_gnutls_finish_handshake (GTlsConnectionGnutls *gnutls,
{
}
static int
g_tls_server_connection_gnutls_server_name_cb (gnutls_session_t session)
{
GTlsServerConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
gchar name[MAX_SERVER_NAME_LEN];
gsize length = MAX_SERVER_NAME_LEN;
guint type;
int ret;
ret = gnutls_server_name_get (session, name, &length, &type, 0);
if (ret == 0 && type == GNUTLS_NAME_DNS)
{
if (gnutls->priv->server_identity)
g_free (gnutls->priv->server_identity);
gnutls->priv->server_identity = g_strdup (name);
g_object_notify (G_OBJECT (gnutls), "server-identity");
}
else if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
g_warning ("ignoring too-long SNI name");
return 0;
}
static const char *
g_tls_server_connection_gnutls_get_server_identity (GTlsServerConnection *conn)
{
GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (conn);
return gnutls->priv->server_identity;
}
/* Session cache management */
static int
......@@ -262,17 +322,20 @@ g_tls_server_connection_gnutls_class_init (GTlsServerConnectionGnutlsClass *klas
gobject_class->get_property = g_tls_server_connection_gnutls_get_property;
gobject_class->set_property = g_tls_server_connection_gnutls_set_property;
gobject_class->finalize = g_tls_server_connection_gnutls_finalize;
connection_gnutls_class->failed = g_tls_server_connection_gnutls_failed;
connection_gnutls_class->begin_handshake = g_tls_server_connection_gnutls_begin_handshake;
connection_gnutls_class->finish_handshake = g_tls_server_connection_gnutls_finish_handshake;
g_object_class_override_property (gobject_class, PROP_AUTHENTICATION_MODE, "authentication-mode");
g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
}
static void
g_tls_server_connection_gnutls_server_connection_interface_init (GTlsServerConnectionInterface *iface)
{
iface->get_server_identity = g_tls_server_connection_gnutls_get_server_identity;
}
static void
......
......@@ -72,6 +72,7 @@ typedef struct {
GError *server_error;
gboolean server_should_close;
gboolean server_running;
gboolean do_sni;
char buf[128];
gssize nread, nwrote;
......@@ -251,6 +252,36 @@ on_output_write_finish (GObject *object,
close_server_connection (test);
}
static void
on_received_server_identity (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
TestConnection *test = user_data;
const gchar *identity;
GTlsCertificate *cert;
GError *error = NULL;
identity = g_tls_server_connection_get_server_identity (G_TLS_SERVER_CONNECTION (object));
if (!test->do_sni)
{
g_assert_cmpstr (identity, ==, "server.example.com");
return;
}
if (!strcmp (identity, "other.example.com"))
{
cert = g_tls_certificate_new_from_files (tls_test_file_path ("other.pem"),
tls_test_file_path ("other-key.pem"),
&error);
g_assert_no_error (error);
g_tls_connection_set_certificate (G_TLS_CONNECTION (test->server_connection), cert);
g_object_unref (cert);
}
}
static gboolean
on_incoming_connection (GSocketService *service,
GSocketConnection *connection,
......@@ -270,9 +301,12 @@ on_incoming_connection (GSocketService *service,
g_assert_no_error (error);
g_object_unref (cert);
g_object_set (test->server_connection, "authentication-mode", test->auth_mode, NULL);
g_signal_connect (test->server_connection, "accept-certificate",
G_CALLBACK (on_accept_certificate), test);
g_signal_connect (test->server_connection, "notify::server-identity",
G_CALLBACK (on_received_server_identity), test);
if (test->database)
g_tls_connection_set_database (G_TLS_CONNECTION (test->server_connection), test->database);
......@@ -768,6 +802,9 @@ test_failed_connection (TestConnection *test,
g_assert_no_error (error);
g_object_unref (connection);
/* Set this so that on_received_server_identity() won't assert */
test->do_sni = TRUE;
g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection),
G_PRIORITY_DEFAULT, NULL,
handshake_failed_cb, test);
......@@ -1462,6 +1499,41 @@ test_fallback_subprocess (TestConnection *test,
g_assert_no_error (error);
}
static void
test_sni (TestConnection *test,
gconstpointer data)
{
const char *identity = (const char *) data;
GIOStream *connection;
GError *error = NULL;
test->do_sni = TRUE;
connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE);
test->identity = g_network_address_new (identity, 80);
test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
g_assert_no_error (error);
g_object_unref (connection);
test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
g_assert_no_error (error);
g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
read_test_data_async (test);
g_main_loop_run (test->loop);
if (!strcmp (identity, "fail.example.com"))
{
g_assert_error (test->read_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE);
g_assert_no_error (test->server_error);
}
else
{
g_assert_no_error (test->read_error);
g_assert_no_error (test->server_error);
}
}
int
main (int argc,
char *argv[])
......@@ -1544,6 +1616,13 @@ main (int argc,
TestConnection, NULL,
setup_connection, test_fallback_subprocess, teardown_connection);
g_test_add ("/tls/connection/sni/server", TestConnection, "server.example.com",
setup_connection, test_sni, teardown_connection);
g_test_add ("/tls/connection/sni/other", TestConnection, "other.example.com",
setup_connection, test_sni, teardown_connection);
g_test_add ("/tls/connection/sni/fail", TestConnection, "fail.example.com",
setup_connection, test_sni, teardown_connection);
ret = g_test_run();
/* for valgrinding */
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment