about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJosh Rickmar <jrick@devio.us>2012-06-07 12:54:25 -0400
committerJosh Rickmar <jrick@devio.us>2012-06-07 12:54:25 -0400
commit5d21e3a3954e947f889f024b8ba9db9e616aa28e (patch)
tree7e44dd9fae763b256db81361d278ad2830c1bffa
parent07616810c81eb3e7073c9ca00e28f451c7bfb091 (diff)
downloadxombrero-5d21e3a3954e947f889f024b8ba9db9e616aa28e.tar.gz
Implement a warn_cert_changes setting to warn users when the remote
ssl certificate is different from a previously cached certificate to
help prevent against MITM attacks.  Prompt the user with an action to
take (show remote cert, allow for that session, or cache the new
remote cert).
-rw-r--r--about.c146
-rw-r--r--settings.c16
-rw-r--r--xombrero.125
-rw-r--r--xombrero.c161
-rw-r--r--xombrero.conf1
-rw-r--r--xombrero.h27
6 files changed, 334 insertions, 42 deletions
diff --git a/about.c b/about.c
index 012e52e..5c69a40 100644
--- a/about.c
+++ b/about.c
@@ -68,6 +68,7 @@
 #define XT_XTP_FL		(4)	/* favorites */
 #define XT_XTP_SL		(5)	/* search */
 #define XT_XTP_AB		(6)	/* about */
+#define XT_XTP_SV		(7)	/* security violation */
 
 /* XTP download actions */
 #define XT_XTP_DL_LIST		(1)
@@ -96,6 +97,11 @@
 /* XPT about actions */
 #define XT_XTP_AB_EDIT_CONF	(1)
 
+/* XTP security violation actions */
+#define XT_XTP_SV_SHOW_CERT	(1)
+#define XT_XTP_SV_ALLOW_SESSION	(2)
+#define XT_XTP_SV_CACHE		(3)
+
 int			js_show_wl(struct tab *, struct karg *);
 int			pl_show_wl(struct tab *, struct karg *);
 int			set(struct tab *, struct karg *);
@@ -125,6 +131,7 @@ struct about_type about_list[] = {
 	{ XT_URI_ABOUT_PLUGINWL,	pl_show_wl },
 	{ XT_URI_ABOUT_WEBKIT,		about_webkit },
 	{ XT_URI_ABOUT_SEARCH,		xtp_page_sl },
+	{ XT_URI_ABOUT_SECVIOLATION,	NULL },
 };
 
 struct search_type {
@@ -156,6 +163,7 @@ char			*cl_session_key;	/* cookie list */
 char			*fl_session_key;	/* favorites list */
 char			*sl_session_key;	/* search */
 char			*ab_session_key;	/* about */
+char			*sv_session_key;	/* secviolation */
 
 int			updating_ab_tabs = 0;
 int			updating_fl_tabs = 0;
@@ -163,6 +171,7 @@ int			updating_dl_tabs = 0;
 int			updating_hl_tabs = 0;
 int			updating_cl_tabs = 0;
 int			updating_sl_tabs = 0;
+int			updating_sv_tabs = 0;
 struct download_list	downloads;
 
 size_t
@@ -879,6 +888,51 @@ xtp_handle_sl(struct tab *t, uint8_t cmd, int arg)
 	g_free(uri);
 }
 
