From ab3a4d1eafda14b04454343ad0ba59df3236503d Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sat, 20 Feb 2010 12:44:44 -0500 Subject: [PATCH] Add SOUP_SESSION_SSL_STRICT, to control whether SSL cert errors are fatal Based on a patch from Gustavo Noronha Silva https://bugzilla.gnome.org/show_bug.cgi?id=610374 --- libsoup/soup-connection.c | 17 +++++++++++++++++ libsoup/soup-connection.h | 1 + libsoup/soup-gnutls.c | 6 ++---- libsoup/soup-session.c | 29 +++++++++++++++++++++++++++++ libsoup/soup-session.h | 1 + libsoup/soup-socket.c | 44 ++++++++++++++++++++++++++++++++++++++++---- libsoup/soup-socket.h | 3 ++- tests/test-utils.c | 5 +++++ 8 files changed, 97 insertions(+), 9 deletions(-) diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c index 8cb110c..b730b62 100644 --- a/libsoup/soup-connection.c +++ b/libsoup/soup-connection.c @@ -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); diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h index ae8973e..f2ec40c 100644 --- a/libsoup/soup-connection.h +++ b/libsoup/soup-connection.h @@ -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" diff --git a/libsoup/soup-gnutls.c b/libsoup/soup-gnutls.c index c77f2b2..1b6e613 100644 --- a/libsoup/soup-gnutls.c +++ b/libsoup/soup-gnutls.c @@ -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); diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 2b13228..009d702 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -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, diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h index 056799d..fe07892 100644 --- a/libsoup/soup-session.h +++ b/libsoup/soup-session.h @@ -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" diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index 00fd881..2bbf22d 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -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); } diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index 079297b..4106e86 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -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, diff --git a/tests/test-utils.c b/tests/test-utils.c index 0b13ca1..8d7e9f6 100644 --- a/tests/test-utils.c +++ b/tests/test-utils.c @@ -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); -- 2.7.4