[Libguestfs] [PATCH v2 1/7] New APIs: cryptsetup-open and cryptsetup-close.

Richard W.M. Jones
Mon, 07 Sep 2020 09:43:54 -0000

This commit deprecates luks-open/luks-open-ro/luks-close for the more
generic sounding names cryptsetup-open/cryptsetup-close, which also
correspond directly to the cryptsetup commands.

The optional cryptsetup-open readonly flag is used to replace the
functionality of luks-open-ro.

The optional cryptsetup-open crypttype parameter can be used to select
the type (corresponding to cryptsetup open --type), which allows us to
open BitLocker-encrypted disks with no extra effort.  As a convenience
the crypttype parameter may be omitted, and libguestfs will use a
heuristic (based on vfs-type output) to try to determine the correct
type to use.

The deprecated functions and the new functions are all (re-)written in
OCaml.

There is no new test here, unfortunately.  It would be nice to test
Windows BitLocker support in this new API, however the Linux tools do
not support creating BitLocker disks, and while it is possible to
create one under Windows, the smallest compressed disk I could create
is 37M because of a mixture of the minimum support size for BitLocker
disks and the fact that encrypted parts of NTFS cannot be compressed.
---
 .gitignore                           |   1 +
 daemon/Makefile.am                   |   3 +
 daemon/cryptsetup.ml                 |  83 +++++++++++++++++++++
 daemon/luks.c                        |  83 ---------------------
 generator/actions_core.ml            | 106 +++++++++++++++------------
 generator/actions_core_deprecated.ml |  52 +++++++++++++
 generator/daemon.ml                  |   5 +-
 generator/proc_nr.ml                 |   2 +
 gobject/Makefile.inc                 |   2 +
 lib/MAX_PROC_NR                      |   2 +-
 lib/guestfs.pod                      |  17 +++--
 11 files changed, 216 insertions(+), 140 deletions(-)

diff --git a/.gitignore b/.gitignore
index ab4cd0667..39354de51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -147,6 +147,7 @@ Makefile.in
 /daemon/btrfs.mli
 /daemon/callbacks.ml
 /daemon/caml-stubs.c
+/daemon/cryptsetup.mli
 /daemon/daemon_config.ml
 /daemon/daemon_utils_tests
 /daemon/devsparts.mli
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index e57e5a506..480192e1c 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -39,6 +39,7 @@ generator_built = \
 	blkid.mli \
 	btrfs.mli \
 	callbacks.ml \
+	cryptsetup.mli \
 	devsparts.mli \
 	file.mli \
 	filearch.mli \
@@ -269,6 +270,7 @@ SOURCES_MLI = \
 	btrfs.mli \
 	callbacks.mli \
 	chroot.mli \
+	cryptsetup.mli \
 	daemon.mli \
 	daemon_config.mli \
 	devsparts.mli \
@@ -310,6 +312,7 @@ SOURCES_ML = \
 	chroot.ml \
 	blkid.ml \
 	btrfs.ml \
+	cryptsetup.ml \
 	devsparts.ml \
 	file.ml \
 	filearch.ml \