+void
+xtp_handle_sv(struct tab *t, uint8_t cmd, int id)
+{
+	SoupURI			*soupuri = NULL;
+	struct karg		args = {0};
+	struct secviolation	find, *sv;
+	struct sv_ignore	*svi = NULL;
+
+	find.xtp_arg = id;
+	if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
+		return;
+
+	args.ptr = (void *)sv->t;
+	args.s = sv->uri;
+
+	switch (cmd) {
+	case XT_XTP_SV_SHOW_CERT:
+		args.i = XT_SHOW;
+		cert_cmd(t, &args);
+		break;
+	case XT_XTP_SV_ALLOW_SESSION:
+		soupuri = soup_uri_new(sv->uri);
+		svi = malloc(sizeof(struct sv_ignore));
+		svi->domain = g_strdup(soupuri->host);
+		RB_INSERT(sv_ignore_list, &svil, svi);
+		load_uri(t, sv->uri);
+		focus_webview(t);
+		break;
+	case XT_XTP_SV_CACHE:
+		args.i = XT_CACHE;
+		cert_cmd(t, &args);
+		load_uri(t, sv->uri);
+		focus_webview(t);
+		break;
+	default:
+		show_oops(t, "%s: invalid secviolation command", __func__);
+		break;
+	};
+
+	g_free(sv->uri);
+	if (soupuri)
+		soup_uri_free(soupuri);
+	RB_REMOVE(secviolation_list, &svl, sv);
+}
+
 /* link an XTP class to it's session key and handler function */
 struct xtp_despatch {
 	uint8_t			xtp_class;
@@ -893,6 +947,7 @@ struct xtp_despatch		xtp_despatches[] = {
 	{ XT_XTP_CL, &cl_session_key, xtp_handle_cl },
 	{ XT_XTP_SL, &sl_session_key, xtp_handle_sl },
 	{ XT_XTP_AB, &ab_session_key, xtp_handle_ab },
+	{ XT_XTP_SV, &sv_session_key, xtp_handle_sv },
 	{ XT_XTP_INVALID, NULL, NULL }
 };
 
@@ -928,6 +983,7 @@ xtp_generate_keys(void)
 	generate_xtp_session_key(&cl_session_key);
 	generate_xtp_session_key(&fl_session_key);
 	generate_xtp_session_key(&ab_session_key);
+	generate_xtp_session_key(&sv_session_key);
 }
 
 /*
@@ -1126,6 +1182,25 @@ update_about_tabs(struct tab *apart_from)
 	}
 }
 
+/*
+ * update all secviolation tabs apart from one. Pass NULL if
+ * you want to update all.
+ */
+void
+update_secviolation_tabs(struct tab *apart_from)
+{
+	struct tab			*t;
+
+	if (!updating_sv_tabs) {
+		updating_sv_tabs = 1; /* stop infinite recursion */
+		TAILQ_FOREACH(t, &tabs, entry)
+			if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SV)
+			    && (t != apart_from))
+				xtp_page_sv(t, NULL);
+		updating_sv_tabs = 0;
+	}
+}
+
 int
 xtp_page_ab(struct tab *t, struct karg *args)
 {
@@ -1189,6 +1264,9 @@ xtp_page_ab(struct tab *t, struct karg *args)
 	g_free(body);
 
 	load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
+
+	update_about_tabs(t);
+
 	g_free(page);
 
 	return (0);
@@ -1749,6 +1827,74 @@ xtp_page_sl(struct tab *t, struct karg *args)
 }
 
 int
