[libsoup/carlosgc/server-client-side-certs: 3/3] server: add support for client side certificates




commit 0821e8967af443de1695ca02a9d08777390d0562
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Tue Jun 15 12:58:44 2021 +0200

    server: add support for client side certificates

 docs/reference/libsoup-3.0-sections.txt |   4 +
 libsoup/server/soup-server-message.c    |  45 ++++++++++
 libsoup/server/soup-server.c            | 149 ++++++++++++++++++++++++++++++--
 libsoup/server/soup-server.h            |  12 +++
 libsoup/server/soup-socket.c            |  68 ++++++++++++++-
 tests/ssl-test.c                        |  32 +++----
 6 files changed, 285 insertions(+), 25 deletions(-)
---
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 5d695e69..15bb9b4b 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -227,6 +227,10 @@ SoupServer
 soup_server_new
 soup_server_set_tls_certificate
 soup_server_get_tls_certificate
+soup_server_set_tls_database
+soup_server_get_tls_database
+soup_server_set_tls_auth_mode
+soup_server_get_tls_auth_mode
 <SUBSECTION>
 SoupServerListenOptions
 soup_server_listen
diff --git a/libsoup/server/soup-server-message.c b/libsoup/server/soup-server-message.c
index 91acaa53..0aecd057 100644
--- a/libsoup/server/soup-server-message.c
+++ b/libsoup/server/soup-server-message.c
@@ -94,6 +94,8 @@ enum {
         DISCONNECTED,
         FINISHED,
 
+        ACCEPT_CERTIFICATE,
+
         LAST_SIGNAL
 };
 
@@ -313,6 +315,32 @@ soup_server_message_class_init (SoupServerMessageClass *klass)
                               NULL, NULL,
                               NULL,
                               G_TYPE_NONE, 0);
+
+       /**
+        * SoupServerMessage::accept-certificate:
+        * @msg: the message
+        * @tls_peer_certificate: the peer's #GTlsCertificate
+        * @tls_peer_errors: the tls errors of @tls_certificate
+        *
+        * Emitted during the @msg's connection TLS handshake
+        * after client TLS certificate has been received.
+        * You can return %TRUE to accept @tls_certificate despite
+        * @tls_errors.
+        *
+        * Returns: %TRUE to accept the TLS certificate and stop other
+        *     handlers from being invoked, or %FALSE to propagate the
+        *     event further.
+        */
+       signals[ACCEPT_CERTIFICATE] =
+               g_signal_new ("accept-certificate",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             g_signal_accumulator_true_handled, NULL,
+                             NULL,
+                             G_TYPE_BOOLEAN, 2,
+                             G_TYPE_TLS_CERTIFICATE,
+                             G_TYPE_TLS_CERTIFICATE_FLAGS);
 }
 
 static void
@@ -321,6 +349,18 @@ socket_disconnected (SoupServerMessage *msg)
         g_signal_emit (msg, signals[DISCONNECTED], 0);
 }
 
+static gboolean
+socket_accept_certificate (SoupServerMessage    *msg,
+                           GTlsCertificate      *tls_certificate,
+                           GTlsCertificateFlags *tls_errors)
+{
+       gboolean accept = FALSE;
+
+       g_signal_emit (msg, signals[ACCEPT_CERTIFICATE], 0,
+                      tls_certificate, tls_errors, &accept);
+       return accept;
+}
+
 SoupServerMessage *
 soup_server_message_new (SoupSocket *sock)
 {
@@ -335,6 +375,9 @@ soup_server_message_new (SoupSocket *sock)
         g_signal_connect_object (sock, "disconnected",
                                  G_CALLBACK (socket_disconnected),
                                  msg, G_CONNECT_SWAPPED);
+        g_signal_connect_object (sock, "accept-certificate",
+                                 G_CALLBACK (socket_accept_certificate),
+                                 msg, G_CONNECT_SWAPPED);
 
         return msg;
 }
@@ -922,6 +965,8 @@ soup_server_message_steal_connection (SoupServerMessage *msg)
                                         g_object_unref);
         }
 
+        g_signal_handlers_disconnect_by_data (msg, msg->sock);
+
         socket_disconnected (msg);
         g_object_unref (msg);
 