diff --git a/daemon/cryptsetup.ml b/daemon/cryptsetup.ml
new file mode 100644
index 000000000..e4e6a114c
--- /dev/null
+++ b/daemon/cryptsetup.ml
@@ -0,0 +1,83 @@
+(* guestfs-inspection
+ * Copyright (C) 2009-2020 Red Hat Inc.
+ *
+ * This program 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+open Unix
+
+open Std_utils
+
+open Utils
+
+let cryptsetup_open ?(readonly = false) ?crypttype device key mapname +  (*
/dev/mapper/mapname must not exist already. *)
+  let devmapper = sprintf "/dev/mapper/%s" mapname in
+  if is_block_device devmapper then
+    failwithf "%s: device already exists" devmapper;
+
+  (* Heuristically determine the encryption type. *)
+  let crypttype +    match crypttype with
+    | Some s -> s
+    | None ->
+       let t = Blkid.vfs_type (Mountable.of_device device) in
+       match t with
+       | "crypto_LUKS" -> "luks"
+       | "BitLocker" -> "bitlk"
+       | _ ->
+          failwithf "%s: unknown encrypted device type" t in
+
+  (* Write the key to a temporary file. *)
+  let keyfile, chan = Filename.open_temp_file "crypt"
".key" in
+  fchmod (descr_of_out_channel chan) 0o600;
+  output_string chan key;
+  close_out chan;
+
+  (* Make sure we always remove the temporary file. *)
+  protect ~f:(
+    fun () ->
+      let args = ref [] in
+      List.push_back_list args ["-d"; keyfile];
+      if readonly then List.push_back args "--readonly";
+      List.push_back_list args ["open"; device; mapname;
"--type"; crypttype];
+      ignore (command "cryptsetup" !args)
+  ) ~finally:(fun () -> unlink keyfile);
+
+  udev_settle ()
+
+let cryptsetup_close device +  (* Must be /dev/mapper/... *)
+  if not (String.is_prefix device "/dev/mapper/") then
+    failwithf "%s: you must call this on the /dev/mapper device created by
cryptsetup-open" device;
+
+  let mapname = String.sub device 12 (String.length device - 12) in
+  ignore (command "cryptsetup" ["close"; mapname]);
+
+  udev_settle ()
+
+(* Deprecated APIs for backwards compatibility.
+ *
+ * For convenient for existing callers we do not specify the
+ * crypttype parameter.  This means that callers of these APIs
+ * will be able to open BitLocker drives transparently.  (If
+ * we wanted to be strict we would pass ~crypttype:"luks"
+ * here, but that seems to have only downsides).
+ *)
+let luks_open device key mapname = cryptsetup_open device key mapname
+let luks_open_ro device key mapname +  cryptsetup_open ~readonly:true device
key mapname
+let luks_close = cryptsetup_close
diff --git a/daemon/luks.c b/daemon/luks.c
index d631cb100..51ced585d 100644
--- a/daemon/luks.c
+++ b/daemon/luks.c
@@ -81,89 +81,6 @@ remove_temp (char *tempfile)
   free (tempfile);
 }

