Blob Blame Raw
From 79f0ea7c0d5cb39cf1ab40afaea0e485aeb4bc49 Mon Sep 17 00:00:00 2001
From: David Zeuthen <davidz@redhat.com>
Date: Thu, 26 Apr 2012 14:35:37 -0400
Subject: [PATCH 7/9] udisks2: Support getting/storing LUKS encryption passphrase from keyring

Also use a nicer message when asking for the passphrase, e.g. use strings like

 'SD04G (SD Card Reader)'
 'WD 2500JB External (250 GB Hard Disk)'

instead of /dev/mmcblk0p1 or /dev/sdb1. If stored in the keyring, we
also use the following display name

 'Encryption passphrase for $DRIVE'

where $DRIVE is of the above form. This makes it easy for the user to
manage (for example, delete) pass-phrases using seahorse(1).

This was discussed in bug 674161.

 https://bugzilla.gnome.org/show_bug.cgi?id=674161

This commit adds/changes translatable strings but Tomas said he would
soon branch gvfs for gnome-3-at a point before this patch.

Signed-off-by: David Zeuthen <davidz@redhat.com>
---
 monitor/udisks2/Makefile.am         |    2 +
 monitor/udisks2/gvfsudisks2volume.c |  225 ++++++++++++++++++++++++++++++++++-
 2 files changed, 221 insertions(+), 6 deletions(-)

diff --git a/monitor/udisks2/Makefile.am b/monitor/udisks2/Makefile.am
index 776f670..c5ddd7c 100644
--- a/monitor/udisks2/Makefile.am
+++ b/monitor/udisks2/Makefile.am
@@ -20,6 +20,7 @@ gvfs_udisks2_volume_monitor_CFLAGS =		\
 	$(UDISKS2_CFLAGS)                       \
 	$(GUDEV_CFLAGS)                         \
 	$(LIBSYSTEMD_LOGIN_CFLAGS)		\
+	$(KEYRING_CFLAGS)			\
 	-DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\"	\
 	-DGVFS_LOCALEDIR=\""$(localedir)"\"	\
 	-DG_DISABLE_DEPRECATED			\
@@ -34,6 +35,7 @@ gvfs_udisks2_volume_monitor_LDADD  =		     			      	\
 	$(UDISKS2_LIBS)                                  			\
 	$(GUDEV_LIBS)                                  			      	\
 	$(LIBSYSTEMD_LOGIN_LIBS)						\
+	$(KEYRING_LIBS)								\
 	$(top_builddir)/common/libgvfscommon.la 			      	\
 	$(top_builddir)/monitor/proxy/libgvfsproxyvolumemonitordaemon-noin.la 	\
 	$(NULL)
diff --git a/monitor/udisks2/gvfsudisks2volume.c b/monitor/udisks2/gvfsudisks2volume.c
index 3ef5895..bb5c687 100644
--- a/monitor/udisks2/gvfsudisks2volume.c
+++ b/monitor/udisks2/gvfsudisks2volume.c
@@ -31,6 +31,10 @@
 #include <glib/gi18n-lib.h>
 #include <gio/gio.h>
 
+#ifdef HAVE_KEYRING
+#include <gnome-keyring.h>
+#endif
+
 #include "gvfsudisks2drive.h"
 #include "gvfsudisks2volume.h"
 #include "gvfsudisks2mount.h"
@@ -756,6 +760,17 @@ gvfs_udisks2_volume_get_activation_root (GVolume *_volume)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+#ifdef HAVE_KEYRING
+static GnomeKeyringPasswordSchema luks_passphrase_schema =
+{
+  GNOME_KEYRING_ITEM_GENERIC_SECRET,
+  {
+    {"gvfs-luks-uuid", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING},
+    {NULL, 0}
+  }
+};
+#endif
+
 struct MountData
 {
   GSimpleAsyncResult *simple;
@@ -769,9 +784,15 @@ struct MountData
 
   gchar *passphrase;
 
+  gchar *passphrase_from_keyring;
+  GPasswordSave password_save;
+
+  gchar *uuid_of_encrypted_to_unlock;
+  gchar *desc_of_encrypted_to_unlock;
   UDisksEncrypted *encrypted_to_unlock;
   UDisksFilesystem *filesystem_to_mount;
 
+  gboolean checked_keyring;
 };
 
 static void
@@ -795,7 +816,10 @@ mount_data_free (MountData *data)
     }
 
   g_free (data->passphrase);
+  g_free (data->passphrase_from_keyring);
 
+  g_free (data->uuid_of_encrypted_to_unlock);
+  g_free (data->desc_of_encrypted_to_unlock);
   g_clear_object (&data->encrypted_to_unlock);
   g_clear_object (&data->filesystem_to_mount);
   g_free (data);