diff --git a/libsoup/server/soup-server.c b/libsoup/server/soup-server.c
index d2cce7c3..b317bf99 100644
--- a/libsoup/server/soup-server.c
+++ b/libsoup/server/soup-server.c
@@ -160,6 +160,8 @@ typedef struct {
        GSList            *clients;
 
        GTlsCertificate   *tls_cert;
+        GTlsDatabase      *tls_database;
+        GTlsAuthenticationMode tls_auth_mode;
 
        char              *server_header;
 
@@ -183,6 +185,8 @@ enum {
        PROP_0,
 
        PROP_TLS_CERTIFICATE,
+        PROP_TLS_DATABASE,
+        PROP_TLS_AUTH_MODE,
        PROP_RAW_PATHS,
        PROP_SERVER_HEADER,
 
@@ -243,6 +247,7 @@ soup_server_finalize (GObject *object)
        SoupServerPrivate *priv = soup_server_get_instance_private (server);
 
        g_clear_object (&priv->tls_cert);
+        g_clear_object (&priv->tls_database);
 
        g_free (priv->server_header);
 
@@ -269,6 +274,12 @@ soup_server_set_property (GObject *object, guint prop_id,
        case PROP_TLS_CERTIFICATE:
                 soup_server_set_tls_certificate (server, g_value_get_object (value));
                break;
+        case PROP_TLS_DATABASE:
+                soup_server_set_tls_database (server, g_value_get_object (value));
+                break;
+        case PROP_TLS_AUTH_MODE:
+                soup_server_set_tls_auth_mode (server, g_value_get_enum (value));
+                break;
        case PROP_RAW_PATHS:
                priv->raw_paths = g_value_get_boolean (value);
                break;
@@ -304,6 +315,12 @@ soup_server_get_property (GObject *object, guint prop_id,
        case PROP_TLS_CERTIFICATE:
                g_value_set_object (value, priv->tls_cert);
                break;
+        case PROP_TLS_DATABASE:
+                g_value_set_object (value, priv->tls_database);
+                break;
+        case PROP_TLS_AUTH_MODE:
+                g_value_set_enum (value, priv->tls_auth_mode);
+                break;
        case PROP_RAW_PATHS:
                g_value_set_boolean (value, priv->raw_paths);
                break;
@@ -441,6 +458,35 @@ soup_server_class_init (SoupServerClass *server_class)
                                      G_PARAM_CONSTRUCT |
                                      G_PARAM_STATIC_STRINGS);
 
+        /**
+         * SoupServer:tls-database:
+         *
+         * A #GTlsDatabase to use for validating SSL/TLS client certificates.
+         */
+        properties[PROP_TLS_DATABASE] =
+                g_param_spec_object ("tls-database",
+                                     "TLS database",
+                                     "GTlsDatabase to use for validating SSL/TLS client certificates",
+                                     G_TYPE_TLS_DATABASE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_CONSTRUCT |
+                                     G_PARAM_STATIC_STRINGS);
+
+        /**
+         * SoupServer:tls-auth-mode:
+         *
+         * A #GTlsAuthenticationMode for SSL/TLS client authentication
+         */
+        properties[PROP_TLS_AUTH_MODE] =
+                g_param_spec_enum ("tls-auth-mode",
+                                   "TLS Authentication Mode",
+                                   "GTlsAuthenticationMode to use for SSL/TLS client authentication",
+                                   G_TYPE_TLS_AUTHENTICATION_MODE,
+                                   G_TLS_AUTHENTICATION_NONE,
+                                   G_PARAM_READWRITE |
+                                   G_PARAM_CONSTRUCT |
+                                   G_PARAM_STATIC_STRINGS);
+
         properties[PROP_RAW_PATHS] =
                g_param_spec_boolean ("raw-paths",
                                      "Raw paths",
@@ -527,11 +573,11 @@ void
 soup_server_set_tls_certificate (SoupServer      *server,
                                  GTlsCertificate *certificate)
 {
-       SoupServerPrivate *priv;
+        SoupServerPrivate *priv;
 
-       g_return_if_fail (SOUP_IS_SERVER (server));
+        g_return_if_fail (SOUP_IS_SERVER (server));
 
-       priv = soup_server_get_instance_private (server);
+        priv = soup_server_get_instance_private (server);
         if (priv->tls_cert == certificate)
                 return;
 
@@ -559,6 +605,91 @@ soup_server_get_tls_certificate (SoupServer *server)
         return priv->tls_cert;
 }
 
+/**
+ * soup_server_set_tls_database:
+ * @server: a #SoupServer
+ * @tls_database: a #GTlsDatabase
+ *
+ * Sets @server's #GTlsDatabase to use for validating SSL/TLS client certificates
+ */
+void
+soup_server_set_tls_database (SoupServer   *server,
+                              GTlsDatabase *tls_database)
+{
+        SoupServerPrivate *priv;
+
+        g_return_if_fail (SOUP_IS_SERVER (server));
+
+        priv = soup_server_get_instance_private (server);
+        if (priv->tls_database == tls_database)
+                return;
+
+        g_clear_object (&priv->tls_database);
+        priv->tls_database = tls_database ? g_object_ref (tls_database) : NULL;
+        g_object_notify_by_pspec (G_OBJECT (server), properties[PROP_TLS_DATABASE]);
+}
+
+/**
+ * soup_server_get_tls_database:
+ * @server: a #SoupServer
+ *
+ * Gets the @server SSL/TLS database
+ *
+ * Returns: (transfer none) (nullable): a #GTlsDatabase or %NULL
+ */
+GTlsDatabase *
+soup_server_get_tls_database (SoupServer *server)
+{
+        SoupServerPrivate *priv;
+
+        g_return_val_if_fail (SOUP_IS_SERVER (server), NULL);
+
+        priv = soup_server_get_instance_private (server);
+        return priv->tls_database;
+}
+
+/**
+ * soup_server_set_tls_auth_mode:
+ * @server: a #SoupServer
+ * @mode: a #GTlsAuthenticationMode
+ *
+ * Sets @server's #GTlsAuthenticationMode to use for SSL/TLS client authentication
+ */
+void
+soup_server_set_tls_auth_mode (SoupServer             *server,
+                               GTlsAuthenticationMode  mode)
+{
+        SoupServerPrivate *priv;
+
+        g_return_if_fail (SOUP_IS_SERVER (server));
+
+        priv = soup_server_get_instance_private (server);
+        if (priv->tls_auth_mode == mode)
+                return;
+
+        priv->tls_auth_mode = mode;
+        g_object_notify_by_pspec (G_OBJECT (server), properties[PROP_TLS_AUTH_MODE]);
+}
+
+/**
+ * soup_server_get_tls_auth_mode:
+ * @server: a #SoupServer
+ *
+ * Gets the @server SSL/TLS client authentication mode
+ *
+ * Returns: a #GTlsAuthenticationMode
+ */
+GTlsAuthenticationMode
+soup_server_get_tls_auth_mode (SoupServer *server)
+{
+        SoupServerPrivate *priv;
+
+        g_return_val_if_fail (SOUP_IS_SERVER (server), G_TLS_AUTHENTICATION_NONE);
+
+        priv = soup_server_get_instance_private (server);
+        return priv->tls_auth_mode;
+}
+
 /**
  * soup_server_is_https:
  * @server: a #SoupServer
@@ -1071,9 +1202,15 @@ soup_server_listen_internal (SoupServer *server, SoupSocket *listener,
                        return FALSE;
                }
 
-               g_object_set (G_OBJECT (listener),
-                             "tls-certificate", priv->tls_cert,
-                             NULL);
+                g_object_bind_property (server, "tls-certificate",
+                                        listener, "tls-certificate",
+                                        G_BINDING_SYNC_CREATE);
+                g_object_bind_property (server, "tls-database",
+                                        listener, "tls-database",
+                                        G_BINDING_SYNC_CREATE);
+                g_object_bind_property (server, "tls-auth-mode",
+                                        listener, "tls-auth-mode",
+                                        G_BINDING_SYNC_CREATE);
        }
 
        if (soup_socket_get_gsocket (listener) == NULL) {
diff --git a/libsoup/server/soup-server.h b/libsoup/server/soup-server.h
index 8f9a977a..d98ef512 100644
--- a/libsoup/server/soup-server.h
+++ b/libsoup/server/soup-server.h
@@ -47,6 +47,18 @@ void            soup_server_set_tls_certificate (SoupServer              *server
 SOUP_AVAILABLE_IN_ALL
 GTlsCertificate *soup_server_get_tls_certificate (SoupServer             *server);
 
+SOUP_AVAILABLE_IN_ALL
+void            soup_server_set_tls_database   (SoupServer               *server,
+                                                GTlsDatabase             *tls_database);
+SOUP_AVAILABLE_IN_ALL
+GTlsDatabase   *soup_server_get_tls_database   (SoupServer               *server);
+
+SOUP_AVAILABLE_IN_ALL
+void            soup_server_set_tls_auth_mode  (SoupServer               *server,
+                                                GTlsAuthenticationMode    mode);
+SOUP_AVAILABLE_IN_ALL
+GTlsAuthenticationMode soup_server_get_tls_auth_mode (SoupServer               *server);
+
 SOUP_AVAILABLE_IN_ALL
 gboolean        soup_server_is_https           (SoupServer               *server);
 
diff --git a/libsoup/server/soup-socket.c b/libsoup/server/soup-socket.c
index 208634cf..aa9815ee 100644
--- a/libsoup/server/soup-socket.c
+++ b/libsoup/server/soup-socket.c
@@ -31,6 +31,7 @@
 enum {
        DISCONNECTED,
        NEW_CONNECTION,
+        ACCEPT_CERTIFICATE,
        LAST_SIGNAL
 };
 
@@ -46,6 +47,8 @@ enum {
        PROP_REMOTE_CONNECTABLE,
        PROP_IPV6_ONLY,
        PROP_TLS_CERTIFICATE,
+        PROP_TLS_DATABASE,
+        PROP_TLS_AUTH_MODE,
 
        LAST_PROPERTY
 };
@@ -67,6 +70,8 @@ typedef struct {
        guint ipv6_only:1;
        guint ssl:1;
        GTlsCertificate *tls_certificate;
+        GTlsDatabase *tls_database;
+        GTlsAuthenticationMode tls_auth_mode;
 
        GMainContext   *async_context;
        GSource        *watch_src;
@@ -162,6 +167,7 @@ soup_socket_finalize (GObject *object)
         g_clear_object (&priv->remote_connectable);
 
        g_clear_object (&priv->tls_certificate);
+        g_clear_object (&priv->tls_database);
 
        if (priv->watch_src) {
                g_source_destroy (priv->watch_src);
@@ -224,6 +230,12 @@ soup_socket_set_property (GObject *object, guint prop_id,
        case PROP_TLS_CERTIFICATE:
                priv->tls_certificate = g_value_dup_object (value);
                break;
+        case PROP_TLS_DATABASE:
+                priv->tls_database = g_value_dup_object (value);
+                break;
+        case PROP_TLS_AUTH_MODE:
+                priv->tls_auth_mode = g_value_get_enum (value);
+                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -253,6 +265,12 @@ soup_socket_get_property (GObject *object, guint prop_id,
        case PROP_TLS_CERTIFICATE:
                g_value_set_object (value, priv->tls_certificate);
                break;
+        case PROP_TLS_DATABASE:
+                g_value_set_object (value, priv->tls_database);
+                break;
+        case PROP_TLS_AUTH_MODE:
+                g_value_set_enum (value, priv->tls_auth_mode);
+                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -308,6 +326,17 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                              G_TYPE_NONE, 1,
                              SOUP_TYPE_SOCKET);
 
+       signals[ACCEPT_CERTIFICATE] =
+               g_signal_new ("accept-certificate",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              0,
+                              g_signal_accumulator_true_handled, NULL,
+                              NULL,
+                              G_TYPE_BOOLEAN, 2,
+                              G_TYPE_TLS_CERTIFICATE,
+                              G_TYPE_TLS_CERTIFICATE_FLAGS);
+
        /* properties */
         properties[PROP_GSOCKET] =
                 g_param_spec_object ("gsocket",
@@ -364,6 +393,23 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_STRINGS);
 
+        properties[PROP_TLS_DATABASE] =
+                g_param_spec_object ("tls-database",
+                                     "TLS Database",
+                                     "The server TLS database",
+                                     G_TYPE_TLS_DATABASE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_STATIC_STRINGS);
+
+        properties[PROP_TLS_AUTH_MODE] =
+                g_param_spec_enum ("tls-auth-mode",
+                                     "TLS Authentication Mode",
+                                     "The server TLS authentication mode",
+                                     G_TYPE_TLS_AUTHENTICATION_MODE,
+                                     G_TLS_AUTHENTICATION_NONE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_STATIC_STRINGS);
+
         g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
 }
 
@@ -437,6 +483,18 @@ soup_socket_get_iostream (SoupSocket *sock)
        return priv->iostream;
 }
 
+static gboolean
+tls_connection_accept_certificate (SoupSocket          *sock,
+                                   GTlsCertificate     *tls_certificate,
+                                   GTlsCertificateFlags tls_errors)
+{
+        gboolean accept = FALSE;
+
+        g_signal_emit (sock, signals[ACCEPT_CERTIFICATE], 0,
+                       tls_certificate, tls_errors, &accept);
+        return accept;
+}
+
 static gboolean
 soup_socket_setup_ssl (SoupSocket *sock)
 {
@@ -453,7 +511,8 @@ soup_socket_setup_ssl (SoupSocket *sock)
                               NULL, NULL,
                               "base-io-stream", priv->conn,
                               "certificate", priv->tls_certificate,
-                              "use-system-certdb", FALSE,
+                              "database", priv->tls_database,
+                               "authentication-mode", priv->tls_auth_mode,
                               "require-close-notify", FALSE,
                               NULL);
        if (!conn)
@@ -462,6 +521,10 @@ soup_socket_setup_ssl (SoupSocket *sock)
        g_object_unref (priv->conn);
        priv->conn = G_IO_STREAM (conn);
 
+        g_signal_connect_object (priv->conn, "accept-certificate",
+                                 G_CALLBACK (tls_connection_accept_certificate),
+                                 sock, G_CONNECT_SWAPPED);
+
        g_clear_object (&priv->istream);
        g_clear_object (&priv->ostream);
        g_clear_object (&priv->iostream);
@@ -490,6 +553,9 @@ listen_watch (GObject *pollable, gpointer data)
        new_priv->ssl = priv->ssl;
        if (priv->tls_certificate)
                new_priv->tls_certificate = g_object_ref (priv->tls_certificate);
+        if (priv->tls_database)
+                new_priv->tls_database = g_object_ref (priv->tls_database);
+        new_priv->tls_auth_mode = priv->tls_auth_mode;
        finish_socket_setup (new);
 
        if (new_priv->tls_certificate) {
diff --git a/tests/ssl-test.c b/tests/ssl-test.c
index 5b41246a..4f183a9a 100644
--- a/tests/ssl-test.c
+++ b/tests/ssl-test.c
@@ -127,7 +127,7 @@ test_tls_interaction_class_init (TestTlsInteractionClass *klass)
 
 
 static gboolean
-accept_client_certificate (GTlsConnection       *server,
+accept_client_certificate (SoupServerMessage    *msg,
                           GTlsCertificate      *client_cert,
                           GTlsCertificateFlags  errors)
 {
@@ -136,19 +136,11 @@ accept_client_certificate (GTlsConnection       *server,
 
 static void
 server_request_started (SoupServer        *server,
-                       SoupServerMessage *msg,
-                       GTlsDatabase      *tls_db)
+                        SoupServerMessage *msg)
 {
-       SoupSocket *sock;
-       GIOStream *conn;
-
-       sock = soup_server_message_get_soup_socket (msg);
-       conn = soup_socket_get_connection (sock);
-       g_tls_connection_set_database (G_TLS_CONNECTION (conn), tls_db);
-       g_object_set (conn, "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED, NULL);
-       g_signal_connect (conn, "accept-certificate",
-                         G_CALLBACK (accept_client_certificate),
-                         NULL);
+        g_signal_connect (msg, "accept-certificate",
+                          G_CALLBACK (accept_client_certificate),
+                          NULL);
 }
 
 static void
@@ -167,10 +159,11 @@ do_tls_interaction_test (gconstpointer data)
 
        session = soup_test_session_new (NULL);
        tls_db = soup_session_get_tls_database (session);
+        g_object_set (server, "tls-database", tls_db, "tls-auth-mode", G_TLS_AUTHENTICATION_REQUIRED, NULL);
 
        g_signal_connect (server, "request-started",
-                         G_CALLBACK (server_request_started),
-                         tls_db);
+                          G_CALLBACK (server_request_started),
+                          session);
 
        /* Without a GTlsInteraction */
        msg = soup_message_new_from_uri ("GET", uri);
@@ -203,7 +196,8 @@ do_tls_interaction_test (gconstpointer data)
        g_bytes_unref (body);
        g_object_unref (msg);
 
-       g_signal_handlers_disconnect_by_data (server, tls_db);
+        g_object_set (server, "tls-database", NULL, "tls-auth-mode", G_TLS_AUTHENTICATION_NONE, NULL);
+       g_signal_handlers_disconnect_by_data (server, session);
 
        soup_test_session_abort_unref (session);
        g_object_unref (certificate);
@@ -313,10 +307,11 @@ do_tls_interaction_msg_test (gconstpointer data)
 
         session = soup_test_session_new (NULL);
         tls_db = soup_session_get_tls_database (session);
+        g_object_set (server, "tls-database", tls_db, "tls-auth-mode", G_TLS_AUTHENTICATION_REQUIRED, NULL);
 
         g_signal_connect (server, "request-started",
                           G_CALLBACK (server_request_started),
-                          tls_db);
+                          session);
 
         /* Not handling request-certificate signal */
         msg = soup_message_new_from_uri ("GET", uri);
@@ -502,7 +497,8 @@ do_tls_interaction_msg_test (gconstpointer data)
                 g_object_unref (pkcs11_certificate);
         }
 
-        g_signal_handlers_disconnect_by_data (server, tls_db);
+        g_object_set (server, "tls-database", NULL, "tls-auth-mode", G_TLS_AUTHENTICATION_NONE, NULL);
+        g_signal_handlers_disconnect_by_data (server, session);
 
         soup_test_session_abort_unref (session);
         g_object_unref (certificate);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]