+xtp_page_sv(struct tab *t, struct karg *args)
+{
+	SoupURI			*soupuri;
+	static int		arg = 0;
+	struct secviolation	find, *sv;
+	char			*page, *body;
+
+	if (t == NULL)
+		show_oops(NULL, "secviolation invalid parameters");
+
+	/* Generate a new session key for next page instance.
+	 * This only happens for the top level call to xtp_page_ab()
+	 * in which case updating_sv_tabs = 0.
+	 */
+	if (!updating_sv_tabs)
+		generate_xtp_session_key(&sv_session_key);
+
+	if (args == NULL) {
+		find.xtp_arg = t->xtp_arg;
+		sv = RB_FIND(secviolation_list, &svl, &find);
+		if (sv == NULL)
+			return (-1);
+	} else {
+		sv = g_malloc(sizeof(struct secviolation));
+		sv->xtp_arg = ++arg;
+		t->xtp_arg = arg;
+		sv->t = t;
+		sv->uri = args->s;
+		RB_INSERT(secviolation_list, &svl, sv);
+	}
+
+	if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
+		return (-1);
+
+	body = g_strdup_printf(
+	    "The domain of the page you have tried to access, %s, has a "
+	    "different remote certificate then the local cached version from a "
+	    "previous visit.  As a security precaution to help prevent against "
+	    "man-in-the-middle attacks, please choose one of the following "
+	    "actions to continue, or disable the <tt>warn_cert_changes</tt> "
+	    "setting in your xombrero configuration."
+	    "<p><b>Choose an action:"
+	    "<br><a href='%s%d/%s/%d/%d'>Show Certificate</a>"
+	    "<br><a href='%s%d/%s/%d/%d'>Allow for this Session</a>"
+	    "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>",
+	    soupuri->host,
+	    XT_XTP_STR, XT_XTP_SV, sv_session_key, XT_XTP_SV_SHOW_CERT,
+		sv->xtp_arg,
+	    XT_XTP_STR, XT_XTP_SV, sv_session_key, XT_XTP_SV_ALLOW_SESSION,
+		sv->xtp_arg,
+	    XT_XTP_STR, XT_XTP_SV, sv_session_key, XT_XTP_SV_CACHE,
+		sv->xtp_arg);
+
+	page = get_html_page("Security Violation", body, "", 0);
+	g_free(body);
+
+	update_secviolation_tabs(t);
+
+	load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION);
+
+	g_free(page);
+	if (soupuri)
+		soup_uri_free(soupuri);
+
+	return (0);
+}
+
+int
 startpage(struct tab *t, struct karg *args)
 {
 	char			*page, *body, *b;
diff --git a/settings.c b/settings.c
index 8a567b2..eabc2ce 100644
--- a/settings.c
+++ b/settings.c
@@ -105,6 +105,7 @@ char		*external_editor = NULL;
 int		referer_mode = XT_DS_REFERER_MODE;
 char		*referer_custom = NULL;
 int		download_notifications = XT_DS_DOWNLOAD_NOTIFICATIONS;
+int		warn_cert_changes = 0;
 
 char		*cmd_font_name = NULL;	/* these are all set at startup */
 char		*oops_font_name = NULL;
@@ -186,6 +187,7 @@ int		set_url_regex(char *);
 int		set_userstyle_global(char *);
 int		set_external_editor(char *);
 int		set_xterm_workaround(char *);
+int		set_warn_cert_changes(char *);
 
 void		walk_mime_type(struct settings *, void (*)(struct settings *,
 		    char *, void *), void *);
@@ -420,6 +422,7 @@ struct settings		rs[] = {
 	{ "referer",			XT_S_STR, 0, NULL, NULL,&s_referer, NULL, set_referer_rt },
 	{ "download_notifications",	XT_S_INT, 0,		&download_notifications, NULL, NULL, NULL, set_download_notifications },
 	{ "include_config",		XT_S_STR, 0, NULL,	&include_config, NULL, NULL, NULL },
+	{ "warn_cert_changes",		XT_S_INT, 0,		&warn_cert_changes, NULL, NULL, NULL, set_warn_cert_changes },
 
 	/* font settings */
 	{ "cmd_font",			XT_S_STR, 0, NULL, &cmd_font_name, NULL, NULL, set_cmd_font },
@@ -2142,6 +2145,19 @@ set_userstyle_global(char *value)
 	return (0);
 }
 
+int
+set_warn_cert_changes(char *value)
+{
+	int			tmp;
+	const char		*errstr;
+
+	tmp = strtonum(value, 0, 1, &errstr);
+	if (errstr)
+		return (-1);
+	warn_cert_changes = tmp;
+	return (0);
+}
+
 char *
 get_edit_mode(struct settings *s)
 {
diff --git a/xombrero.1 b/xombrero.1
index 6bd548d..b25b1d3 100644
--- a/xombrero.1
+++ b/xombrero.1
@@ -1369,12 +1369,13 @@ en_US.
 .It Cm ssl_ca_file
 If set to a valid PEM file
 all server certificates will be validated against it.
-The URL bar will be colored green (or blue when saved ) when the certificate is
+The URL bar will be colored green (or blue when saved) when the certificate is
 trusted and yellow when untrusted.
 .Pp
 If
 .Cm ssl_ca_file
-is not set then the URL bar will color all HTTPS connections red.
+is not set, the URL bar will be colored red if the certificate has not
+been saved, or blue if it has been saved.
 .Pp
 WebKit only supports a single PEM file.
 Many OS' or distributions have many PEM files.
@@ -1428,6 +1429,26 @@ user-agent string. May be specified several times for switching between
 user-agents.
 .It Cm userstyle_global
 When enabled new tabs will automatically be displayed in low contrast mode.
+.It Cm warn_cert_changes
+When enabled all SSL certificates from HTTPS websites will be
+cached in the
+.Cm ~/.xombrero/certs_cache/
+directory.
+On each page load, if the remote certificate differs from the cached
+local version, a warning page with options of what to do next will be displayed
+instead of the requested page.
+This feature may be used in addition to the coloring of the URL bar and
+can be used to help prevent against man-in-the-middle attacks, even if
+the new remote certificate is signed by a trusted certificate
+authority in
+.Cm ssl_ca_file .
+Default is 0.
+.Pp
+If a remote certificate has changed and you wish to cache the new one,
+you must manually remove the old cached certificate from the
+.Cm ~/.xombrero/certs_cache/
+directory and refresh the page.
+The certificate will be named the same as the domain.
 .It Cm window_height
 Set the default height of the browser window.
 .It Cm window_width
diff --git a/xombrero.c b/xombrero.c
index 20894e8..cb4a4ac 100644
--- a/xombrero.c
+++ b/xombrero.c
@@ -85,6 +85,7 @@ TAILQ_HEAD(command_list, command_entry);
 #define XT_DIR			(".xombrero")
 #define XT_CACHE_DIR		("cache")
 #define XT_CERT_DIR		("certs")
+#define XT_CERT_CACHE_DIR	("certs_cache")
 #define XT_JS_DIR		("js")
 #define XT_SESSIONS_DIR		("sessions")
 #define XT_TEMP_DIR		("tmp")
@@ -222,6 +223,8 @@ struct command_list	chl;
 struct command_list	shl;
 struct command_entry	*history_at;
 struct command_entry	*search_at;
+struct secviolation_list	svl;
+struct sv_ignore_list	svil;
 int			undo_count;
 int			cmd_history_count = 0;
 int			search_history_count = 0;
@@ -655,6 +658,7 @@ show_oops(struct tab *at, const char *fmt, ...)
 
 char			work_dir[PATH_MAX];
 char			certs_dir[PATH_MAX];
+char			certs_cache_dir[PATH_MAX];
 char			js_dir[PATH_MAX];
 char			cache_dir[PATH_MAX];
 char			sessions_dir[PATH_MAX];
@@ -697,6 +701,20 @@ download_rb_cmp(struct download *e1, struct download *e2)
 }
 RB_GENERATE(download_list, download, entry, download_rb_cmp);
 
+int
+secviolation_rb_cmp(struct secviolation *s1, struct secviolation *s2)
+{
+	return (s1->xtp_arg < s2->xtp_arg ? -1 : s1->xtp_arg > s2->xtp_arg);
+}
+RB_GENERATE(secviolation_list, secviolation, entry, secviolation_rb_cmp);
+
+int
+sv_ignore_rb_cmp(struct sv_ignore *s1, struct sv_ignore *s2)
+{
+	return (strcmp(s1->domain, s2->domain));
+}
+RB_GENERATE(sv_ignore_list, sv_ignore, entry, sv_ignore_rb_cmp);
+
 struct valid_url_types {
 	char		*type;
 } vut[] = {
@@ -704,6 +722,7 @@ struct valid_url_types {
 	{ "https://" },
 	{ "ftp://" },
 	{ "file://" },
+	{ XT_URI_ABOUT },
 	{ XT_XTP_STR },
 };
 
@@ -835,7 +854,8 @@ load_uri(struct tab *t, gchar *uri)
 
 	if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
 		for (i = 0; i < about_list_size(); i++)
-			if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
+			if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name) &&
+			    about_list[i].func != NULL) {
 				bzero(&args, sizeof args);
 				about_list[i].func(t, &args);
 				gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
@@ -1778,18 +1798,17 @@ statusbar_modify_attr(struct tab *t, const char *text, const char *base)
 
 void
 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
-    size_t cert_count, char *domain)
+    size_t cert_count, const char *domain, const char *dir)
 {
 	size_t			cert_buf_sz;
 	char			cert_buf[64 * 1024], file[PATH_MAX];
 	int			i;
 	FILE			*f;
-	GdkColor		color;
 
 	if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
 		return;
 
-	snprintf(file, sizeof file, "%s" PS "%s", certs_dir, domain);
+	snprintf(file, sizeof file, "%s" PS "%s", dir, domain);
 	if ((f = fopen(file, "w")) == NULL) {
 		show_oops(t, "Can't create cert file %s %s",
 		    file, strerror(errno));
@@ -1809,10 +1828,6 @@ save_certs(struct tab *t, gnutls_x509_crt_t *certs,
 		}
 	}
 
-	/* not the best spot but oh well */
-	gdk_color_parse(XT_COLOR_BLUE, &color);
-	gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
-	statusbar_modify_attr(t, XT_COLOR_BLACK, XT_COLOR_BLUE);
 done:
 	fclose(f);
 }
