From 83f5332708d7b8cbb6a2efb76eef4bdeec318b5a Mon Sep 17 00:00:00 2001 From: John-Mark Bell Date: Sat, 21 Apr 2018 22:54:04 +0000 Subject: HSTS: teach llcache to update and enforce policy. --- content/llcache.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 13 deletions(-) diff --git a/content/llcache.c b/content/llcache.c index 58803ea71..30cb10c42 100644 --- a/content/llcache.c +++ b/content/llcache.c @@ -114,6 +114,8 @@ typedef struct { bool tried_with_tls_downgrade; /**< Whether we've tried TLS <= 1.0 */ bool outstanding_query; /**< Waiting for a query response */ + + bool tainted_tls; /**< Whether the TLS transport is tainted */ } llcache_fetch_ctx; /** @@ -1856,6 +1858,86 @@ static nserror llcache_object_add_user(llcache_object *object, return NSERROR_OK; } +/** + * Transform a request-URI based on HSTS policy + * + * \param url URL to transform + * \param result Pointer to location to receive transformed URL + * \param hsts_in_use Pointer to location to receive HSTS in-use flag + * \return NSERROR_OK on success, appropriate error otherwise + */ +static nserror llcache_hsts_transform_url(nsurl *url, nsurl **result, + bool *hsts_in_use) +{ + lwc_string *scheme = NULL; + bool match; + nserror error = NSERROR_OK; + + scheme = nsurl_get_component(url, NSURL_SCHEME); + if (lwc_string_caseless_isequal(scheme, corestring_lwc_http, + &match) != lwc_error_ok || match == false) { + /* Non-HTTP fetch: ignore */ + lwc_string_unref(scheme); + *result = nsurl_ref(url); + *hsts_in_use = false; + return error; + } + lwc_string_unref(scheme); + + if (urldb_get_hsts_enabled(url)) { + /* Only need to force HTTPS. If original port was explicitly + * specified as 80, nsurl_create/join will remove it (as + * it's redundant) */ + error = nsurl_replace_scheme(url, corestring_lwc_https, + result); + *hsts_in_use = (error == NSERROR_OK); + } else { + *result = nsurl_ref(url); + *hsts_in_use = false; + } + + return error; +} + +/** + * Update HSTS policy for target domain. + * + * \param object Newly-fetched cache object + * \return NSERROR_OK on success, appropriate error otherwise + */ +static nserror llcache_hsts_update_policy(llcache_object *object) +{ + size_t i; + lwc_string *scheme = NULL; + bool match = false; + + scheme = nsurl_get_component(object->url, NSURL_SCHEME); + if (lwc_string_caseless_isequal(scheme, corestring_lwc_https, + &match) != lwc_error_ok || match == false) { + /* Non-HTTPS fetch: ignore */ + lwc_string_unref(scheme); + return NSERROR_OK; + } + lwc_string_unref(scheme); + + if (object->fetch.tainted_tls) { + /* Transport is tainted: ignore */ + return NSERROR_OK; + } + + for (i = 0; i < object->num_headers; i++) { + if (strcasecmp("Strict-Transport-Security", + object->headers[i].name) == 0) { + urldb_set_hsts_policy(object->url, + object->headers[i].value); + /* Only process the first one we find */ + break; + } + } + + return NSERROR_OK; +} + /** * Handle FETCH_REDIRECT event * @@ -1871,10 +1953,10 @@ static nserror llcache_fetch_redirect(llcache_object *object, llcache_object *dest; llcache_object_user *user, *next; const llcache_post_data *post = object->fetch.post; - nsurl *url; + nsurl *url, *hsts_url; lwc_string *scheme; lwc_string *object_scheme; - bool match; + bool match, hsts_in_use; /* Extract HTTP response code from the fetch object */ long http_code = fetch_http_code(object->fetch.fetch); llcache_event event; @@ -1906,15 +1988,23 @@ static nserror llcache_fetch_redirect(llcache_object *object, if (error != NSERROR_OK) return error; + /* Perform HSTS transform */ + error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use); + if (error != NSERROR_OK) { + nsurl_unref(url); + return error; + } + nsurl_unref(url); + /* Inform users of redirect */ event.type = LLCACHE_EVENT_REDIRECT; event.data.redirect.from = object->url; - event.data.redirect.to = url; + event.data.redirect.to = hsts_url; error = llcache_send_event_to_users(object, &event); if (error != NSERROR_OK) { - nsurl_unref(url); + nsurl_unref(hsts_url); return error; } @@ -1922,7 +2012,7 @@ static nserror llcache_fetch_redirect(llcache_object *object, * A "validated" scheme is one over which we have some guarantee that * the source is trustworthy. */ object_scheme = nsurl_get_component(object->url, NSURL_SCHEME); - scheme = nsurl_get_component(url, NSURL_SCHEME); + scheme = nsurl_get_component(hsts_url, NSURL_SCHEME); /* resource: and about: are allowed to redirect anywhere */ if ((lwc_string_isequal(object_scheme, corestring_lwc_resource, @@ -1938,7 +2028,7 @@ static nserror llcache_fetch_redirect(llcache_object *object, &match) == lwc_error_ok && match == true)) { lwc_string_unref(object_scheme); lwc_string_unref(scheme); - nsurl_unref(url); + nsurl_unref(hsts_url); return NSERROR_OK; } } @@ -1947,8 +2037,8 @@ static nserror llcache_fetch_redirect(llcache_object *object, lwc_string_unref(object_scheme); /* Bail out if we've no way of handling this URL */ - if (fetch_can_fetch(url) == false) { - nsurl_unref(url); + if (fetch_can_fetch(hsts_url) == false) { + nsurl_unref(hsts_url); return NSERROR_OK; } @@ -1957,17 +2047,17 @@ static nserror llcache_fetch_redirect(llcache_object *object, post = NULL; } else if (http_code != 307 || post != NULL) { /** \todo 300, 305, 307 with POST */ - nsurl_unref(url); + nsurl_unref(hsts_url); return NSERROR_OK; } /* Attempt to fetch target URL */ - error = llcache_object_retrieve(url, object->fetch.flags, + error = llcache_object_retrieve(hsts_url, object->fetch.flags, object->fetch.referer, post, object->fetch.redirect_count + 1, &dest); /* No longer require url */ - nsurl_unref(url); + nsurl_unref(hsts_url); if (error != NSERROR_OK) return error; @@ -2059,6 +2149,8 @@ static nserror llcache_fetch_notmodified(llcache_object *object, /* Mark it complete */ object->fetch.state = LLCACHE_FETCH_COMPLETE; + (void) llcache_hsts_update_policy(object); + /* Old object will be flushed from the cache on the next poll */ return NSERROR_OK; @@ -2253,6 +2345,9 @@ static nserror llcache_fetch_cert_error(llcache_object *object, /* Invalidate cache-control data */ llcache_invalidate_cache_control_data(object); + /* Consider the TLS transport tainted */ + object->fetch.tainted_tls = true; + if (llcache->query_cb != NULL) { llcache_query query; @@ -2303,6 +2398,9 @@ static nserror llcache_fetch_ssl_error(llcache_object *object) /* Invalidate cache-control data */ llcache_invalidate_cache_control_data(object); + /* Consider the TLS transport tainted */ + object->fetch.tainted_tls = true; + if (object->fetch.tried_with_tls_downgrade == true) { /* Have already tried to downgrade, so give up */ llcache_event event; @@ -2684,6 +2782,8 @@ static void llcache_fetch_callback(const fetch_msg *msg, void *p) /* record when the fetch finished */ object->cache.fin_time = time(NULL); + (void) llcache_hsts_update_policy(object); + guit->misc->schedule(5000, llcache_persist, NULL); } break; @@ -3483,23 +3583,34 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t flags, nserror error; llcache_object_user *user; llcache_object *object; + nsurl *hsts_url; + bool hsts_in_use; + + /* Perform HSTS transform */ + error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use); + if (error != NSERROR_OK) { + return error; + } /* Can we fetch this URL at all? */ - if (fetch_can_fetch(url) == false) { + if (fetch_can_fetch(hsts_url) == false) { + nsurl_unref(hsts_url); return NSERROR_NO_FETCH_HANDLER; } /* Create a new object user */ error = llcache_object_user_new(cb, pw, &user); if (error != NSERROR_OK) { + nsurl_unref(hsts_url); return error; } /* Retrieve a suitable object from the cache, * creating a new one if needed. */ - error = llcache_object_retrieve(url, flags, referer, post, 0, &object); + error = llcache_object_retrieve(hsts_url, flags, referer, post, 0, &object); if (error != NSERROR_OK) { llcache_object_user_destroy(user); + nsurl_unref(hsts_url); return error; } @@ -3511,6 +3622,8 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t flags, /* Users exist which are now not caught up! */ llcache_users_not_caught_up(); + nsurl_unref(hsts_url); + return NSERROR_OK; } -- cgit v1.2.3