Commit 68b0f857 authored by Patrick Griffis's avatar Patrick Griffis Committed by Michael Catanzaro

Move password management out of web process

This is done to move all libsecret usage out of the webprocess
for sandboxing. This also makes it more robust against vulernabilities
such as Spectre moving it out of process.
parent 0487d089
Pipeline #27181 passed with stage
in 2 minutes and 35 seconds
......@@ -34,10 +34,12 @@
#include "ephy-filters-manager.h"
#include "ephy-flatpak-utils.h"
#include "ephy-history-service.h"
#include "ephy-password-manager.h"
#include "ephy-profile-utils.h"
#include "ephy-settings.h"
#include "ephy-snapshot-service.h"
#include "ephy-tabs-catalog.h"
#include "ephy-uri-helpers.h"
#include "ephy-uri-tester-shared.h"
#include "ephy-view-source-handler.h"
#include "ephy-web-app-utils.h"
......@@ -61,6 +63,7 @@ typedef struct {
WebKitUserContentManager *user_content;
EphyDownloadsManager *downloads_manager;
EphyPermissionsManager *permissions_manager;
EphyPasswordManager *password_manager;
EphyAboutHandler *about_handler;
EphyViewSourceHandler *source_handler;
char *guid;
......@@ -77,7 +80,6 @@ enum {
PAGE_CREATED,
ALLOW_TLS_CERTIFICATE,
ALLOW_UNSAFE_BROWSING,
FORM_AUTH_DATA_SAVE_REQUESTED,
SENSITIVE_FORM_FOCUSED,
LAST_SIGNAL
......@@ -102,6 +104,46 @@ G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, DZL_TYPE_APPLICATION,
G_IMPLEMENT_INTERFACE (EPHY_TYPE_TABS_CATALOG,
ephy_embed_shell_tabs_catalog_iface_init))
static EphyWebView *
ephy_embed_shell_get_view_for_page_id (EphyEmbedShell *self,
guint64 page_id,
const char *origin)
{
GList *windows = gtk_application_get_windows (GTK_APPLICATION (self));
for (GList *l = windows; l && l->data; l = l->next) {
g_autoptr(GList) tabs = ephy_embed_container_get_children (l->data);
for (GList *t = tabs; t && t->data; t = t->next) {
EphyWebView *ephy_view = ephy_embed_get_web_view (t->data);
WebKitWebView *web_view = WEBKIT_WEB_VIEW (ephy_view);
if (webkit_web_view_get_page_id (web_view) != page_id)
continue;
g_autofree char *real_origin = ephy_uri_to_security_origin (webkit_web_view_get_uri (web_view));
if (g_strcmp0 (real_origin, origin)) {
g_debug ("Extension's origin '%s' doesn't match real origin '%s'", origin, real_origin);
return NULL;
}
return ephy_view;
}
}
return NULL;
}
static EphyWebExtensionProxy *
ephy_embed_shell_get_extension_proxy_for_page_id (EphyEmbedShell *self,
guint64 page_id,
const char *origin)
{
EphyWebView *view = ephy_embed_shell_get_view_for_page_id (self, page_id, origin);
return view ? ephy_web_view_get_web_extension_proxy (view) : NULL;
}
static GList *
tabs_catalog_get_tabs_info (EphyTabsCatalog *catalog)
{
......@@ -166,6 +208,7 @@ ephy_embed_shell_dispose (GObject *object)
g_clear_object (&priv->source_handler);
g_clear_object (&priv->user_content);
g_clear_object (&priv->downloads_manager);
g_clear_object (&priv->password_manager);
g_clear_object (&priv->permissions_manager);
g_clear_object (&priv->web_context);
g_clear_pointer (&priv->guid, g_free);
......@@ -176,28 +219,6 @@ ephy_embed_shell_dispose (GObject *object)
G_OBJECT_CLASS (ephy_embed_shell_parent_class)->dispose (object);
}
static void
web_extension_form_auth_data_message_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
EphyEmbedShell *shell)
{
guint request_id;
guint64 page_id;
const char *origin;
const char *username;
GVariant *variant;
gchar *message_str;
message_str = jsc_value_to_string (webkit_javascript_result_get_js_value (message));
variant = g_variant_parse (G_VARIANT_TYPE ("(utss)"), message_str, NULL, NULL, NULL);
g_free (message_str);
g_variant_get (variant, "(ut&s&s)", &request_id, &page_id, &origin, &username);
g_signal_emit (shell, signals[FORM_AUTH_DATA_SAVE_REQUESTED], 0,
request_id, page_id, origin, username);
g_variant_unref (variant);
}
static void
web_extension_sensitive_form_focused_message_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
......@@ -323,6 +344,214 @@ web_extension_about_apps_message_received_cb (WebKitUserContentManager *manager,
g_free (app_id);
}
typedef struct {
EphyEmbedShell *shell;
char *origin;
gint32 promise_id;
gint32 page_id;
} PasswordManagerData;
static void
password_manager_query_finished_cb (GList *records,
PasswordManagerData *data)
{
EphyPasswordRecord *record;
const char *username = NULL;
const char *password = NULL;
record = records && records->data ? EPHY_PASSWORD_RECORD (records->data) : NULL;
if (record) {
username = ephy_password_record_get_username (record);
password = ephy_password_record_get_password (record);
}
EphyWebExtensionProxy *proxy = ephy_embed_shell_get_extension_proxy_for_page_id (data->shell,
data->page_id,
data->origin);
if (proxy)
ephy_web_extension_proxy_password_query_response (proxy, username, password, data->promise_id);
g_object_unref (data->shell);
g_free (data->origin);
g_free (data);
}
static char *
property_to_string_or_null (JSCValue *value,
const char *name)
{
g_autoptr(JSCValue) prop = jsc_value_object_get_property (value, name);
if (jsc_value_is_null (prop) || jsc_value_is_undefined (prop))
return NULL;
return jsc_value_to_string (prop);
}
static int
property_to_int32 (JSCValue *value,
const char *name)
{
g_autoptr(JSCValue) prop = jsc_value_object_get_property (value, name);
return jsc_value_to_int32 (prop);
}
static void
web_extension_password_manager_query_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
EphyEmbedShell *shell)
{
EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
JSCValue *value = webkit_javascript_result_get_js_value (message);
g_autofree char *origin = property_to_string_or_null (value, "origin");
g_autofree char *target_origin = property_to_string_or_null (value, "targetOrigin");
g_autofree char *username = property_to_string_or_null (value, "username");
g_autofree char *username_field = property_to_string_or_null (value, "usernameField");
g_autofree char *password_field = property_to_string_or_null (value, "passwordField");
gint32 promise_id = property_to_int32 (value, "promiseID");
gint32 page_id = property_to_int32 (value, "pageID");
PasswordManagerData *data = g_new (PasswordManagerData, 1);
data->shell = g_object_ref (shell);
data->promise_id = promise_id;
data->page_id = page_id;
data->origin = g_steal_pointer (&origin);
ephy_password_manager_query (priv->password_manager,
NULL,
origin,
target_origin,
username,
username_field,
password_field,
(EphyPasswordManagerQueryCallback)password_manager_query_finished_cb,
data);
}
typedef struct {
EphyPasswordManager *password_manager;
EphyPermissionsManager *permissions_manager;
char *origin;
char *target_origin;
char *username;
char *password;
char *username_field;
char *password_field;
gboolean is_new;
} SaveAuthRequest;
static void
save_auth_request_free (SaveAuthRequest *request)
{
g_object_unref (request->password_manager);
g_object_unref (request->permissions_manager);
g_free (request->origin);
g_free (request->target_origin);
g_free (request->username);
g_free (request->password);
g_free (request->username_field);
g_free (request->password_field);
g_free (request);
}
static void
save_auth_request_response_cb (gint response_id,
SaveAuthRequest *data)
{
if (response_id == GTK_RESPONSE_REJECT) {
ephy_permissions_manager_set_permission (data->permissions_manager,
EPHY_PERMISSION_TYPE_SAVE_PASSWORD,
data->origin,
EPHY_PERMISSION_DENY);
} else if (response_id == GTK_RESPONSE_YES) {
ephy_password_manager_save (data->password_manager, data->origin, data->target_origin,
data->username, data->password, data->username_field,
data->password_field, data->is_new);
}
}
static void
web_extension_password_manager_save_real (EphyEmbedShell *shell,
JSCValue *value,
gboolean is_request)
{
EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
g_autofree char *origin = property_to_string_or_null (value, "origin");
g_autofree char *target_origin = property_to_string_or_null (value, "targetOrigin");
g_autofree char *username = property_to_string_or_null (value, "username");
g_autofree char *password = property_to_string_or_null (value, "password");
g_autofree char *username_field = property_to_string_or_null (value, "usernameField");
g_autofree char *password_field = property_to_string_or_null (value, "passwordField");
g_autoptr(JSCValue) is_new_prop = jsc_value_object_get_property (value, "isNew");
gboolean is_new = jsc_value_to_boolean (is_new_prop);
gint32 page_id = property_to_int32 (value, "pageID");
// This also sanity checks that a page isn't saving websites for other origins
EphyWebView *view = ephy_embed_shell_get_view_for_page_id (shell,
page_id,
origin);
if (!view)
return;
if (!is_request) {
ephy_password_manager_save (priv->password_manager, origin, target_origin, username,
password, username_field, password_field, is_new);
return;
}
SaveAuthRequest *request = g_new (SaveAuthRequest, 1);
request->password_manager = g_object_ref (priv->password_manager);
request->permissions_manager = g_object_ref (priv->permissions_manager);
request->origin = g_steal_pointer (&origin);
request->target_origin = g_steal_pointer (&target_origin);
request->username = g_steal_pointer (&username);
request->password = g_steal_pointer (&password);
request->username_field = g_steal_pointer (&username_field);
request->password_field = g_steal_pointer (&password_field);
request->is_new = is_new;
ephy_web_view_show_auth_form_save_request (view, origin, username,
(EphyPasswordSaveRequestCallback)save_auth_request_response_cb,
request, (GDestroyNotify)save_auth_request_free);
}
static void
web_extension_password_manager_save_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
EphyEmbedShell *shell)
{
JSCValue *value = webkit_javascript_result_get_js_value (message);
web_extension_password_manager_save_real (shell, value, FALSE);
}
static void
web_extension_password_manager_request_save_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
EphyEmbedShell *shell)
{
JSCValue *value = webkit_javascript_result_get_js_value (message);
web_extension_password_manager_save_real (shell, value, TRUE);
}
static void
web_extension_password_manager_cached_users_received_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *message,
EphyEmbedShell *shell)
{
EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
JSCValue *value = webkit_javascript_result_get_js_value (message);
g_autofree char *origin = jsc_value_to_string (jsc_value_object_get_property (value, "origin"));
gint32 promise_id = jsc_value_to_int32 (jsc_value_object_get_property (value, "promiseID"));
gint32 page_id = jsc_value_to_int32 (jsc_value_object_get_property (value, "pageID"));
GList *cached_users;
cached_users = ephy_password_manager_get_cached_users (priv->password_manager, origin);
EphyWebExtensionProxy *proxy = ephy_embed_shell_get_extension_proxy_for_page_id (
shell, page_id, origin);
if (proxy)
ephy_web_extension_proxy_password_cached_users_response (proxy, cached_users, promise_id);
}
static void
history_service_url_title_changed_cb (EphyHistoryService *service,
const char *url,
......@@ -932,13 +1161,6 @@ ephy_embed_shell_startup (GApplication *application)
G_CALLBACK (web_extension_unsafe_browsing_error_page_message_received_cb),
shell, 0);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"formAuthData",
priv->guid);
g_signal_connect_object (priv->user_content, "script-message-received::formAuthData",
G_CALLBACK (web_extension_form_auth_data_message_received_cb),
shell, 0);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"sensitiveFormFocused",
priv->guid);
......@@ -952,6 +1174,34 @@ ephy_embed_shell_startup (GApplication *application)
G_CALLBACK (web_extension_about_apps_message_received_cb),
shell, 0);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"passwordManagerQuery",
priv->guid);
g_signal_connect (priv->user_content, "script-message-received::passwordManagerQuery",
G_CALLBACK (web_extension_password_manager_query_received_cb),
shell);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"passwordManagerQueryUsernames",
priv->guid);
g_signal_connect (priv->user_content, "script-message-received::passwordManagerQueryUsernames",
G_CALLBACK (web_extension_password_manager_cached_users_received_cb),
shell);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"passwordManagerSave",
priv->guid);
g_signal_connect (priv->user_content, "script-message-received::passwordManagerSave",
G_CALLBACK (web_extension_password_manager_save_received_cb),
shell);
webkit_user_content_manager_register_script_message_handler_in_world (priv->user_content,
"passwordManagerRequestSave",
priv->guid);
g_signal_connect (priv->user_content, "script-message-received::passwordManagerRequestSave",
G_CALLBACK (web_extension_password_manager_request_save_received_cb),
shell);
ephy_embed_shell_setup_process_model (shell);
g_signal_connect_object (priv->web_context, "initialize-web-extensions",
G_CALLBACK (initialize_web_extensions),
......@@ -962,6 +1212,8 @@ ephy_embed_shell_startup (GApplication *application)
G_CALLBACK (initialize_notification_permissions),
shell, 0);
priv->password_manager = ephy_password_manager_new ();
/* Favicon Database */
if (priv->mode == EPHY_EMBED_SHELL_MODE_PRIVATE)
favicon_db_path = g_build_filename (ephy_dot_dir (), "icondatabase", NULL);
......@@ -1038,12 +1290,21 @@ ephy_embed_shell_shutdown (GApplication *application)
webkit_user_content_manager_unregister_script_message_handler (priv->user_content,
"unsafeBrowsingErrorPage");
webkit_user_content_manager_unregister_script_message_handler_in_world (priv->user_content,
"formAuthData",
"passwordManagerRequestSave",
priv->guid);
webkit_user_content_manager_unregister_script_message_handler_in_world (priv->user_content,
"sensitiveFormFocused",
priv->guid);
webkit_user_content_manager_unregister_script_message_handler (priv->user_content, "aboutApps");
webkit_user_content_manager_unregister_script_message_handler_in_world (priv->user_content,
"passwordManagerQuery",
priv->guid);
webkit_user_content_manager_unregister_script_message_handler_in_world (priv->user_content,
"passwordManagerSave",
priv->guid);
webkit_user_content_manager_unregister_script_message_handler_in_world (priv->user_content,
"passwordManagerQueryUsernames",
priv->guid);
g_list_foreach (priv->web_extensions, (GFunc)ephy_embed_shell_unwatch_web_extension, application);
......@@ -1224,29 +1485,6 @@ ephy_embed_shell_class_init (EphyEmbedShellClass *klass)
G_TYPE_NONE, 1,
G_TYPE_UINT64);
/**
* EphyEmbedShell::form-auth-data-save-requested:
* @shell: the #EphyEmbedShell
* @request_id: the identifier of the request
* @page_id: the identifier of the web page
* @hostname: the hostname
* @username: the username
*
* Emitted when a web page requests confirmation to save
* the form authentication data for the given @hostname and
* @username
*/
signals[FORM_AUTH_DATA_SAVE_REQUESTED] =
g_signal_new ("form-auth-data-save-requested",
EPHY_TYPE_EMBED_SHELL,
G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 4,
G_TYPE_UINT,
G_TYPE_UINT64,
G_TYPE_STRING,
G_TYPE_STRING);
/**
* EphyEmbedShell::sensitive-form-focused
* @shell: the #EphyEmbedShell
......@@ -1522,3 +1760,11 @@ ephy_embed_shell_get_search_engine_manager (EphyEmbedShell *shell)
priv->search_engine_manager = ephy_search_engine_manager_new ();
return priv->search_engine_manager;
}
EphyPasswordManager *
ephy_embed_shell_get_password_manager (EphyEmbedShell *shell)
{
EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
return priv->password_manager;
}
......@@ -28,6 +28,7 @@
#include "ephy-encodings.h"
#include "ephy-gsb-service.h"
#include "ephy-history-service.h"
#include "ephy-password-manager.h"
#include "ephy-permissions-manager.h"
#include "ephy-search-engine-manager.h"
......@@ -84,5 +85,6 @@ WebKitUserContentManager *ephy_embed_shell_get_user_content_manager (EphyEmbedSh
EphyDownloadsManager *ephy_embed_shell_get_downloads_manager (EphyEmbedShell *shell);
EphyPermissionsManager *ephy_embed_shell_get_permissions_manager (EphyEmbedShell *shell);
EphySearchEngineManager *ephy_embed_shell_get_search_engine_manager (EphyEmbedShell *shell);
EphyPasswordManager *ephy_embed_shell_get_password_manager (EphyEmbedShell *shell);
G_END_DECLS
......@@ -188,25 +188,6 @@ ephy_web_extension_proxy_new (GDBusConnection *connection)
return web_extension;
}
void
ephy_web_extension_proxy_form_auth_data_save_confirmation_response (EphyWebExtensionProxy *web_extension,
guint request_id,
gboolean response)
{
g_assert (EPHY_IS_WEB_EXTENSION_PROXY (web_extension));
if (!web_extension->proxy)
return;
g_dbus_proxy_call (web_extension->proxy,
"FormAuthDataSaveConfirmationResponse",
g_variant_new ("(ub)", request_id, response),
G_DBUS_CALL_FLAGS_NONE,
-1,
web_extension->cancellable,
NULL, NULL);
}
void
ephy_web_extension_proxy_history_set_urls (EphyWebExtensionProxy *web_extension,
GList *urls)
......@@ -313,3 +294,43 @@ ephy_web_extension_proxy_history_clear (EphyWebExtensionProxy *web_extension)
web_extension->cancellable,
NULL, NULL);
}
void
ephy_web_extension_proxy_password_cached_users_response (EphyWebExtensionProxy *web_extension,
GList *users,
gint32 id)
{
if (!web_extension->proxy)
return;
GList *l;
g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY);
for (l = users; l != NULL; l = l->next)
g_variant_builder_add (&builder, "s", l->data);
g_dbus_proxy_call (web_extension->proxy,
"PasswordQueryUsernamesResponse",
g_variant_new ("(asi)", &builder, id),
G_DBUS_CALL_FLAGS_NONE,
-1,
web_extension->cancellable,
NULL, NULL);
}
void
ephy_web_extension_proxy_password_query_response (EphyWebExtensionProxy *web_extension,
const char *username,
const char *password,
gint32 id)
{
if (!web_extension->proxy)
return;
g_dbus_proxy_call (web_extension->proxy,
"PasswordQueryResponse",
g_variant_new ("(ssi)", username ?: "", password ?: "", id),
G_DBUS_CALL_FLAGS_NONE,
-1,
web_extension->cancellable,
NULL, NULL);
}
\ No newline at end of file
......@@ -29,9 +29,6 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphyWebExtensionProxy, ephy_web_extension_proxy, EPHY, WEB_EXTENSION_PROXY, GObject)
EphyWebExtensionProxy *ephy_web_extension_proxy_new (GDBusConnection *connection);
void ephy_web_extension_proxy_form_auth_data_save_confirmation_response (EphyWebExtensionProxy *web_extension,
guint request_id,
gboolean response);
void ephy_web_extension_proxy_history_set_urls (EphyWebExtensionProxy *web_extension,
GList *urls);
void ephy_web_extension_proxy_history_set_url_thumbnail (EphyWebExtensionProxy *web_extension,
......@@ -45,5 +42,11 @@ void ephy_web_extension_proxy_history_delete_url
void ephy_web_extension_proxy_history_delete_host (EphyWebExtensionProxy *web_extension,
const char *host);
void ephy_web_extension_proxy_history_clear (EphyWebExtensionProxy *web_extension);
void ephy_web_extension_proxy_password_cached_users_response (EphyWebExtensionProxy *web_extension,
GList *users,
gint32 id);
void ephy_web_extension_proxy_password_query_response (EphyWebExtensionProxy *web_extension,
const char *username,
const char *password,
gint32 id);
G_END_DECLS
......@@ -44,7 +44,6 @@
#include "ephy-uri-helpers.h"
#include "ephy-view-source-handler.h"
#include "ephy-web-app-utils.h"
#include "ephy-web-extension-proxy.h"
#include "ephy-zoom.h"
#include <gio/gio.h>
......@@ -571,6 +570,56 @@ ephy_web_view_create_form_auth_save_confirmation_info_bar (EphyWebView *web_view
return info_bar;
}
typedef struct {
EphyPasswordSaveRequestCallback callback;
gpointer callback_data;
GDestroyNotify callback_destroy;
} SaveRequestData;
static void
save_auth_request_destroy (SaveRequestData *data,
GClosure *ignored)
{
if (data->callback_destroy)
data->callback_destroy (data->callback_data);
g_free (data);
}
static void
info_bar_save_request_response_cb (GtkInfoBar *info_bar,
gint response_id,
SaveRequestData *data)
{
g_assert (data->callback);
data->callback (response_id, data->callback_data);
gtk_widget_destroy (GTK_WIDGET (info_bar));
}
void
ephy_web_view_show_auth_form_save_request (EphyWebView *web_view,
const char *origin,
const char *username,
EphyPasswordSaveRequestCallback response_callback,
gpointer response_data,
GDestroyNotify response_destroy)
{
GtkWidget *info_bar;
info_bar = ephy_web_view_create_form_auth_save_confirmation_info_bar (web_view, origin, username);
SaveRequestData *data = g_new(SaveRequestData, 1);
data->callback = response_callback;
data->callback_data = response_data;
data->callback_destroy = response_destroy;
g_signal_connect_data (info_bar, "response",
G_CALLBACK (info_bar_save_request_response_cb),
data, (GClosureNotify)save_auth_request_destroy, 0);
gtk_widget_show (info_bar);
}
static void
update_navigation_flags (WebKitWebView *view)
{
......@@ -721,94 +770,6 @@ icon_changed_cb (EphyWebView *view,
_ephy_web_view_update_icon (view);
}
typedef struct {
EphyWebView *web_view;
guint request_id;
char *origin;
} FormAuthRequestData;
static FormAuthRequestData *
form_auth_request_data_new (EphyWebView *web_view,
guint request_id,
const char *origin)
{
FormAuthRequestData *data;
data = g_slice_new (FormAuthRequestData);
data->web_view = web_view;
data->request_id = request_id;
data->origin = g_strdup (origin);
return data;
}