@@ -1825,7 +1840,7 @@ enum cert_trust {
 };
 
 enum cert_trust
-load_compare_cert(const gchar *uri, const gchar **error_str)
+load_compare_cert(const gchar *uri, const gchar **error_str, const char *dir)
 {
 	char			domain[8182], file[PATH_MAX];
 	char			cert_buf[64 * 1024], r_cert_buf[64 * 1024];
@@ -1867,7 +1882,7 @@ load_compare_cert(const gchar *uri, const gchar **error_str)
 		goto done;
 	}
 
-	snprintf(file, sizeof file, "%s" PS "%s", certs_dir, domain);
+	snprintf(file, sizeof file, "%s" PS "%s", dir, domain);
 	if ((f = fopen(file, "r")) == NULL) {
 		if (!error)
 			rv = CERT_TRUSTED;
@@ -1880,7 +1895,7 @@ load_compare_cert(const gchar *uri, const gchar **error_str)
 		    cert_buf, &cert_buf_sz)) {
 			goto freeit;
 		}
-		if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
+		if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1 && !feof(f)) {
 			rv = CERT_BAD; /* critical */
 			goto freeit;
 		}
@@ -1901,7 +1916,7 @@ done:
 		close(s);
 
 	/* only complain if we didn't save it locally */
-	if (error && rv != CERT_LOCAL) {
+	if (strlen(ssl_ca_file) != 0 && error && rv != CERT_LOCAL) {
 		strlcpy(serr, "Certificate exception(s): ", sizeof serr);
 		if (error & GNUTLS_CERT_INVALID)
 			strlcat(serr, "invalid, ", sizeof serr);
@@ -1935,7 +1950,6 @@ done:
 int
 cert_cmd(struct tab *t, struct karg *args)
 {
-	struct stat		sb;
 	const gchar		*uri, *error_str = NULL;
 	char			domain[8182];
 	int			s = -1;
@@ -1943,16 +1957,14 @@ cert_cmd(struct tab *t, struct karg *args)
 	gnutls_session_t	gsession;
 	gnutls_x509_crt_t	*certs;
 	gnutls_certificate_credentials_t xcred;
+	GdkColor		color;
 
 	if (t == NULL)
 		return (1);
 
-	if (stat(ssl_ca_file, &sb)) {
-		show_oops(t, "Can't open CA file: %s", ssl_ca_file);
-		return (1);
-	}
-
-	if ((uri = get_uri(t)) == NULL) {
+	if (args->s != NULL)
+		uri = args->s;
+	else if ((uri = get_uri(t)) == NULL) {
 		show_oops(t, "Invalid URI");
 		return (1);
 	}
@@ -1975,8 +1987,13 @@ cert_cmd(struct tab *t, struct karg *args)
 
 	if (args->i & XT_SHOW)
 		show_certs(t, certs, cert_count, "Certificate Chain");
-	else if (args->i & XT_SAVE)
-		save_certs(t, certs, cert_count, domain);
+	else if (args->i & XT_SAVE) {
+		save_certs(t, certs, cert_count, domain, certs_dir);
+		gdk_color_parse(XT_COLOR_BLUE, &color);
+		gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
+		statusbar_modify_attr(t, XT_COLOR_BLACK, XT_COLOR_BLUE);
+	} else if (args->i & XT_CACHE)
+		save_certs(t, certs, cert_count, domain, certs_cache_dir);
 
 	free_connection_certs(certs, cert_count);
 done:
@@ -1989,6 +2006,65 @@ done:
 	return (0);
 }
 
+/*
+ * args must be allocated dynamically as the thread that added this function
+ * to the idle loop no longer exists
+ */
+gboolean
+warn_cert_cache_differs_idle(struct karg *args)
+{
+	if (args == NULL) {
+		show_oops(NULL, "%s: invalid parameters", __func__);
+		/* return 0 to not re-add function to the idle loop */
+		return (0);
+	}
+	xtp_page_sv((struct tab *)args->ptr, args);
+	free(args);
+	return (0);
+}
+
+int
+check_cert_changes(struct tab *t, const char *uri)
+{
+	SoupURI			*soupuri = NULL;
+	struct karg		args = {0};
+	struct sv_ignore	find;
+	const char		*errstr = NULL;
+	struct karg		*argsp;
+
+	if (!(warn_cert_changes && g_str_has_prefix(uri, "https://")))
+		return (0);
+
+	switch (load_compare_cert(uri, &errstr, certs_cache_dir)) {
+	case CERT_LOCAL:
+		/* The cached certificate is identical */
+		break;
+	case CERT_TRUSTED:	/* FALLTHROUGH */
+	case CERT_UNTRUSTED:
+		/* cache new certificate */
+		args.i = XT_CACHE;
+		cert_cmd(t, &args);
+		break;
+	case CERT_BAD:
+		if ((soupuri = soup_uri_new(uri)) == NULL)
+			break;
+		find.domain = soupuri->host;
+		if (RB_FIND(sv_ignore_list, &svil, &find))
+			break;
+		t->xtp_meaning = XT_XTP_TAB_MEANING_SV;
+		argsp = malloc(sizeof(struct karg));
+		bzero(argsp, sizeof(struct karg));
+		argsp->s = g_strdup((char *)uri);
+		argsp->ptr = (void *)t;
+		g_idle_add((GSourceFunc)warn_cert_cache_differs_idle, argsp);
+		break;
+	}
+
+	if (soupuri)
+		soup_uri_free(soupuri);
+	return (0);
+}
+
 int
 remove_cookie(int index)
 {
@@ -2704,7 +2780,7 @@ command(struct tab *t, struct karg *args)
 			sp = NULL;
 		}
 		s = sl;
-		for (i = 0; i < sizeof subs / sizeof (struct prompt_sub); ++i) {
+		for (i = 0; i < LENGTH(subs); ++i) {
 			sv = g_strsplit(sl, subs[i].s, -1);
 			if (sl)
 				g_free(sl);
@@ -3537,15 +3613,17 @@ color_address_bar(gpointer p)
 #endif
 
 	col_str = XT_COLOR_YELLOW;
-	switch (load_compare_cert(u, &error_str)) {
+	switch (load_compare_cert(u, &error_str, certs_dir)) {
 	case CERT_LOCAL:
 		col_str = XT_COLOR_BLUE;
 		break;
 	case CERT_TRUSTED:
-		col_str = XT_COLOR_GREEN;
+		col_str = (strlen(ssl_ca_file) == 0) ? XT_COLOR_RED
+		    : XT_COLOR_GREEN;
 		break;
 	case CERT_UNTRUSTED:
-		col_str = XT_COLOR_YELLOW;
+		col_str = (strlen(ssl_ca_file) == 0) ? XT_COLOR_RED
+		    : XT_COLOR_YELLOW;
 		break;
 	case CERT_BAD:
 		col_str = XT_COLOR_RED;
@@ -3610,10 +3688,16 @@ done:
 }
 
 void
+check_certs(gpointer p)
+{
+	color_address_bar((struct tab *)p);
+	check_cert_changes((struct tab *)p, get_uri((struct tab *)p));
+}
+
+void
 show_ca_status(struct tab *t, const char *uri)
 {
 	GdkColor		color;
-	struct stat		sb;
 	gchar			*col_str = XT_COLOR_WHITE, *text, *base;
 
 	DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
@@ -3622,20 +3706,10 @@ show_ca_status(struct tab *t, const char *uri)
 	if (t == NULL)
 		return;
 
-	if (uri == NULL)
-		goto done;
-	if (stat(ssl_ca_file, &sb)) {
-		if (g_str_has_prefix(uri, "http://"))
-			goto done;
-		if (g_str_has_prefix(uri, "https://")) {
-			col_str = XT_COLOR_RED;
-			goto done;
-		}
-		return;
-	}
-	if (g_str_has_prefix(uri, "http://") ||
+	if (uri == NULL || g_str_has_prefix(uri, "http://") ||
 	    !g_str_has_prefix(uri, "https://"))
 		goto done;
+
 #ifdef USE_THREADS
 	/*
 	 * It is not necessary to see if the thread is already running.
@@ -3644,9 +3718,9 @@ show_ca_status(struct tab *t, const char *uri)
 	 */
 
 	/* thread the coloring of the address bar */
-	t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
+	t->thread = g_thread_create((GThreadFunc)check_certs, t, TRUE, NULL);
 #else
-	color_address_bar(t);
+	check_certs(t);
 #endif
 	return;
 
@@ -7834,6 +7908,8 @@ main(int argc, char **argv)
 	RB_INIT(&pl_wl);
 	RB_INIT(&downloads);
 	RB_INIT(&st_tree);
+	RB_INIT(&svl);
+	RB_INIT(&svil);
 
 	TAILQ_INIT(&sessions);
 	TAILQ_INIT(&tabs);
@@ -7981,6 +8057,11 @@ main(int argc, char **argv)
 	snprintf(certs_dir, sizeof certs_dir, "%s" PS "%s", work_dir, XT_CERT_DIR);
 	xxx_dir(certs_dir);
 
+	/* cert changes dir */
+	snprintf(certs_cache_dir, sizeof certs_cache_dir, "%s" PS "%s",
+	    work_dir, XT_CERT_CACHE_DIR);
+	xxx_dir(certs_cache_dir);
+
 	/* sessions dir */
 	snprintf(sessions_dir, sizeof sessions_dir, "%s" PS "%s",
 	    work_dir, XT_SESSIONS_DIR);
diff --git a/xombrero.conf b/xombrero.conf
index e732265..0c4dc14 100644
--- a/xombrero.conf
+++ b/xombrero.conf
@@ -60,6 +60,7 @@
 # enable_favicon_entry	= 0
 # enable_favicon_tabs	= 1
 # referer		= always
+# warn_cert_changes	= 1
 
 # See http://www.xroxy.com/proxylist.php for a good list of open
 # proxies.
diff --git a/xombrero.h b/xombrero.h
index b204705..df0b0fe 100644
--- a/xombrero.h
+++ b/xombrero.h
@@ -246,6 +246,7 @@ struct tab {
 	int			ctrl_click;
 	gchar			*status;
 	int			xtp_meaning; /* identifies dls/favorites */
+	int			xtp_arg;
 	gchar			*tmp_uri;
 	int			popup; /* 1 if cmd_entry has popup visible */
 #ifdef USE_THREADS
@@ -284,6 +285,7 @@ struct karg {
 	int		i;
 	char		*s;
 	int		precount;
+	void		*ptr;
 };
 
 struct download {
@@ -317,6 +319,22 @@ struct strict_transport {
 RB_HEAD(strict_transport_tree, strict_transport);
 RB_PROTOTYPE(strict_transport_tree, strict_transport, entry, strict_transport_rb_cmp);
 
+struct secviolation {
+	RB_ENTRY(secviolation)	entry;
+	int			xtp_arg;
+	struct tab		*t;
+	char			*uri;
+};
+RB_HEAD(secviolation_list, secviolation);
+RB_PROTOTYPE(secviolation_list, secviolation, entry, secviolation_rb_cmp);
+
+struct sv_ignore {
+	RB_ENTRY(sv_ignore)	entry;
+	char			*domain;
+};
+RB_HEAD(sv_ignore_list, sv_ignore);
+RB_PROTOTYPE(sv_ignore_list, sv_ignore, entry, sv_ignore_rb_cmp);
+
 /* utility */
 #define XT_NAME			("xombrero")
 #define XT_CB_HANDLED		(TRUE)
@@ -405,6 +423,7 @@ char			*tld_get_suffix(const char *);
 #define XT_URI_ABOUT_STARTPAGE	("startpage")
 #define XT_URI_ABOUT_WEBKIT	("webkit")
 #define XT_URI_ABOUT_SEARCH	("search")
+#define XT_URI_ABOUT_SECVIOLATION ("secviolation")
 
 struct about_type {
 	char		*name;
@@ -430,6 +449,7 @@ int			xtp_page_dl(struct tab *, struct karg *);
 int			xtp_page_fl(struct tab *, struct karg *);
 int			xtp_page_hl(struct tab *, struct karg *);
 int			xtp_page_sl(struct tab *, struct karg *);
+int			xtp_page_sv(struct tab *, struct karg *);
 int			parse_xtp_url(struct tab *, const char *);
 int			add_favorite(struct tab *, struct karg *);
 void			update_favorite_tabs(struct tab *);
@@ -454,6 +474,7 @@ void			startpage_add(const char *, ...);
 #define XT_XTP_TAB_MEANING_HL		(8)  /* history manager in this tab */
 #define XT_XTP_TAB_MEANING_SL		(9)  /* search engine chooser */
 #define XT_XTP_TAB_MEANING_AB		(10) /* about:about in this tab */
+#define XT_XTP_TAB_MEANING_SV		(18) /* about:secviolation in tab */
 
 /* whitelists */
 #define XT_WL_TOGGLE		(1<<0)
@@ -468,6 +489,7 @@ void			startpage_add(const char *, ...);
 #define XT_DELETE		(1<<9)
 #define XT_SAVE			(1<<10)
 #define XT_OPEN			(1<<11)
+#define XT_CACHE		(1<<12)
 
 #define XT_WL_INVALID		(0)
 #define XT_WL_JAVASCRIPT	(1)
@@ -686,6 +708,8 @@ int		urlaction(struct tab *, struct karg *);
 int		userstyle(struct tab *, struct karg *);
 struct tab	*get_current_tab(void);
 int		resizetab(struct tab *, struct karg *);
+int		cert_cmd(struct tab *, struct karg *);
+void		focus_webview(struct tab *);
 
 #define		XT_DL_START	(0)
 #define		XT_DL_RESTART	(1)
@@ -765,6 +789,7 @@ extern int	enable_favicon_tabs;
 extern int	referer_mode;
 extern char	*referer_custom;
 extern int	download_notifications;
+extern int	warn_cert_changes;
 extern regex_t	url_re;
 
 /* globals */
@@ -800,6 +825,8 @@ extern struct sp_list		spl;
 extern struct user_agent_list	ua_list;
 extern struct cmd_alias_list	cal;
 extern struct custom_uri_list	cul;
+extern struct secviolation_list	svl;
+extern struct sv_ignore_list	svil;
 extern int			user_agent_count;
 
 extern PangoFontDescription	*cmd_font;