Add SOUP_SESSION_SSL_STRICT, to control whether SSL cert errors are fatal
authorDan Winship <danw@gnome.org>
Sat, 20 Feb 2010 17:44:44 +0000 (12:44 -0500)
committerDan Winship <danw@gnome.org>
Sun, 21 Feb 2010 15:06:10 +0000 (10:06 -0500)
Based on a patch from Gustavo Noronha Silva

https://bugzilla.gnome.org/show_bug.cgi?id=610374

libsoup/soup-connection.c
libsoup/soup-connection.h
libsoup/soup-gnutls.c
libsoup/soup-session.c
libsoup/soup-session.h
libsoup/soup-socket.c
libsoup/soup-socket.h
tests/test-utils.c

index 8cb110c..b730b62 100644 (file)
@@ -34,6 +34,7 @@ typedef struct {
        SoupAddress *remote_addr, *tunnel_addr;
        SoupURI     *proxy_uri;
        gpointer     ssl_creds;
+       gboolean     ssl_strict;
 
        GMainContext      *async_context;
 
@@ -61,6 +62,7 @@ enum {
        PROP_TUNNEL_ADDRESS,
        PROP_PROXY_URI,
        PROP_SSL_CREDS,
+       PROP_SSL_STRICT,
        PROP_ASYNC_CONTEXT,
        PROP_TIMEOUT,
        PROP_IDLE_TIMEOUT,
@@ -175,6 +177,13 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
                                      "Opaque SSL credentials for this connection",
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (
+               object_class, PROP_SSL_STRICT,
+               g_param_spec_boolean (SOUP_CONNECTION_SSL_STRICT,
+                                     "Strictly validate SSL certificates",
+                                     "Whether certificate errors should be considered a connection error",
+                                     TRUE,
+                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (
                object_class, PROP_ASYNC_CONTEXT,
                g_param_spec_pointer (SOUP_CONNECTION_ASYNC_CONTEXT,
                                      "Async GMainContext",
@@ -246,6 +255,9 @@ set_property (GObject *object, guint prop_id,
        case PROP_SSL_CREDS:
                priv->ssl_creds = g_value_get_pointer (value);
                break;
+       case PROP_SSL_STRICT:
+               priv->ssl_strict = g_value_get_boolean (value);
+               break;
        case PROP_ASYNC_CONTEXT:
                priv->async_context = g_value_get_pointer (value);
                if (priv->async_context)
@@ -285,6 +297,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_SSL_CREDS:
                g_value_set_pointer (value, priv->ssl_creds);
                break;
+       case PROP_SSL_STRICT:
+               g_value_set_boolean (value, priv->ssl_strict);
+               break;
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
@@ -466,6 +481,7 @@ soup_connection_connect_async (SoupConnection *conn,
        priv->socket =
                soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
                                 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
+                                SOUP_SOCKET_SSL_STRICT, priv->ssl_strict,
                                 SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context,
                                 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
                                 NULL);
@@ -496,6 +512,7 @@ soup_connection_connect_sync (SoupConnection *conn)
        priv->socket =
                soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
                                 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
+                                SOUP_SOCKET_SSL_STRICT, priv->ssl_strict,
                                 SOUP_SOCKET_FLAG_NONBLOCKING, FALSE,
                                 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
                                 NULL);
index ae8973e..f2ec40c 100644 (file)
@@ -44,6 +44,7 @@ typedef void  (*SoupConnectionCallback)        (SoupConnection   *conn,
 #define SOUP_CONNECTION_TUNNEL_ADDRESS  "tunnel-address"
 #define SOUP_CONNECTION_PROXY_URI       "proxy-uri"
 #define SOUP_CONNECTION_SSL_CREDENTIALS "ssl-creds"
+#define SOUP_CONNECTION_SSL_STRICT      "ssl-strict"
 #define SOUP_CONNECTION_ASYNC_CONTEXT   "async-context"
 #define SOUP_CONNECTION_TIMEOUT         "timeout"
 #define SOUP_CONNECTION_IDLE_TIMEOUT    "idle-timeout"
index c77f2b2..1b6e613 100644 (file)
@@ -164,6 +164,8 @@ again:
                return G_IO_STATUS_ERROR;
        }
 
+       chan->established = TRUE;
+
        if (chan->type == SOUP_SSL_TYPE_CLIENT && chan->creds->have_ca_file &&
            !verify_certificate (chan->session, chan->hostname, err))
                return G_IO_STATUS_ERROR;
@@ -190,8 +192,6 @@ again:
                if (result == G_IO_STATUS_AGAIN ||
                    result == G_IO_STATUS_ERROR)
                        return result;
-
-               chan->established = TRUE;
        }
 
        result = gnutls_record_recv (chan->session, buf, count);
@@ -252,8 +252,6 @@ again:
                if (result == G_IO_STATUS_AGAIN ||
                    result == G_IO_STATUS_ERROR)
                        return result;
-
-               chan->established = TRUE;
        }
 
        result = gnutls_record_send (chan->session, buf, count);
index 2b13228..009d702 100644 (file)
@@ -71,6 +71,7 @@ typedef struct {
 typedef struct {
        char *ssl_ca_file;
        SoupSSLCredentials *ssl_creds;
+       gboolean ssl_strict;
 
        SoupMessageQueue *queue;
 
@@ -140,6 +141,7 @@ enum {
        PROP_MAX_CONNS_PER_HOST,
        PROP_USE_NTLM,
        PROP_SSL_CA_FILE,
+       PROP_SSL_STRICT,
        PROP_ASYNC_CONTEXT,
        PROP_TIMEOUT,
        PROP_USER_AGENT,
@@ -191,6 +193,8 @@ soup_session_init (SoupSession *session)
         * so hold a ref on the default GResolver.
         */
        priv->resolver = g_resolver_get_default ();
+
+       priv->ssl_strict = TRUE;
 }
 
 static void
@@ -508,6 +512,24 @@ soup_session_class_init (SoupSessionClass *session_class)
                                     NULL,
                                     G_PARAM_READWRITE));
        /**
+        * SOUP_SESSION_SSL_STRICT:
+        *
+        * Alias for the #SoupSession:ignore-ssl-cert-errors
+        * property. By default, when validating certificates against
+        * a CA file, Soup will consider invalid certificates as a
+        * connection error. Setting this property to %TRUE makes soup
+        * ignore the errors, and make the connection.
+        *
+        * Since: 2.30
+        **/
+       g_object_class_install_property (
+               object_class, PROP_SSL_STRICT,
+               g_param_spec_boolean (SOUP_SESSION_SSL_STRICT,
+                                     "Strictly validate SSL certificates",
+                                     "Whether certificate errors should be considered a connection error",
+                                     TRUE,
+                                     G_PARAM_READWRITE));
+       /**
         * SOUP_SESSION_ASYNC_CONTEXT:
         *
         * Alias for the #SoupSession:async-context property. (The
@@ -843,6 +865,9 @@ set_property (GObject *object, guint prop_id,
                }
 
                break;
+       case PROP_SSL_STRICT:
+               priv->ssl_strict = g_value_get_boolean (value);
+               break;
        case PROP_ASYNC_CONTEXT:
                priv->async_context = g_value_get_pointer (value);
                if (priv->async_context)
@@ -936,6 +961,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_SSL_CA_FILE:
                g_value_set_string (value, priv->ssl_ca_file);
                break;
+       case PROP_SSL_STRICT:
+               g_value_set_boolean (value, priv->ssl_strict);
+               break;
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
@@ -1408,6 +1436,7 @@ soup_session_get_connection (SoupSession *session,
                SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr,
                SOUP_CONNECTION_PROXY_URI, item->proxy_uri,
                SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds,
+               SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict,
                SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
                SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
                SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
index 056799d..fe07892 100644 (file)
@@ -63,6 +63,7 @@ GType soup_session_get_type (void);
 #define SOUP_SESSION_MAX_CONNS_PER_HOST     "max-conns-per-host"
 #define SOUP_SESSION_USE_NTLM               "use-ntlm"
 #define SOUP_SESSION_SSL_CA_FILE            "ssl-ca-file"
+#define SOUP_SESSION_SSL_STRICT             "ssl-strict"
 #define SOUP_SESSION_ASYNC_CONTEXT          "async-context"
 #define SOUP_SESSION_TIMEOUT                "timeout"
 #define SOUP_SESSION_USER_AGENT             "user-agent"
index 00fd881..2bbf22d 100644 (file)
@@ -66,6 +66,7 @@ enum {
        PROP_NON_BLOCKING,
        PROP_IS_SERVER,
        PROP_SSL_CREDENTIALS,
+       PROP_SSL_STRICT,
        PROP_ASYNC_CONTEXT,
        PROP_TIMEOUT,
 
@@ -81,6 +82,7 @@ typedef struct {
        guint is_server:1;
        guint timed_out:1;
        gpointer ssl_creds;
+       gboolean ssl_strict;
 
        GMainContext   *async_context;
        GSource        *watch_src;
@@ -354,6 +356,18 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      "SSL credential information, passed from the session to the SSL implementation",
                                      G_PARAM_READWRITE));
        /**
+        * SOUP_SOCKET_SSL_STRICT:
+        *
+        * Alias for the #SoupSocket:ignore-ssl-cert-errors property.
+        **/
+       g_object_class_install_property (
+               object_class, PROP_SSL_STRICT,
+               g_param_spec_boolean (SOUP_SOCKET_SSL_STRICT,
+                                     "Strictly validate SSL certificates",
+                                     "Whether certificate errors should be considered a connection error",
+                                     TRUE,
+                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       /**
         * SOUP_SOCKET_ASYNC_CONTEXT:
         *
         * Alias for the #SoupSocket:async-context property. (The
@@ -492,6 +506,9 @@ set_property (GObject *object, guint prop_id,
        case PROP_SSL_CREDENTIALS:
                priv->ssl_creds = g_value_get_pointer (value);
                break;
+       case PROP_SSL_STRICT:
+               priv->ssl_strict = g_value_get_boolean (value);
+               break;
        case PROP_ASYNC_CONTEXT:
                priv->async_context = g_value_get_pointer (value);
                if (priv->async_context)
@@ -528,6 +545,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_SSL_CREDENTIALS:
                g_value_set_pointer (value, priv->ssl_creds);
                break;
+       case PROP_SSL_STRICT:
+               g_value_set_boolean (value, priv->ssl_strict);
+               break;
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
@@ -1218,11 +1238,19 @@ read_from_network (SoupSocket *sock, gpointer buffer, gsize len,
                return SOUP_SOCKET_ERROR;
        }
 
+again:
        status = g_io_channel_read_chars (priv->iochannel,
                                          buffer, len, nread, &my_err);
        if (my_err) {
-               if (my_err->domain == SOUP_SSL_ERROR &&
-                   my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE)
+               if (g_error_matches (my_err, SOUP_SSL_ERROR,
+                                    SOUP_SSL_ERROR_CERTIFICATE) &&
+                   !priv->ssl_strict) {
+                       g_clear_error (&my_err);
+                       goto again;
+               }
+
+               if (g_error_matches (my_err, SOUP_SSL_ERROR,
+                                    SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE))
                        cond = G_IO_OUT;
                g_propagate_error (error, my_err);
        }
@@ -1517,11 +1545,19 @@ soup_socket_write (SoupSocket *sock, gconstpointer buffer,
                return SOUP_SOCKET_WOULD_BLOCK;
        }
 
+again:
        status = g_io_channel_write_chars (priv->iochannel,
                                           buffer, len, nwrote, &my_err);
        if (my_err) {
-               if (my_err->domain == SOUP_SSL_ERROR &&
-                   my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ)
+               if (g_error_matches (my_err, SOUP_SSL_ERROR,
+                                    SOUP_SSL_ERROR_CERTIFICATE) &&
+                   !priv->ssl_strict) {
+                       g_clear_error (&my_err);
+                       goto again;
+               }
+
+               if (g_error_matches (my_err, SOUP_SSL_ERROR,
+                                    SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ))
                        cond = G_IO_IN;
                g_propagate_error (error, my_err);
        }
index 079297b..4106e86 100644 (file)
@@ -45,8 +45,9 @@ typedef struct {
 #define SOUP_SOCKET_FLAG_NONBLOCKING "non-blocking"
 #define SOUP_SOCKET_IS_SERVER        "is-server"
 #define SOUP_SOCKET_SSL_CREDENTIALS  "ssl-creds"
+#define SOUP_SOCKET_SSL_STRICT       "ssl-strict"
 #define SOUP_SOCKET_ASYNC_CONTEXT    "async-context"
-#define SOUP_SOCKET_TIMEOUT         "timeout"
+#define SOUP_SOCKET_TIMEOUT          "timeout"
 
 typedef void (*SoupSocketCallback)            (SoupSocket         *sock,
                                               guint               status,
index 0b13ca1..8d7e9f6 100644 (file)
@@ -231,6 +231,11 @@ soup_test_session_new (GType type, ...)
        session = (SoupSession *)g_object_new_valist (type, propname, args);
        va_end (args);
 
+       g_object_set (G_OBJECT (session),
+                     SOUP_SESSION_SSL_CA_FILE, SRCDIR "/test-cert.pem",
+                     SOUP_SESSION_SSL_STRICT, FALSE,
+                     NULL);
+
        if (http_debug_level && !logger) {
                SoupLoggerLogLevel level = MIN ((SoupLoggerLogLevel)http_debug_level, SOUP_LOGGER_LOG_BODY);