From 1cf1ec55bc7647e737d7ec41bfe1def721269c02 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Tue, 6 Aug 2019 13:15:23 +0100 Subject: Support SSL verification through new about: handler In doing this, also propagate why the certificates were bad so that the page can display a reason. We will need FatMessages for all these. Signed-off-by: Daniel Silverstone --- content/fetch.h | 18 +----- content/fetchers/curl.c | 43 +++++++++++++ desktop/browser_window.c | 145 ++++++++++++++++++++++++++++++++++++++------ frontends/amiga/gui.c | 1 - frontends/atari/gui.c | 1 - frontends/gtk/gui.c | 1 - frontends/riscos/gui.c | 1 - frontends/windows/main.c | 2 - include/netsurf/ssl_certs.h | 61 +++++++++++++++++++ utils/corestringlist.h | 1 + utils/messages.c | 48 +++++++++++++++ utils/messages.h | 9 +++ 12 files changed, 291 insertions(+), 40 deletions(-) create mode 100644 include/netsurf/ssl_certs.h diff --git a/content/fetch.h b/content/fetch.h index 7c02fb0d7..66be857f8 100644 --- a/content/fetch.h +++ b/content/fetch.h @@ -28,6 +28,7 @@ #include "utils/config.h" #include "utils/nsurl.h" #include "utils/inet.h" +#include "netsurf/ssl_certs.h" struct content; struct fetch; @@ -88,23 +89,6 @@ struct fetch_multipart_data { bool file; /**< Item is a file */ }; -/** - * ssl certificate information for certificate error message - */ -struct ssl_cert_info { - long version; /**< Certificate version */ - char not_before[32]; /**< Valid from date */ - char not_after[32]; /**< Valid to date */ - int sig_type; /**< Signature type */ - char serialnum[64]; /**< Serial number */ - char issuer[256]; /**< Issuer details */ - char subject[256]; /**< Subject details */ - int cert_type; /**< Certificate type */ -}; - -/** maximum number of X509 certificates in chain for TLS connection */ -#define MAX_SSL_CERTS 10 - typedef void (*fetch_callback)(const fetch_msg *msg, void *p); /** diff --git a/content/fetchers/curl.c b/content/fetchers/curl.c index f5649e0c3..345f16ce1 100644 --- a/content/fetchers/curl.c +++ b/content/fetchers/curl.c @@ -555,6 +555,49 @@ fetch_curl_report_certs_upstream(struct curl_fetch_info *f) ssl_certs[depth].cert_type = X509_certificate_type(certs[depth].cert, X509_get_pubkey(certs[depth].cert)); + + /* error code (if any) */ + switch (certs[depth].err) { + case X509_V_OK: + ssl_certs[depth].err = SSL_CERT_ERR_OK; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + /* fallthrough */ + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + ssl_certs[depth].err = SSL_CERT_ERR_BAD_ISSUER; + break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + /* fallthrough */ + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + /* fallthrough */ + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + /* fallthrough */ + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + ssl_certs[depth].err = SSL_CERT_ERR_BAD_SIG; + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + /* fallthrough */ + case X509_V_ERR_CRL_NOT_YET_VALID: + ssl_certs[depth].err = SSL_CERT_ERR_TOO_YOUNG; + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + /* fallthrough */ + case X509_V_ERR_CRL_HAS_EXPIRED: + ssl_certs[depth].err = SSL_CERT_ERR_TOO_OLD; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + ssl_certs[depth].err = SSL_CERT_ERR_SELF_SIGNED; + break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + ssl_certs[depth].err = SSL_CERT_ERR_CHAIN_SELF_SIGNED; + break; + case X509_V_ERR_CERT_REVOKED: + ssl_certs[depth].err = SSL_CERT_ERR_REVOKED; + break; + default: + ssl_certs[depth].err = SSL_CERT_ERR_UNKNOWN; + break; + } } msg.type = FETCH_CERTS; diff --git a/desktop/browser_window.c b/desktop/browser_window.c index d74b56a77..a6d3ae92c 100644 --- a/desktop/browser_window.c +++ b/desktop/browser_window.c @@ -844,23 +844,45 @@ browser_window_content_done(struct browser_window *bw) * Handle query responses from SSL requests */ static nserror -browser_window__handle_query_response(bool proceed, void *pw) +browser_window__handle_ssl_query_response(bool proceed, void *pw) { struct browser_window *bw = (struct browser_window *)pw; - nserror res = NSERROR_OK; - if (proceed) { - /* We want to restart the request, with the loading - * context + /* If we're in the process of loading, stop the load */ + if (bw->loading_content != NULL) { + /* We had a loading content (maybe auth page?) */ + browser_window_stop(bw); + browser_window_remove_caret(bw, false); + browser_window_destroy_children(bw); + } + + if (!proceed) { + /* We're processing a "back to safety", do a rough-and-ready + * nav to the old 'current' parameters, with any post data + * stripped away */ - res = browser_window__navigate_internal(bw, &bw->loading_parameters); + if (bw->current_parameters.post_urlenc != NULL) { + free(bw->current_parameters.post_urlenc); + bw->current_parameters.post_urlenc = NULL; + } - if (res != NSERROR_OK) { - NSLOG(netsurf, WARNING, "Unable to navigate after query proceeds"); + if (bw->current_parameters.post_multipart != NULL) { + fetch_multipart_data_destroy(bw->current_parameters.post_multipart); + bw->current_parameters.post_multipart = NULL; } + + bw->current_parameters.flags &= ~BW_NAVIGATE_HISTORY; + bw->internal_nav = false; + return browser_window__navigate_internal(bw, &bw->current_parameters); } - return res; + /* We're processing a "proceed" attempt from the form */ + /* First, we permit the SSL */ + urldb_set_cert_permissions(bw->loading_parameters.url, true); + + /* And then we navigate to the original loading parameters */ + bw->internal_nav = false; + return browser_window__navigate_internal(bw, &bw->loading_parameters); } /** @@ -1073,6 +1095,70 @@ out: return err; } +/** + * Handle a certificate verification request (BAD_CERTS) during a fetch + */ +static nserror +browser_window__handle_bad_certs(struct browser_window *bw, + nsurl *url) +{ + struct browser_fetch_parameters params; + nserror err; + /* Initially we don't know WHY the SSL cert was bad */ + const char *reason = messages_get_sslcode(SSL_CERT_ERR_UNKNOWN); + size_t n; + + memset(¶ms, 0, sizeof(params)); + + err = nsurl_create("about:query/ssl", ¶ms.url); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "siteurl", + nsurl_access(url)); + if (err != NSERROR_OK) { + goto out; + } + + for (n = 0; n < bw->loading_ssl_info.num; ++n) { + size_t idx = bw->loading_ssl_info.num - (n + 1); + ssl_cert_err err = bw->loading_ssl_info.certs[idx].err; + if (err != SSL_CERT_ERR_OK) { + reason = messages_get_sslcode(err); + break; + } + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "reason", + reason); + if (err != NSERROR_OK) { + goto out; + } + + /* Now we issue the fetch */ + bw->internal_nav = true; + err = browser_window__navigate_internal(bw, ¶ms); + if (err != NSERROR_OK) { + goto out; + } + + err = guit->misc->cert_verify(url, + bw->loading_ssl_info.certs, + bw->loading_ssl_info.num, + browser_window__handle_ssl_query_response, + bw); + + if (err == NSERROR_NOT_IMPLEMENTED) { + err = NSERROR_OK; + } +out: + browser_window__free_fetch_parameters(¶ms); + return err; +} + /** * Handle errors during content fetch */ @@ -1129,14 +1215,7 @@ browser_window__handle_error(struct browser_window *bw, res = browser_window__handle_login(bw, message, url); break; case NSERROR_BAD_CERTS: - res = guit->misc->cert_verify(url, - bw->loading_ssl_info.certs, - bw->loading_ssl_info.num, - browser_window__handle_query_response, - bw); - if (res != NSERROR_OK) { - NSLOG(netsurf, DEBUG, "Unable to start GUI callback for SSL certs"); - } + res = browser_window__handle_bad_certs(bw, url); break; default: break; @@ -2986,6 +3065,8 @@ browser_window_navigate(struct browser_window *bw, if (scheme == corestring_lwc_about) { if (path == corestring_lwc_query_auth) { is_internal = true; + } else if (path == corestring_lwc_query_ssl) { + is_internal = true; } } lwc_string_unref(scheme); @@ -3331,6 +3412,32 @@ browser_window__navigate_internal_query_auth(struct browser_window *bw, } +/** + * Internal navigation handler for the SSL/privacy query page. + * + * If the parameters indicate we're processing a *response* from the handler + * then we deal with that, otherwise we pass it on to the about: handler + */ +static nserror +browser_window__navigate_internal_query_ssl(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + bool is_proceed = false, is_back = false; + + assert(params->post_multipart != NULL); + + is_proceed = fetch_multipart_data_find(params->post_multipart, "proceed") != NULL; + is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL; + + if (!(is_proceed || is_back)) { + /* This is a request, so pass it on */ + return browser_window__navigate_internal_real(bw, params); + } + + return browser_window__handle_ssl_query_response(is_proceed, bw); +} + + nserror browser_window__navigate_internal(struct browser_window *bw, struct browser_fetch_parameters *params) @@ -3356,6 +3463,10 @@ browser_window__navigate_internal(struct browser_window *bw, lwc_string_unref(path); return browser_window__navigate_internal_query_auth(bw, params); } + if (path == corestring_lwc_query_ssl) { + lwc_string_unref(path); + return browser_window__navigate_internal_query_ssl(bw, params); + } lwc_string_unref(path); /* Fall through to a normal about: fetch */ diff --git a/frontends/amiga/gui.c b/frontends/amiga/gui.c index af9322e53..a81de1692 100644 --- a/frontends/amiga/gui.c +++ b/frontends/amiga/gui.c @@ -6086,7 +6086,6 @@ static struct gui_misc_table amiga_misc_table = { .quit = gui_quit, .launch_url = gui_launch_url, - .cert_verify = ami_cert_verify, }; /** Normal entry point from OS */ diff --git a/frontends/atari/gui.c b/frontends/atari/gui.c index cce4e13ee..517289d49 100644 --- a/frontends/atari/gui.c +++ b/frontends/atari/gui.c @@ -1107,7 +1107,6 @@ static struct gui_misc_table atari_misc_table = { .warning = atari_warn_user, .quit = gui_quit, - .cert_verify = gui_cert_verify, }; /* #define WITH_DBG_LOGFILE 1 */ diff --git a/frontends/gtk/gui.c b/frontends/gtk/gui.c index 740543b44..384f3fccc 100644 --- a/frontends/gtk/gui.c +++ b/frontends/gtk/gui.c @@ -1072,7 +1072,6 @@ static struct gui_misc_table nsgtk_misc_table = { .quit = gui_quit, .launch_url = gui_launch_url, - .cert_verify = gtk_cert_verify, .pdf_password = nsgtk_pdf_password, }; diff --git a/frontends/riscos/gui.c b/frontends/riscos/gui.c index ef215487d..169b89b1c 100644 --- a/frontends/riscos/gui.c +++ b/frontends/riscos/gui.c @@ -2431,7 +2431,6 @@ static struct gui_misc_table riscos_misc_table = { .quit = gui_quit, .launch_url = gui_launch_url, - .cert_verify = gui_cert_verify, }; diff --git a/frontends/windows/main.c b/frontends/windows/main.c index bae7815ae..ea9d99571 100644 --- a/frontends/windows/main.c +++ b/frontends/windows/main.c @@ -312,8 +312,6 @@ static nserror nsw32_messages_init(char **respaths) static struct gui_misc_table win32_misc_table = { .schedule = win32_schedule, .warning = win32_warning, - - .cert_verify = nsw32_cert_verify, }; /** diff --git a/include/netsurf/ssl_certs.h b/include/netsurf/ssl_certs.h new file mode 100644 index 000000000..a73dc604c --- /dev/null +++ b/include/netsurf/ssl_certs.h @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Daniel Silverstone + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * SSL related types and values + */ + +#ifndef NETSURF_SSL_CERTS_H_ +#define NETSURF_SSL_CERTS_H_ + +/** + * ssl certificate error status + */ +typedef enum { + SSL_CERT_ERR_OK, /**< Nothing wrong with this certificate */ + SSL_CERT_ERR_UNKNOWN, /**< Unknown error */ + SSL_CERT_ERR_BAD_ISSUER, /**< Bad issuer */ + SSL_CERT_ERR_BAD_SIG, /**< Bad signature on this certificate */ + SSL_CERT_ERR_TOO_YOUNG, /**< This certificate is not yet valid */ + SSL_CERT_ERR_TOO_OLD, /**< This certificate is no longer valid */ + SSL_CERT_ERR_SELF_SIGNED, /**< This certificate (or the chain) is self signed */ + SSL_CERT_ERR_CHAIN_SELF_SIGNED, /**< This certificate chain is self signed */ + SSL_CERT_ERR_REVOKED, /**< This certificate has been revoked */ +} ssl_cert_err; + +/** + * ssl certificate information for certificate error message + */ +struct ssl_cert_info { + long version; /**< Certificate version */ + char not_before[32]; /**< Valid from date */ + char not_after[32]; /**< Valid to date */ + int sig_type; /**< Signature type */ + char serialnum[64]; /**< Serial number */ + char issuer[256]; /**< Issuer details */ + char subject[256]; /**< Subject details */ + int cert_type; /**< Certificate type */ + ssl_cert_err err; /**< Whatever is wrong with this certificate */ +}; + +/** maximum number of X509 certificates in chain for TLS connection */ +#define MAX_SSL_CERTS 10 + +#endif /* NETSURF_SSL_CERTS_H_ */ diff --git a/utils/corestringlist.h b/utils/corestringlist.h index e6530c506..82fffe263 100644 --- a/utils/corestringlist.h +++ b/utils/corestringlist.h @@ -147,6 +147,7 @@ CORESTRING_LWC_VALUE(max_age, "max-age"); CORESTRING_LWC_VALUE(no_cache, "no-cache"); CORESTRING_LWC_VALUE(no_store, "no-store"); CORESTRING_LWC_VALUE(query_auth, "query/auth"); +CORESTRING_LWC_VALUE(query_ssl, "query/ssl"); /* mime types */ CORESTRING_LWC_VALUE(multipart_form_data, "multipart/form-data"); diff --git a/utils/messages.c b/utils/messages.c index 5beeba38d..29443f99e 100644 --- a/utils/messages.c +++ b/utils/messages.c @@ -343,6 +343,54 @@ const char *messages_get_errorcode(nserror code) return messages_get_ctx("Unknown", messages_hash); } +/* exported function documented in utils/messages.h */ +const char *messages_get_sslcode(ssl_cert_err code) +{ + switch (code) { + case SSL_CERT_ERR_OK: + /* Nothing wrong with this certificate */ + return messages_get_ctx("SSLCertErrOk", messages_hash); + + case SSL_CERT_ERR_UNKNOWN: + /* Unknown error */ + return messages_get_ctx("SSLCertErrUnknown", messages_hash); + + case SSL_CERT_ERR_BAD_ISSUER: + /* Bad issuer */ + return messages_get_ctx("SSLCertErrBadIssuer", messages_hash); + + case SSL_CERT_ERR_BAD_SIG: + /* Bad signature on this certificate */ + return messages_get_ctx("SSLCertErrBadSig", messages_hash); + + case SSL_CERT_ERR_TOO_YOUNG: + /* This certificate is not yet valid */ + return messages_get_ctx("SSLCertErrTooYoung", messages_hash); + + case SSL_CERT_ERR_TOO_OLD: + /* This certificate is no longer valid */ + return messages_get_ctx("SSLCertErrTooOld", messages_hash); + + case SSL_CERT_ERR_SELF_SIGNED: + /* This certificate is self signed */ + return messages_get_ctx("SSLCertErrSelfSigned", messages_hash); + + case SSL_CERT_ERR_CHAIN_SELF_SIGNED: + /* This certificate chain is self signed */ + return messages_get_ctx("SSLCertErrChainSelfSigned", messages_hash); + + case SSL_CERT_ERR_REVOKED: + /* This certificate has been revoked */ + return messages_get_ctx("SSLCertErrRevoked", messages_hash); + } + + /* The switch has no default, so the compiler should tell us when we + * forget to add messages for new error codes. As such, we should + * never get here. + */ + assert(0); + return messages_get_ctx("Unknown", messages_hash); +} /* exported function documented in utils/messages.h */ void messages_destroy(void) diff --git a/utils/messages.h b/utils/messages.h index 4024f7e77..635d6e8e4 100644 --- a/utils/messages.h +++ b/utils/messages.h @@ -36,6 +36,7 @@ #include #include "utils/errors.h" +#include "netsurf/ssl_certs.h" /** * Read keys and values from messages file into the standard Messages hash. @@ -78,6 +79,14 @@ const char *messages_get(const char *key); */ const char *messages_get_errorcode(nserror code); +/** + * lookup of a message by SSL error code from the standard Messages hash. + * + * \param code ssl error code + * \return message text + */ +const char *messages_get_sslcode(ssl_cert_err code); + /** * Formatted message from a key in the global message hash. * -- cgit v1.2.3