@@ -912,6 +936,62 @@ do_mount (MountData *data)
 
 /* ------------------------------ */
 
+#ifdef HAVE_KEYRING
+static void
+luks_store_passphrase_cb (GnomeKeyringResult result,
+                          gpointer           user_data)
+{
+  MountData *data = user_data;
+  if (result == GNOME_KEYRING_RESULT_OK)
+    {
+      /* everything is good */
+      do_mount (data);
+    }
+  else
+    {
+      /* report failure */
+      g_simple_async_result_set_error (data->simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_FAILED,
+                                       _("Error storing passphrase in keyring (error code %d)"),
+                                       result);
+      g_simple_async_result_complete (data->simple);
+      mount_data_free (data);
+    }
+}
+#endif
+
+
+static void do_unlock (MountData *data);
+
+
+#ifdef HAVE_KEYRING
+static void
+luks_delete_passphrase_cb (GnomeKeyringResult result,
+                           gpointer           user_data)
+{
+  MountData *data = user_data;
+  if (result == GNOME_KEYRING_RESULT_OK)
+    {
+      /* with the bad passphrase out of the way, try again */
+      g_free (data->passphrase);
+      data->passphrase = NULL;
+      do_unlock (data);
+    }
+  else
+    {
+      /* report failure */
+      g_simple_async_result_set_error (data->simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_FAILED,
+                                       _("Error deleting invalid passphrase from keyring (error code %d)"),
+                                       result);
+      g_simple_async_result_complete (data->simple);
+      mount_data_free (data);
+    }
+}
+#endif
+
 static void
 unlock_cb (GObject       *source_object,
            GAsyncResult  *res,
@@ -927,6 +1007,26 @@ unlock_cb (GObject       *source_object,
                                             res,
                                             &error))
     {
+#ifdef HAVE_KEYRING
+      /* If this failed with a passphrase read from the keyring, try again
+       * this time prompting the user...
+       *
+       * TODO: ideally check against something like UDISKS_ERROR_PASSPHRASE_INVALID
+       * when such a thing is available in udisks
+       */
+      if (data->passphrase_from_keyring != NULL &&
+          g_strcmp0 (data->passphrase, data->passphrase_from_keyring) == 0)
+        {
+          /* nuke the invalid passphrase from keyring... */
+          gnome_keyring_delete_password (&luks_passphrase_schema,
+                                         luks_delete_passphrase_cb,
+                                         data,
+                                         NULL, /* GDestroyNotify */
+                                         "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+                                         NULL); /* sentinel */
+          goto out;
+        }
+#endif
       gvfs_udisks2_utils_udisks_error_to_gio_error (error);
       g_simple_async_result_take_error (data->simple, error);
       g_simple_async_result_complete (data->simple);
@@ -953,6 +1053,42 @@ unlock_cb (GObject       *source_object,
           goto out;
         }
 
+#ifdef HAVE_KEYRING
+      /* passphrase worked - save it in the keyring if requested */
+      if (data->password_save != G_PASSWORD_SAVE_NEVER)
+        {
+          const gchar *keyring;
+          gchar *display_name;
+
+          switch (data->password_save)
+            {
+            case G_PASSWORD_SAVE_NEVER:
+              g_assert_not_reached ();
+              break;
+            case G_PASSWORD_SAVE_FOR_SESSION:
+              keyring = GNOME_KEYRING_SESSION;
+              break;
+            case G_PASSWORD_SAVE_PERMANENTLY:
+              keyring = GNOME_KEYRING_DEFAULT;
+              break;
+            }
+
+          display_name = g_strdup_printf (_("Encryption passphrase for %s"),
+                                          data->desc_of_encrypted_to_unlock);
+
+          gnome_keyring_store_password (&luks_passphrase_schema,
+                                        keyring,
+                                        display_name,
+                                        data->passphrase,
+                                        luks_store_passphrase_cb,
+                                        data,
+                                        NULL, /* GDestroyNotify */
+                                        "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+                                        NULL); /* sentinel */
+          goto out;
+        }
+#endif
+
       /* OK, ready to rock */
       do_mount (data);
     }
@@ -961,8 +1097,6 @@ unlock_cb (GObject       *source_object,
   g_free (cleartext_device);
 }
 
-static void do_unlock (MountData *data);
-
 static void
 on_mount_operation_reply (GMountOperation       *mount_operation,
                           GMountOperationResult result,
@@ -1005,6 +1139,9 @@ on_mount_operation_reply (GMountOperation       *mount_operation,
     }
 
   data->passphrase = g_strdup (g_mount_operation_get_password (mount_operation));
+  data->password_save = g_mount_operation_get_password_save (mount_operation);
+
+  /* Don't save password in keyring just yet - check if it works first */
 
   do_unlock (data);
 
@@ -1049,6 +1186,27 @@ has_crypttab_passphrase (MountData *data)
   return ret;
 }
 
+#ifdef HAVE_KEYRING
+static void
+luks_find_passphrase_cb (GnomeKeyringResult result,
+                         const gchar       *string,
+                         gpointer           user_data)
+{
+  MountData *data = user_data;
+
+  /* Don't fail if a keyring error occured - just continue and request
+   * the passphrase from the user...
+   */
+  if (result == GNOME_KEYRING_RESULT_OK)
+    {
+      data->passphrase = g_strdup (string);
+      data->passphrase_from_keyring = g_strdup (string);
+    }
+  /* try again */
+  do_unlock (data);
+}
+#endif
+
 static void
 do_unlock (MountData *data)
 {
@@ -1065,6 +1223,21 @@ do_unlock (MountData *data)
         {
           gchar *message;
 
+#ifdef HAVE_KEYRING
+          /* check if the passphrase is in the user's keyring */
+          if (!data->checked_keyring)
+            {
+              data->checked_keyring = TRUE;
+              gnome_keyring_find_password (&luks_passphrase_schema,
+                                           luks_find_passphrase_cb,
+                                           data,
+                                           NULL, /* GDestroyNotify */
+                                           "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+                                           NULL); /* sentinel */
+              goto out;
+            }
+#endif
+
           if (data->mount_operation == NULL)
             {
               g_simple_async_result_set_error (data->simple,
@@ -1084,9 +1257,10 @@ do_unlock (MountData *data)
                                                                        "aborted",
                                                                        G_CALLBACK (on_mount_operation_aborted),
                                                                        data);
-          message = g_strdup_printf (_("Enter a password to unlock the volume\n"
-                                       "The device %s contains encrypted data."),
-                                     udisks_block_get_device (data->volume->block));
+          /* Translators: This is the message shown to users */
+          message = g_strdup_printf (_("Enter a passphrase to unlock the volume\n"
+                                       "The passphrase is needed to access encrypted data on %s."),
+                                     data->desc_of_encrypted_to_unlock);
 
           /* NOTE: We (currently) don't offer the user to save the
            * passphrase in the keyring or /etc/crypttab - compared to
@@ -1109,7 +1283,7 @@ do_unlock (MountData *data)
                                  NULL,
                                  NULL,
                                  G_ASK_PASSWORD_NEED_PASSWORD |
-                                 0/*G_ASK_PASSWORD_SAVING_SUPPORTED*/);
+                                 G_ASK_PASSWORD_SAVING_SUPPORTED);
           g_free (message);
           goto out;
         }
@@ -1209,6 +1383,45 @@ gvfs_udisks2_volume_mount (GVolume             *_volume,
       data->encrypted_to_unlock = udisks_object_get_encrypted (UDISKS_OBJECT (object));
       if (data->encrypted_to_unlock != NULL)
         {
+          UDisksDrive *udisks_drive;
+
+          /* This description is used in both the prompt and the display-name of
+           * the key stored in the user's keyring ...
+           *
+           * NOTE: we want a little bit more detail than what g_drive_get_name()
+           * gives us, since this is going to be used to refer to the device even
+           * when not plugged in
+           */
+          udisks_drive = udisks_client_get_drive_for_block (gvfs_udisks2_volume_monitor_get_udisks_client (volume->monitor),
+                                                            block);
+          if (udisks_drive != NULL)
+            {
+              gchar *drive_name;
+              gchar *drive_desc;
+              udisks_client_get_drive_info (gvfs_udisks2_volume_monitor_get_udisks_client (volume->monitor),
+                                            udisks_drive,
+                                            &drive_name,
+                                            &drive_desc,
+                                            NULL,  /* drive_icon */
+                                            NULL,  /* media_desc */
+                                            NULL); /* media_icon */
+              /* Translators: this is used to describe the drive the encrypted media
+               * is on - the first %s is the name (such as 'WD 2500JB External'), the
+               * second %s is the description ('250 GB Hard Disk').
+               */
+              data->desc_of_encrypted_to_unlock = g_strdup_printf (_("%s (%s)"),
+                                                                   drive_name,
+                                                                   drive_desc);
+              g_free (drive_desc);
+              g_free (drive_name);
+              g_object_unref (udisks_drive);
+            }
+          else
+            {
+              data->desc_of_encrypted_to_unlock = udisks_block_dup_preferred_device (block);
+            }
+          data->uuid_of_encrypted_to_unlock = udisks_block_dup_id_uuid (block);
+
           do_unlock (data);
           goto out;
         }
-- 
1.7.3.4