[evolution-data-server] I#164 - HSTS (RFC 6797) support for "On The Web" calendars



commit 40ff7f93c22f9e04db1f6ab009088ae4e3bbe48b
Author: Milan Crha <mcrha redhat com>
Date:   Wed Oct 16 12:47:17 2019 +0200

    I#164 - HSTS (RFC 6797) support for "On The Web" calendars
    
    Closes https://gitlab.gnome.org/GNOME/evolution-data-server/issues/164

 src/calendar/backends/http/e-cal-backend-http.c | 77 +++++++++++++++++++++++--
 1 file changed, 73 insertions(+), 4 deletions(-)
---
diff --git a/src/calendar/backends/http/e-cal-backend-http.c b/src/calendar/backends/http/e-cal-backend-http.c
index 85622c67d..ea0cd42ef 100644
--- a/src/calendar/backends/http/e-cal-backend-http.c
+++ b/src/calendar/backends/http/e-cal-backend-http.c
@@ -40,6 +40,7 @@ struct _ECalBackendHttpPrivate {
        GInputStream *input_stream;
        GRecMutex conn_lock;
        GHashTable *components; /* gchar *uid ~> ICalComponent * */
+       gint64 hsts_until_time;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (ECalBackendHttp, e_cal_backend_http, E_TYPE_CAL_META_BACKEND)
@@ -48,10 +49,12 @@ static gchar *
 ecb_http_webcal_to_http_method (const gchar *webcal_str,
                                gboolean secure)
 {
-       if (secure && (strncmp ("http://";, webcal_str, sizeof ("http://";) - 1) == 0))
+       if (secure && (
+           g_str_has_prefix (webcal_str, "http://";) ||
+           g_str_has_prefix (webcal_str, "webcal://")))
                return g_strconcat ("https://";, webcal_str + sizeof ("http://";) - 1, NULL);
 
-       if (strncmp ("webcal://", webcal_str, sizeof ("webcal://") - 1))
+       if (!g_str_has_prefix (webcal_str, "webcal://"))
                return g_strdup (webcal_str);
 
        if (secure)
@@ -92,6 +95,8 @@ ecb_http_dup_uri (ECalBackendHttp *cbhttp)
                return NULL;
        }
 
+       secure_connection = secure_connection || (cbhttp->priv->hsts_until_time && g_get_real_time () <= 
cbhttp->priv->hsts_until_time);
+
        uri = ecb_http_webcal_to_http_method (uri_string, secure_connection);
 
        g_free (uri_string);
@@ -99,6 +104,59 @@ ecb_http_dup_uri (ECalBackendHttp *cbhttp)
        return uri;
 }
 
+/* https://tools.ietf.org/html/rfc6797 */
+static gint64
+ecb_http_extract_hsts_until_time (ECalBackendHttp *cbhttp)
+{
+       SoupMessage *message;
+       GTlsCertificate *cert = NULL;
+       GTlsCertificateFlags cert_errors = 0;
+       gint64 hsts_until_time = 0;
+
+       g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (cbhttp), hsts_until_time);
+       g_return_val_if_fail (cbhttp->priv->request, hsts_until_time);
+
+       message = soup_request_http_get_message (cbhttp->priv->request);
+       if (!message)
+               return hsts_until_time;
+
+       if (message->response_headers &&
+           soup_message_get_https_status (message, &cert, &cert_errors) &&
+           !cert_errors) {
+               const gchar *hsts_header;
+
+               hsts_header = soup_message_headers_get_one (message->response_headers, 
"Strict-Transport-Security");
+               if (hsts_header && *hsts_header) {
+                       GHashTable *params;
+
+                       params = soup_header_parse_semi_param_list (hsts_header);
+                       if (params) {
+                               const gchar *max_age;
+
+                               max_age = g_hash_table_lookup (params, "max-age");
+
+                               if (max_age && *max_age) {
+                                       gint64 value;
+
+                                       if (*max_age == '\"')
+                                               max_age++;
+
+                                       value = g_ascii_strtoll (max_age, NULL, 10);
+
+                                       if (value > 0)
+                                               hsts_until_time = g_get_real_time () + (value * 
G_USEC_PER_SEC);
+                               }
+
+                               soup_header_free_param_list (params);
+                       }
+               }
+       }
+
+       g_object_unref (message);
+
+       return hsts_until_time;
+}
+
 static gchar *
 ecb_http_read_stream_sync (GInputStream *input_stream,
                           goffset expected_length,
@@ -205,9 +263,14 @@ ecb_http_connect_sync (ECalMetaBackend *meta_backend,
                if (success) {
                        e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
                } else {
-                       guint status_code = message ? message->status_code : SOUP_STATUS_MALFORMED;
+                       guint status_code;
                        gboolean credentials_empty;
 
+                       if (local_error && local_error->domain == SOUP_HTTP_ERROR)
+                               status_code = local_error->code;
+                       else
+                               status_code = message ? message->status_code : SOUP_STATUS_MALFORMED;
+
                        credentials_empty = !credentials || !e_named_parameters_count (credentials);
 
                        *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
@@ -215,7 +278,8 @@ ecb_http_connect_sync (ECalMetaBackend *meta_backend,
                        /* because evolution knows only G_IO_ERROR_CANCELLED */
                        if (status_code == SOUP_STATUS_CANCELLED) {
                                g_set_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
-                                       "%s", message->reason_phrase);
+                                       "%s", (local_error && local_error->domain == SOUP_HTTP_ERROR) ? 
local_error->message :
+                                       (message && message->reason_phrase) ? message->reason_phrase : 
soup_status_get_phrase (status_code));
                        } else if (status_code == SOUP_STATUS_FORBIDDEN && credentials_empty) {
                                *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
                        } else if (status_code == SOUP_STATUS_UNAUTHORIZED) {
@@ -252,11 +316,16 @@ ecb_http_connect_sync (ECalMetaBackend *meta_backend,
        if (success) {
                cbhttp->priv->request = request;
                cbhttp->priv->input_stream = input_stream;
+               cbhttp->priv->hsts_until_time = ecb_http_extract_hsts_until_time (cbhttp);
 
                *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
        } else {
                g_clear_object (&request);
                g_clear_object (&input_stream);
+
+               if (*out_auth_result != E_SOURCE_AUTHENTICATION_REQUIRED &&
+                   *out_auth_result != E_SOURCE_AUTHENTICATION_REJECTED)
+                       cbhttp->priv->hsts_until_time = 0;
        }
 
        g_rec_mutex_unlock (&cbhttp->priv->conn_lock);


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