-static int
-luks_open (const char *device, const char *key, const char *mapname,
-           int readonly)
-{
-  /* Sanity check: /dev/mapper/mapname must not exist already.  Note
-   * that the device-mapper control device (/dev/mapper/control) is
-   * always there, so you can't ever have mapname == "control".
-   */
-  CLEANUP_FREE char *devmapper = NULL;
-  if (asprintf (&devmapper, "/dev/mapper/%s", mapname) == -1) {
-    reply_with_perror ("asprintf");
-    return -1;
-  }
-  if (access (devmapper, F_OK) == 0) {
-    reply_with_error ("%s: device already exists", devmapper);
-    return -1;
-  }
-
-  char *tempfile = write_key_to_temp (key);
-  if (!tempfile)
-    return -1;
-
-  const char *argv[MAX_ARGS];
-  size_t i = 0;
-
-  ADD_ARG (argv, i, "cryptsetup");
-  ADD_ARG (argv, i, "-d");
-  ADD_ARG (argv, i, tempfile);
-  if (readonly) ADD_ARG (argv, i, "--readonly");
-  ADD_ARG (argv, i, "luksOpen");
-  ADD_ARG (argv, i, device);
-  ADD_ARG (argv, i, mapname);
-  ADD_ARG (argv, i, NULL);
-
-  CLEANUP_FREE char *err = NULL;
-  int r = commandv (NULL, &err, (const char * const *) argv);
-  remove_temp (tempfile);
-
-  if (r == -1) {
-    reply_with_error ("%s", err);
-    return -1;
-  }
-
-  udev_settle ();
-
-  return 0;
-}
-
-int
-do_luks_open (const char *device, const char *key, const char *mapname)
-{
-  return luks_open (device, key, mapname, 0);
-}
-
-int
-do_luks_open_ro (const char *device, const char *key, const char *mapname)
-{
-  return luks_open (device, key, mapname, 1);
-}
-
-int
-do_luks_close (const char *device)
-{
-  /* Must be /dev/mapper/... */
-  if (! STRPREFIX (device, "/dev/mapper/")) {
-    reply_with_error ("luks_close: you must call this on the /dev/mapper
device created by luks_open");
-    return -1;
-  }
-
-  const char *mapname = &device[12];
-
-  CLEANUP_FREE char *err = NULL;
-  int r = command (NULL, &err, "cryptsetup",
"luksClose", mapname, NULL);
-  if (r == -1) {
-    reply_with_error ("%s", err);
-    return -1;
-  }
-
-  udev_settle ();
-
-  return 0;
-}
-
 static int
 luks_format (const char *device, const char *key, int keyslot,
              const char *cipher)
diff --git a/generator/actions_core.ml b/generator/actions_core.ml
index 9a24a8d78..54156b2b8 100644
--- a/generator/actions_core.ml
+++ b/generator/actions_core.ml
@@ -5664,52 +5664,6 @@ will be able to see every block device.
 This command also clears the LVM cache and performs a volume
 group scan." };

-  { defaults with
-    name = "luks_open"; added = (1, 5, 1);
-    style = RErr, [String (Device, "device"); String (Key,
"key"); String (PlainString, "mapname")], [];
-    optional = Some "luks";
-    shortdesc = "open a LUKS-encrypted block device";
-    longdesc = "\
-This command opens a block device which has been encrypted
-according to the Linux Unified Key Setup (LUKS) standard.
-
-C<device> is the encrypted block device or partition.
-
-The caller must supply one of the keys associated with the
-LUKS block device, in the C<key> parameter.
-
-This creates a new block device called F</dev/mapper/mapname>.
-Reads and writes to this block device are decrypted from and
-encrypted to the underlying C<device> respectively.
-
-If this block device contains LVM volume groups, then
-calling C<guestfs_lvm_scan> with the C<activate>
-parameter C<true> will make them visible.
-
-Use C<guestfs_list_dm_devices> to list all device mapper
-devices." };
-
-  { defaults with
-    name = "luks_open_ro"; added = (1, 5, 1);
-    style = RErr, [String (Device, "device"); String (Key,
"key"); String (PlainString, "mapname")], [];
-    optional = Some "luks";
-    shortdesc = "open a LUKS-encrypted block device read-only";
-    longdesc = "\
-This is the same as C<guestfs_luks_open> except that a read-only
-mapping is created." };
-
-  { defaults with
-    name = "luks_close"; added = (1, 5, 1);
-    style = RErr, [String (Device, "device")], [];
-    optional = Some "luks";
-    shortdesc = "close a LUKS device";
-    longdesc = "\
-This closes a LUKS device that was created earlier by
-C<guestfs_luks_open> or C<guestfs_luks_open_ro>.  The
-C<device> parameter must be the name of the LUKS mapping
-device (ie. F</dev/mapper/mapname>) and I<not> the name
-of the underlying block device." };
-
   { defaults with
     name = "luks_format"; added = (1, 5, 2);
     style = RErr, [String (Device, "device"); String (Key,
"key"); Int "keyslot"], [];
@@ -9753,4 +9707,64 @@ is used)." };
     longdesc = "\
 This returns the UUID of the LUKS device C<device>." };

+  { defaults with
+    name = "cryptsetup_open"; added = (1, 43, 1);
+    style = RErr, [String (Device, "device"); String (Key,
"key"); String (PlainString, "mapname")], [OBool
"readonly"; OString "crypttype"];
+    impl = OCaml "Cryptsetup.cryptsetup_open";
+    optional = Some "luks";
+    test_excuse = "no way to format BitLocker, and smallest device is
huge";
+    shortdesc = "open an encrypted block device";
+    longdesc = "\
+This command opens a block device which has been encrypted
+according to the Linux Unified Key Setup (LUKS) standard,
+Windows BitLocker, or some other types.
+
+C<device> is the encrypted block device or partition.
+
+The caller must supply one of the keys associated with the
+encrypted block device, in the C<key> parameter.
+
+This creates a new block device called F</dev/mapper/mapname>.
+Reads and writes to this block device are decrypted from and
+encrypted to the underlying C<device> respectively.
+
+If the optional C<crypttype> parameter is not present then
+libguestfs tries to guess the correct type (for example
+LUKS or BitLocker).  However you can override this by
+specifying one of the following types:
+
+=over 4
+
+=item C<luks>
+
+A Linux LUKS device.
+
+=item C<bitlk>
+
+A Windows BitLocker device.
+
+=back
+
+The optional C<readonly> flag, if set to true, creates a
+read-only mapping.
+
+If this block device contains LVM volume groups, then
+calling C<guestfs_lvm_scan> with the C<activate>
+parameter C<true> will make them visible.
+
+Use C<guestfs_list_dm_devices> to list all device mapper
+devices." };
+
+  { defaults with
+    name = "cryptsetup_close"; added = (1, 43, 1);
+    style = RErr, [String (Device, "device")], [];
+    impl = OCaml "Cryptsetup.cryptsetup_close";
+    optional = Some "luks";
+    shortdesc = "close an encrypted device";
+    longdesc = "\
+This closes an encrypted device that was created earlier by
+C<guestfs_cryptsetup_open>.  The C<device> parameter must be
+the name of the mapping device (ie. F</dev/mapper/mapname>)
+and I<not> the name of the underlying block device." };
+
 ]
diff --git a/generator/actions_core_deprecated.ml
b/generator/actions_core_deprecated.ml
index 8556763b7..299f9854b 100644
--- a/generator/actions_core_deprecated.ml
+++ b/generator/actions_core_deprecated.ml
@@ -847,4 +847,56 @@ allows you to specify the new size (in bytes)
explicitly." };
 This rescans all block devices and rebuilds the list of LVM
 physical volumes, volume groups and logical volumes." };

+  { defaults with
+    name = "luks_open"; added = (1, 5, 1);
+    style = RErr, [String (Device, "device"); String (Key,
"key"); String (PlainString, "mapname")], [];
+    impl = OCaml "Cryptsetup.luks_open";
+    optional = Some "luks";
+    deprecated_by = Replaced_by "cryptsetup_open";
+    shortdesc = "open a LUKS-encrypted block device";
+    longdesc = "\
+This command opens a block device which has been encrypted
+according to the Linux Unified Key Setup (LUKS) standard.
+
+C<device> is the encrypted block device or partition.
+
+The caller must supply one of the keys associated with the
+LUKS block device, in the C<key> parameter.
+
+This creates a new block device called F</dev/mapper/mapname>.
+Reads and writes to this block device are decrypted from and
+encrypted to the underlying C<device> respectively.
+
+If this block device contains LVM volume groups, then
+calling C<guestfs_lvm_scan> with the C<activate>
+parameter C<true> will make them visible.
+
+Use C<guestfs_list_dm_devices> to list all device mapper
+devices." };
+
+  { defaults with
+    name = "luks_open_ro"; added = (1, 5, 1);
+    style = RErr, [String (Device, "device"); String (Key,
"key"); String (PlainString, "mapname")], [];
+    impl = OCaml "Cryptsetup.luks_open_ro";
+    optional = Some "luks";
+    deprecated_by = Replaced_by "cryptsetup_open";
+    shortdesc = "open a LUKS-encrypted block device read-only";
+    longdesc = "\
+This is the same as C<guestfs_luks_open> except that a read-only
+mapping is created." };
+
+  { defaults with
+    name = "luks_close"; added = (1, 5, 1);
+    style = RErr, [String (Device, "device")], [];
+    impl = OCaml "Cryptsetup.luks_close";
+    optional = Some "luks";
+    deprecated_by = Replaced_by "cryptsetup_close";
+    shortdesc = "close a LUKS device";
+    longdesc = "\
+This closes a LUKS device that was created earlier by
+C<guestfs_luks_open> or C<guestfs_luks_open_ro>.  The
+C<device> parameter must be the name of the LUKS mapping
+device (ie. F</dev/mapper/mapname>) and I<not> the name
+of the underlying block device." };
+
 ]
diff --git a/generator/daemon.ml b/generator/daemon.ml
index 9edef462c..b1047427b 100644
--- a/generator/daemon.ml
+++ b/generator/daemon.ml
@@ -776,7 +776,8 @@ let generate_daemon_caml_stubs ()                pr
"Val_bool (%s)" n;
            | OInt _ -> assert false
            | OInt64 _ -> assert false
-           | OString _ -> assert false
+           | OString _ ->
+              pr "caml_copy_string (%s)" n
            | OStringList _ -> assert false
           );
           pr ";\n";
@@ -792,7 +793,7 @@ let generate_daemon_caml_stubs ()             | Bool n ->
pr "Val_bool (%s)" n
            | Int n -> pr "Val_int (%s)" n
            | Int64 n -> pr "caml_copy_int64 (%s)" n
-           | String ((PlainString|Device|Pathname|Dev_or_Path), n) ->
+           | String ((PlainString|Device|Pathname|Dev_or_Path|Key), n) ->
               pr "caml_copy_string (%s)" n
            | String ((Mountable|Mountable_or_Path), n) ->
               pr "guestfs_int_daemon_copy_mountable (%s)" n
diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml
index d8e7e1282..30e42864f 100644
--- a/generator/proc_nr.ml
+++ b/generator/proc_nr.ml
@@ -514,6 +514,8 @@ let proc_nr = [
 505, "f2fs_expand";
 506, "lvm_scan";
 507, "luks_uuid";
+508, "cryptsetup_open";
+509, "cryptsetup_close";
 ]

 (* End of list.  If adding a new entry, add it at the end of the list
diff --git a/gobject/Makefile.inc b/gobject/Makefile.inc
index 84ad960ef..650f8ddac 100644
--- a/gobject/Makefile.inc
+++ b/gobject/Makefile.inc
@@ -69,6 +69,7 @@ guestfs_gobject_headers= \
   include/guestfs-gobject/optargs-copy_file_to_device.h \
   include/guestfs-gobject/optargs-copy_file_to_file.h \
   include/guestfs-gobject/optargs-cpio_out.h \
+  include/guestfs-gobject/optargs-cryptsetup_open.h \
   include/guestfs-gobject/optargs-disk_create.h \
   include/guestfs-gobject/optargs-download_blocks.h \
   include/guestfs-gobject/optargs-e2fsck.h \
@@ -162,6 +163,7 @@ guestfs_gobject_sources= \
   src/optargs-copy_file_to_device.c \
   src/optargs-copy_file_to_file.c \
   src/optargs-cpio_out.c \
+  src/optargs-cryptsetup_open.c \
   src/optargs-disk_create.c \
   src/optargs-download_blocks.c \
   src/optargs-e2fsck.c \
diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR
index 055b6671a..77afe238f 100644
--- a/lib/MAX_PROC_NR
+++ b/lib/MAX_PROC_NR
@@ -1 +1 @@
-507
+509
diff --git a/lib/guestfs.pod b/lib/guestfs.pod
index d746a41b1..bce9eb79f 100644
--- a/lib/guestfs.pod
+++ b/lib/guestfs.pod
@@ -585,17 +585,18 @@ Libguestfs allows you to access Linux guests which have
been
 encrypted using whole disk encryption that conforms to the
 Linux Unified Key Setup (LUKS) standard.  This includes
 nearly all whole disk encryption systems used by modern
-Linux guests.
+Linux guests.  Windows BitLocker is also supported.

-Use L</guestfs_vfs_type> to identify LUKS-encrypted block
-devices (it returns the string C<crypto_LUKS>).
+Use L</guestfs_vfs_type> to identify encrypted block
+devices.  For LUKS it returns the string C<crypto_LUKS>.
+For Windows BitLocker it returns C<BitLocker>.

-Then open these devices by calling L</guestfs_luks_open>.
+Then open these devices by calling L</guestfs_cryptsetup_open>.
 Obviously you will require the passphrase!

-Opening a LUKS device creates a new device mapper device
+Opening an encrypted device creates a new device mapper device
 called F</dev/mapper/mapname> (where C<mapname> is the
-string you supply to L</guestfs_luks_open>).
+string you supply to L</guestfs_cryptsetup_open>).
 Reads and writes to this mapper device are decrypted from and
 encrypted to the underlying block device respectively.

@@ -603,11 +604,11 @@ LVM volume groups on the device can be made visible by
calling
 L</guestfs_vgscan> followed by L</guestfs_vg_activate_all>.
 The logical volume(s) can now be mounted in the usual way.

-Use the reverse process to close a LUKS device.  Unmount
+Use the reverse process to close an encrypted device.  Unmount
 any logical volumes on it, deactivate the volume groups
 by calling C<guestfs_vg_activate (g, 0, ["/dev/VG"])>.
 Then close the mapper device by calling
-L</guestfs_luks_close> on the F</dev/mapper/mapname>
+L</guestfs_cryptsetup_close> on the F</dev/mapper/mapname>
 device (I<not> the underlying encrypted block device).

 =head2 MOUNT LOCAL
--
2.27.0