qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices
@ 2016-01-20 17:38 Daniel P. Berrange
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined Daniel P. Berrange
                   ` (16 more replies)
  0 siblings, 17 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

This is a posting of the previously submitted work in progress
code:

  https://lists.gnu.org/archive/html/qemu-devel/2015-11/msg04748.html

This series depends on these previously submitted
patches to the block tools:

  https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg04354.html

As can be guessed from the subject, the primary goal of this
patch series is to support LUKS encryption in the QEMU block
layer. QEMU has increasingly been adding native clients for
network block protocols (RBD, gluster, NFS, iSCSI, etc) and
apps like OpenStack are embracing them as it is much easier
to deal with this from a management POV than to deal with the
kernel block layer & userspace tools. Unfortunately when using
QEMU native clients, apps are locked out of using dm-crypt
and LUKS which is undesirable.

This series introduces two new features to the block layer.
First there is a general purpose 'luks' format driver which
can be layered over any other existing block driver. eg it
can be layed above RBD, iSCSI, etc. Second the qcow2 file
format is extended so that its embedded encryption can be
replaced with the LUKS data format. While you could just
layer the general purpose luks driver over qcow2, this is
slightly less desirable, as it removes the ability to
reliably auto-detect that LUKS is used by QEMU, as opposed
to used by the guest OS. Having use of LUKS encoded in the
qcow2 header addresses this.

The code is designed such that there is a strict separation
between the full disk encryption format and the block I/O
layer. Thus there is an generic API for dealing with full
disk encryption added to the crypto/ subsystem. The block
layer merely calls this FDE API when required, which serves
to minimize the code present in the already complex block
layer.

The first 5 patches add some supporting APIs to the crypto
subsystem.

The 6-7 patches introduce the general full disk encryption
API to the crypto subsystem and LUKS implementation

Patches 8-14 add the new 'luks' block driver format and
convert qcow & qcow2 to the new FDE APIs, enabling LUKS
in qcow2 (but not qcow) at the same time.

Patches 15-16 clean up the horrible password handling
cruft in the block layer and monitor.

Patch 17 blocks use of the legacy qcow[2] encryption
from the system emulators.

Still to do

 - Add support for XTS cipher mode for dm-crypt compat

Changes since v1:

 - Unit testing coverage of the full disk encryption
   APIs
 - Functional testing of LUKS driver via the
   qemu-iotests for the block layer
 - Use GNUTLS random API for the random byte source
 - Use Makefile.objs for conditional compilation of
   pbkdf files (Fam)
 - Use 'key-secret' instead of 'key-id' as property
   for decryption key (Paolo)
 - Rename 'fde' to 'crypto' in block code (Kevin)
 - Fix accounting of encryption header clusters when
   checking refcounts (Kevin)
 - Rename format 'qcowaes' to 'qcow' (Eric)
 - Fix qapi syntax for marking optional parameters (Eric)
 - Add assertions in I/O path for BDRV_O_NO_IO flag
 - Cleanup return codes / error reporting (Kevin)
 - Other misc fixes identified in testing

Changes since WIP:

 - QAPI parameters defined for the encryption key ID
 - The qcow2 integration of LUKS is working, using
   extra allocated clusters to store LUKS header
   instead of trying to expand the main qcow2 header
   region to > 1 cluster
 - qemu-img info now works without prompting for
   decryption password
 - Unit testing of pbkdf2, afsplit and ivgen APis.

Daniel P. Berrange (17):
  crypto: ensure qcrypto_hash_digest_len is always defined
  crypto: add cryptographic random byte source
  crypto: add support for PBKDF2 algorithm
  crypto: add support for generating initialization vectors
  crypto: add support for anti-forensic split algorithm
  crypto: add block encryption framework
  crypto: implement the LUKS block encryption format
  block: add flag to indicate that no I/O will be performed
  qemu-img/qemu-io: don't prompt for passwords if not required
  block: add generic full disk encryption driver
  qcow2: make qcow2_encrypt_sectors encrypt in place
  qcow2: convert QCow2 to use QCryptoBlock for encryption
  qcow: make encrypt_sectors encrypt in place
  qcow: convert QCow to use QCryptoBlock for encryption
  block: rip out all traces of password prompting
  block: remove all encryption handling APIs
  block: remove support for legecy AES qcow/qcow2 encryption

 Makefile.objs                |    2 +-
 block.c                      |   99 +---
 block/Makefile.objs          |    2 +
 block/crypto.c               |  540 +++++++++++++++++++++
 block/io.c                   |    2 +
 block/qapi.c                 |    2 +-
 block/qcow.c                 |  195 ++++----
 block/qcow2-cluster.c        |   53 +-
 block/qcow2-refcount.c       |   10 +
 block/qcow2.c                |  510 ++++++++++++++++---
 block/qcow2.h                |   24 +-
 blockdev.c                   |   40 +-
 crypto/Makefile.objs         |   16 +
 crypto/afsplit.c             |  162 +++++++
 crypto/block-luks.c          | 1104 ++++++++++++++++++++++++++++++++++++++++++
 crypto/block-luks.h          |   28 ++
 crypto/block-qcow.c          |  167 +++++++
 crypto/block-qcow.h          |   28 ++
 crypto/block.c               |  265 ++++++++++
 crypto/blockpriv.h           |   90 ++++
 crypto/hash.c                |   30 +-
 crypto/ivgen-essiv.c         |  112 +++++
 crypto/ivgen-essiv.h         |   28 ++
 crypto/ivgen-plain.c         |   58 +++
 crypto/ivgen-plain.h         |   28 ++
 crypto/ivgen-plain64.c       |   58 +++
 crypto/ivgen-plain64.h       |   28 ++
 crypto/ivgen.c               |   98 ++++
 crypto/ivgenpriv.h           |   49 ++
 crypto/pbkdf-gcrypt.c        |   65 +++
 crypto/pbkdf-nettle.c        |   64 +++
 crypto/pbkdf-stub.c          |   41 ++
 crypto/pbkdf.c               |   68 +++
 crypto/random-gcrypt.c       |   33 ++
 crypto/random-gnutls.c       |   43 ++
 crypto/random-stub.c         |   31 ++
 docs/specs/qcow2.txt         |   74 +++
 hmp.c                        |   31 --
 hw/usb/dev-storage.c         |   34 --
 include/block/block.h        |    6 +-
 include/block/block_int.h    |    1 -
 include/crypto/afsplit.h     |  135 ++++++
 include/crypto/block.h       |  233 +++++++++
 include/crypto/ivgen.h       |  203 ++++++++
 include/crypto/pbkdf.h       |  152 ++++++
 include/crypto/random.h      |   43 ++
 include/monitor/monitor.h    |    7 -
 include/qemu/osdep.h         |    2 -
 monitor.c                    |   68 ---
 qapi/block-core.json         |   41 +-
 qapi/crypto.json             |  120 +++++
 qemu-img.c                   |   45 +-
 qemu-io.c                    |   21 -
 qmp.c                        |    9 -
 tests/.gitignore             |    4 +
 tests/Makefile               |    8 +
 tests/qemu-iotests/049       |    2 +-
 tests/qemu-iotests/049.out   |   10 +-
 tests/qemu-iotests/082.out   |  189 ++++++++
 tests/qemu-iotests/087       |   30 +-
 tests/qemu-iotests/087.out   |   28 +-
 tests/qemu-iotests/134       |   18 +-
 tests/qemu-iotests/134.out   |   33 +-
 tests/qemu-iotests/common    |    1 +
 tests/qemu-iotests/common.rc |    4 +-
 tests/test-crypto-afsplit.c  |  176 +++++++
 tests/test-crypto-block.c    |  343 +++++++++++++
 tests/test-crypto-ivgen.c    |  168 +++++++
 tests/test-crypto-pbkdf.c    |  378 +++++++++++++++
 util/oslib-posix.c           |   66 ---
 util/oslib-win32.c           |   24 -
 71 files changed, 6128 insertions(+), 752 deletions(-)
 create mode 100644 block/crypto.c
 create mode 100644 crypto/afsplit.c
 create mode 100644 crypto/block-luks.c
 create mode 100644 crypto/block-luks.h
 create mode 100644 crypto/block-qcow.c
 create mode 100644 crypto/block-qcow.h
 create mode 100644 crypto/block.c
 create mode 100644 crypto/blockpriv.h
 create mode 100644 crypto/ivgen-essiv.c
 create mode 100644 crypto/ivgen-essiv.h
 create mode 100644 crypto/ivgen-plain.c
 create mode 100644 crypto/ivgen-plain.h
 create mode 100644 crypto/ivgen-plain64.c
 create mode 100644 crypto/ivgen-plain64.h
 create mode 100644 crypto/ivgen.c
 create mode 100644 crypto/ivgenpriv.h
 create mode 100644 crypto/pbkdf-gcrypt.c
 create mode 100644 crypto/pbkdf-nettle.c
 create mode 100644 crypto/pbkdf-stub.c
 create mode 100644 crypto/pbkdf.c
 create mode 100644 crypto/random-gcrypt.c
 create mode 100644 crypto/random-gnutls.c
 create mode 100644 crypto/random-stub.c
 create mode 100644 include/crypto/afsplit.h
 create mode 100644 include/crypto/block.h
 create mode 100644 include/crypto/ivgen.h
 create mode 100644 include/crypto/pbkdf.h
 create mode 100644 include/crypto/random.h
 create mode 100644 tests/test-crypto-afsplit.c
 create mode 100644 tests/test-crypto-block.c
 create mode 100644 tests/test-crypto-ivgen.c
 create mode 100644 tests/test-crypto-pbkdf.c

-- 
2.5.0

^ permalink raw reply	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  6:12   ` Fam Zheng
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source Daniel P. Berrange
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

The qcrypto_hash_digest_len method was accidentally inside
a CONFIG_GNUTLS_HASH block, even though it doesn't depend
on gnutls. Re-arrange it to be unconditionally defined.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/hash.c | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/crypto/hash.c b/crypto/hash.c
index 6e83f43..24cabd5 100644
--- a/crypto/hash.c
+++ b/crypto/hash.c
@@ -23,12 +23,8 @@
 #ifdef CONFIG_GNUTLS_HASH
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
+#endif
 
-static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG__MAX] = {
-    [QCRYPTO_HASH_ALG_MD5] = GNUTLS_DIG_MD5,
-    [QCRYPTO_HASH_ALG_SHA1] = GNUTLS_DIG_SHA1,
-    [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256,
-};
 
 static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG__MAX] = {
     [QCRYPTO_HASH_ALG_MD5] = 16,
@@ -36,14 +32,6 @@ static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG__MAX] = {
     [QCRYPTO_HASH_ALG_SHA256] = 32,
 };
 
-gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg)
-{
-    if (alg < G_N_ELEMENTS(qcrypto_hash_alg_map)) {
-        return true;
-    }
-    return false;
-}
-
 size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg)
 {
     if (alg >= G_N_ELEMENTS(qcrypto_hash_alg_size)) {
@@ -53,6 +41,22 @@ size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg)
 }
 
 
+#ifdef CONFIG_GNUTLS_HASH
+static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG__MAX] = {
+    [QCRYPTO_HASH_ALG_MD5] = GNUTLS_DIG_MD5,
+    [QCRYPTO_HASH_ALG_SHA1] = GNUTLS_DIG_SHA1,
+    [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256,
+};
+
+gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg)
+{
+    if (alg < G_N_ELEMENTS(qcrypto_hash_alg_map)) {
+        return true;
+    }
+    return false;
+}
+
+
 int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg,
                         const struct iovec *iov,
                         size_t niov,
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  6:12   ` Fam Zheng
  2016-02-04 17:44   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
                   ` (14 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

There are three backend impls provided. The preferred
is gnutls, which is backed by nettle in modern distros.
The gcrypt impl is provided for cases where QEMU build
against gnutls is disabled, but crypto is still desired.
No nettle impl is provided, since it is non-trivial to
use the nettle APIs for random numbers. Users of nettle
should ensure gnutls is enabled for QEMU.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile.objs           |  2 +-
 crypto/Makefile.objs    |  4 ++++
 crypto/random-gcrypt.c  | 33 +++++++++++++++++++++++++++++++++
 crypto/random-gnutls.c  | 43 +++++++++++++++++++++++++++++++++++++++++++
 crypto/random-stub.c    | 31 +++++++++++++++++++++++++++++++
 include/crypto/random.h | 43 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 crypto/random-gcrypt.c
 create mode 100644 crypto/random-gnutls.c
 create mode 100644 crypto/random-stub.c
 create mode 100644 include/crypto/random.h

diff --git a/Makefile.objs b/Makefile.objs
index 06b95c7..8b52f33 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -1,6 +1,6 @@
 #######################################################################
 # Common libraries for tools and emulators
-stub-obj-y = stubs/
+stub-obj-y = stubs/ crypto/
 util-obj-y = util/ qobject/ qapi/
 util-obj-y += qmp-introspect.o qapi-types.o qapi-visit.o qapi-event.o
 
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index a3135f1..1802ff5 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -8,6 +8,10 @@ crypto-obj-y += tlscredsanon.o
 crypto-obj-y += tlscredsx509.o
 crypto-obj-y += tlssession.o
 crypto-obj-y += secret.o
+crypto-obj-$(if $(CONFIG_GNUTLS),n,$(CONFIG_GCRYPT)) += random-gcrypt.o
+crypto-obj-$(CONFIG_GNUTLS) += random-gnutls.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
+
+stub-obj-y += random-stub.o
\ No newline at end of file
diff --git a/crypto/random-gcrypt.c b/crypto/random-gcrypt.c
new file mode 100644
index 0000000..a2d77db
--- /dev/null
+++ b/crypto/random-gcrypt.c
@@ -0,0 +1,33 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config-host.h>
+
+#include "crypto/random.h"
+
+#include <gcrypt.h>
+
+int qcrypto_random_bytes(uint8_t *buf,
+                         size_t buflen,
+                         Error **errp G_GNUC_UNUSED)
+{
+    gcry_randomize(buf, buflen, GCRY_STRONG_RANDOM);
+    return 0;
+}
diff --git a/crypto/random-gnutls.c b/crypto/random-gnutls.c
new file mode 100644
index 0000000..f45c295
--- /dev/null
+++ b/crypto/random-gnutls.c
@@ -0,0 +1,43 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config-host.h>
+
+#include "crypto/random.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+int qcrypto_random_bytes(uint8_t *buf,
+                         size_t buflen,
+                         Error **errp)
+{
+    int ret;
+
+    ret = gnutls_rnd(GNUTLS_RND_RANDOM, buf, buflen);
+
+    if (ret < 0) {
+        error_setg(errp, "Cannot get random bytes: %s",
+                   gnutls_strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/crypto/random-stub.c b/crypto/random-stub.c
new file mode 100644
index 0000000..5c2a4e1
--- /dev/null
+++ b/crypto/random-stub.c
@@ -0,0 +1,31 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config-host.h>
+
+#include "crypto/random.h"
+
+int qcrypto_random_bytes(uint8_t *buf G_GNUC_UNUSED,
+                         size_t buflen G_GNUC_UNUSED,
+                         Error **errp)
+{
+    error_setg(errp, "No random byte source provided in this build");
+    return -1;
+}
diff --git a/include/crypto/random.h b/include/crypto/random.h
new file mode 100644
index 0000000..743f5a0
--- /dev/null
+++ b/include/crypto/random.h
@@ -0,0 +1,43 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_RANDOM_H__
+#define QCRYPTO_RANDOM_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+
+
+/**
+ * qcrypto_random_bytes:
+ * @buf: the buffer to fill
+ * @buflen: length of @buf in bytes
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Fill @buf with @buflen bytes of random data
+ *
+ * Returns 0 on sucess, -1 on error
+ */
+int qcrypto_random_bytes(uint8_t *buf,
+                         size_t buflen,
+                         Error **errp);
+
+
+#endif /* QCRYPTO_RANDOM_H__ */
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined Daniel P. Berrange
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  6:59   ` Fam Zheng
  2016-02-04 22:14   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors Daniel P. Berrange
                   ` (13 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

The LUKS data format includes use of PBKDF2 (Password-Based
Key Derivation Function). The Nettle library can provide
an implementation of this, but we don't want code directly
depending on a specific crypto library backend. Introduce
a include/crypto/pbkdf.h header which defines a QEMU
API for invoking PBKDK2. The initial implementations are
backed by nettle & gcrypt, which are commonly available
with distros shipping GNUTLS.

The test suite data is taken from the cryptsetup codebase
under the LGPLv2.1+ license. This merely aims to verify
that whatever backend we provide for this function in QEMU
will comply with the spec.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs      |   6 +-
 crypto/pbkdf-gcrypt.c     |  65 ++++++++
 crypto/pbkdf-nettle.c     |  64 ++++++++
 crypto/pbkdf-stub.c       |  41 +++++
 crypto/pbkdf.c            |  68 +++++++++
 include/crypto/pbkdf.h    | 152 +++++++++++++++++++
 tests/.gitignore          |   1 +
 tests/Makefile            |   2 +
 tests/test-crypto-pbkdf.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 776 insertions(+), 1 deletion(-)
 create mode 100644 crypto/pbkdf-gcrypt.c
 create mode 100644 crypto/pbkdf-nettle.c
 create mode 100644 crypto/pbkdf-stub.c
 create mode 100644 crypto/pbkdf.c
 create mode 100644 include/crypto/pbkdf.h
 create mode 100644 tests/test-crypto-pbkdf.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 1802ff5..4d2cd3e 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -10,8 +10,12 @@ crypto-obj-y += tlssession.o
 crypto-obj-y += secret.o
 crypto-obj-$(if $(CONFIG_GNUTLS),n,$(CONFIG_GCRYPT)) += random-gcrypt.o
 crypto-obj-$(CONFIG_GNUTLS) += random-gnutls.o
+crypto-obj-y += pbkdf.o
+crypto-obj-$(CONFIG_NETTLE) += pbkdf-nettle.o
+crypto-obj-$(if $(CONFIG_NETTLE),n,$(CONFIG_GCRYPT)) += pbkdf-gcrypt.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
 
-stub-obj-y += random-stub.o
\ No newline at end of file
+stub-obj-y += random-stub.o
+stub-obj-y += pbkdf-stub.o
diff --git a/crypto/pbkdf-gcrypt.c b/crypto/pbkdf-gcrypt.c
new file mode 100644
index 0000000..94abcf8
--- /dev/null
+++ b/crypto/pbkdf-gcrypt.c
@@ -0,0 +1,65 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/pbkdf.h"
+#include "gcrypt.h"
+
+bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)
+{
+    switch (hash) {
+    case QCRYPTO_HASH_ALG_SHA1:
+    case QCRYPTO_HASH_ALG_SHA256:
+        return true;
+    default:
+        return false;
+    }
+}
+
+int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
+                   const uint8_t *key, size_t nkey,
+                   const uint8_t *salt, size_t nsalt,
+                   unsigned int iterations,
+                   uint8_t *out, size_t nout,
+                   Error **errp)
+{
+    static const int hash_map[QCRYPTO_HASH_ALG__MAX] = {
+        [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5,
+        [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1,
+        [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256,
+    };
+    int ret;
+
+    if (hash > G_N_ELEMENTS(hash_map)) {
+        error_setg(errp, "Unexpected hash algorithm %d", hash);
+        return -1;
+    }
+
+    ret = gcry_kdf_derive(key, nkey, GCRY_KDF_PBKDF2,
+                          hash_map[hash],
+                          salt, nsalt, iterations,
+                          nout, out);
+    if (ret != 0) {
+        error_setg(errp, "Cannot derive password: %s",
+                   gcry_strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/crypto/pbkdf-nettle.c b/crypto/pbkdf-nettle.c
new file mode 100644
index 0000000..b0f2d8e
--- /dev/null
+++ b/crypto/pbkdf-nettle.c
@@ -0,0 +1,64 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/pbkdf.h"
+#include "nettle/pbkdf2.h"
+
+
+bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)
+{
+    switch (hash) {
+    case QCRYPTO_HASH_ALG_SHA1:
+    case QCRYPTO_HASH_ALG_SHA256:
+        return true;
+    default:
+        return false;
+    }
+}
+
+int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
+                   const uint8_t *key, size_t nkey,
+                   const uint8_t *salt, size_t nsalt,
+                   unsigned int iterations,
+                   uint8_t *out, size_t nout,
+                   Error **errp)
+{
+    switch (hash) {
+    case QCRYPTO_HASH_ALG_SHA1:
+        pbkdf2_hmac_sha1(nkey, key,
+                         iterations,
+                         nsalt, salt,
+                         nout, out);
+        break;
+
+    case QCRYPTO_HASH_ALG_SHA256:
+        pbkdf2_hmac_sha256(nkey, key,
+                           iterations,
+                           nsalt, salt,
+                           nout, out);
+        break;
+
+    default:
+        error_setg_errno(errp, ENOSYS,
+                         "PBKDF does not support hash algorithm %d", hash);
+        return -1;
+    }
+    return 0;
+}
diff --git a/crypto/pbkdf-stub.c b/crypto/pbkdf-stub.c
new file mode 100644
index 0000000..73a08c3
--- /dev/null
+++ b/crypto/pbkdf-stub.c
@@ -0,0 +1,41 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/pbkdf.h"
+
+bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)
+{
+    return false;
+}
+
+int qcrypto_pbkdf2(QCryptoHashAlgorithm hash G_GNUC_UNUSED,
+                   const uint8_t *key G_GNUC_UNUSED,
+                   size_t nkey G_GNUC_UNUSED,
+                   const uint8_t *salt G_GNUC_UNUSED,
+                   size_t nsalt G_GNUC_UNUSED,
+                   unsigned int iterations G_GNUC_UNUSED,
+                   uint8_t *out G_GNUC_UNUSED,
+                   size_t nout G_GNUC_UNUSED,
+                   Error **errp)
+{
+    error_setg_errno(errp, ENOSYS,
+                     "No crypto library supporting PBKDF in this build");
+    return -1;
+}
diff --git a/crypto/pbkdf.c b/crypto/pbkdf.c
new file mode 100644
index 0000000..71f96cd
--- /dev/null
+++ b/crypto/pbkdf.c
@@ -0,0 +1,68 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/pbkdf.h"
+#include <sys/resource.h>
+
+
+int qcrypto_pbkdf2_count_iters(QCryptoHashAlgorithm hash,
+                               const uint8_t *key, size_t nkey,
+                               const uint8_t *salt, size_t nsalt,
+                               Error **errp)
+{
+    uint8_t out[32];
+    int iterations = (1 << 15);
+    struct rusage start, end;
+    unsigned long long delta;
+
+    while (1) {
+        if (getrusage(RUSAGE_THREAD, &start) < 0) {
+            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
+            return -1;
+        }
+        if (qcrypto_pbkdf2(hash,
+                           key, nkey,
+                           salt, nsalt,
+                           iterations,
+                           out, sizeof(out),
+                           errp) < 0) {
+            return -1;
+        }
+        if (getrusage(RUSAGE_THREAD, &end) < 0) {
+            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
+            return -1;
+        }
+
+        delta = (((end.ru_utime.tv_sec * 1000ll) +
+                  (end.ru_utime.tv_usec / 1000)) -
+                 ((start.ru_utime.tv_sec * 1000ll) +
+                  (start.ru_utime.tv_usec / 1000)));
+
+        if (delta > 500) {
+            break;
+        } else if (delta < 100) {
+            iterations = iterations * 10;
+        } else {
+            iterations = (iterations * 1000 / delta);
+        }
+    }
+
+    return iterations * 1000 / delta;
+}
diff --git a/include/crypto/pbkdf.h b/include/crypto/pbkdf.h
new file mode 100644
index 0000000..a5e8267
--- /dev/null
+++ b/include/crypto/pbkdf.h
@@ -0,0 +1,152 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_PBKDF_H__
+#define QCRYPTO_PBKDF_H__
+
+#include "crypto/hash.h"
+
+/**
+ * This module provides an interface to the PBKDF2 algorithm
+ *
+ *   https://en.wikipedia.org/wiki/PBKDF2
+ *
+ * <example>
+ *   <title>Generating a AES encryption key from a user password</title>
+ *   <programlisting>
+ * #include "crypto/cipher.h"
+ * #include "crypto/random.h"
+ * #include "crypto/pbkdf.h"
+ *
+ * ....
+ *
+ * char *password = "a-typical-awful-user-password";
+ * size_t nkey = qcrypto_cipher_get_key_len(QCRYPTO_CIPHER_ALG_AES_128);
+ * uint8_t *salt = g_new0(uint8_t, nkey);
+ * uint8_t *key = g_new0(uint8_t, nkey);
+ * int iterations;
+ * QCryptoCipher *cipher;
+ *
+ * if (qcrypto_random_bytes(salt, nkey, errp) < 0) {
+ *     g_free(key);
+ *     g_free(salt);
+ *     return -1;
+ * }
+ *
+ * iterations = qcrypto_pbkdf2_count_iters(QCRYPTO_HASH_ALG_SHA256,
+ *                                         (const uint8_t *)password,
+ *                                         strlen(password),
+ *                                         salt, nkey, errp);
+ * if (iterations < 0) {
+ *     g_free(key);
+ *     g_free(salt);
+ *     return -1;
+ * }
+ *
+ * if (qcrypto_pbkdf2(QCRYPTO_HASH_ALG_SHA256,
+ *                    (const uint8_t *)password, strlen(password),
+ *                    salt, nkey, iterations, key, nkey, errp) < 0) {
+ *     g_free(key);
+ *     g_free(salt);
+ *     return -1;
+ * }
+ *
+ * g_free(salt);
+ *
+ * cipher = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_128,
+ *                             QCRYPTO_CIPHER_MODE_ECB,
+ *                             key, nkey, errp);
+ * g_free(key);
+ *
+ * ....encrypt some data...
+ *
+ * qcrypto_cipher_free(cipher);
+ *   </programlisting>
+ * </example>
+ *
+ */
+
+/**
+ * qcrypto_pbkdf2_supports:
+ * @hash: the hash algorithm
+ *
+ * Determine if the current build supports the PBKDF2 algorithm
+ * in combination with the hash @hash.
+ *
+ * Returns true if supported, false otherwise
+ */
+bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash);
+
+
+/**
+ * qcrypto_pbkdf2:
+ * @hash: the hash algorithm to use
+ * @key: the user password / key
+ * @nkey: the length of @key in bytes
+ * @salt: a random salt
+ * @nsalt: length of @salt in bytes
+ * @iterations: the number of iterations to compute
+ * @out: pointer to pre-allocated buffer to hold output
+ * @nout: length of @out in bytes
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Apply the PBKDF2 algorithm to derive an encryption
+ * key from a user password provided in @key. The
+ * @salt parameter is used to perturb the algorithm.
+ * The @iterations count determines how many times
+ * the hashing process is run, which influences how
+ * hard it is to crack the key. The number of @iterations
+ * should be large enough such that the algorithm takes
+ * 1 second or longer to derive a key. The derived key
+ * will be stored in the preallocated buffer @out.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
+                   const uint8_t *key, size_t nkey,
+                   const uint8_t *salt, size_t nsalt,
+                   unsigned int iterations,
+                   uint8_t *out, size_t nout,
+                   Error **errp);
+
+/**
+ * qcrypto_pbkdf2_count_iters:
+ * @hash: the hash algorithm to use
+ * @key: the user password / key
+ * @nkey: the length of @key in bytes
+ * @salt: a random salt
+ * @nsalt: length of @salt in bytes
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Time the PBKDF2 algorithm to determine how many
+ * iterations are required to derive an encryption
+ * key from a user password provided in @key in 1
+ * second of compute time. The result of this can
+ * be used as a the @iterations parameter of a later
+ * call to qcrypto_pbkdf2().
+ *
+ * Returns: number of iterations in 1 second, -1 on error
+ */
+int qcrypto_pbkdf2_count_iters(QCryptoHashAlgorithm hash,
+                               const uint8_t *key, size_t nkey,
+                               const uint8_t *salt, size_t nsalt,
+                               Error **errp);
+
+#endif /* QCRYPTO_PBKDF_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 787c95c..db6b9be 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -14,6 +14,7 @@ test-blockjob-txn
 test-coroutine
 test-crypto-cipher
 test-crypto-hash
+test-crypto-pbkdf
 test-crypto-secret
 test-crypto-tlscredsx509
 test-crypto-tlscredsx509-work/
diff --git a/tests/Makefile b/tests/Makefile
index b7352f1..833a290 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -92,6 +92,7 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
 check-unit-y += tests/test-io-channel-command$(EXESUF)
 check-unit-y += tests/test-io-channel-buffer$(EXESUF)
 check-unit-y += tests/test-base64$(EXESUF)
+check-unit-y += tests/test-crypto-pbkdf$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -495,6 +496,7 @@ tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
 tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y)
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-pbkdf.c b/tests/test-crypto-pbkdf.c
new file mode 100644
index 0000000..3e0e938
--- /dev/null
+++ b/tests/test-crypto-pbkdf.c
@@ -0,0 +1,378 @@
+/*
+ * QEMU Crypto cipher algorithms
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "crypto/init.h"
+
+#if defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT)
+#include "crypto/pbkdf.h"
+
+typedef struct QCryptoPbkdfTestData QCryptoPbkdfTestData;
+struct QCryptoPbkdfTestData {
+    const char *path;
+    QCryptoHashAlgorithm hash;
+    unsigned int iterations;
+    const char *key;
+    size_t nkey;
+    const char *salt;
+    size_t nsalt;
+    const char *out;
+    size_t nout;
+};
+
+/* This test data comes from cryptsetup package
+ *
+ *  $SRC/lib/crypto_backend/pbkdf2_generic.c
+ *
+ * under LGPLv2.1+ license
+ */
+static QCryptoPbkdfTestData test_data[] = {
+    /* RFC 3962 test data */
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter1",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 1,
+        .key = "password",
+        .nkey = 8,
+        .salt = "ATHENA.MIT.EDUraeburn",
+        .nsalt = 21,
+        .out = "\xcd\xed\xb5\x28\x1b\xb2\xf8\x01"
+               "\x56\x5a\x11\x22\xb2\x56\x35\x15"
+               "\x0a\xd1\xf7\xa0\x4b\xb9\xf3\xa3"
+               "\x33\xec\xc0\xe2\xe1\xf7\x08\x37",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter2",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 2,
+        .key = "password",
+        .nkey = 8,
+        .salt = "ATHENA.MIT.EDUraeburn",
+        .nsalt = 21,
+        .out = "\x01\xdb\xee\x7f\x4a\x9e\x24\x3e"
+               "\x98\x8b\x62\xc7\x3c\xda\x93\x5d"
+               "\xa0\x53\x78\xb9\x32\x44\xec\x8f"
+               "\x48\xa9\x9e\x61\xad\x79\x9d\x86",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter1200a",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 1200,
+        .key = "password",
+        .nkey = 8,
+        .salt = "ATHENA.MIT.EDUraeburn",
+        .nsalt = 21,
+        .out = "\x5c\x08\xeb\x61\xfd\xf7\x1e\x4e"
+               "\x4e\xc3\xcf\x6b\xa1\xf5\x51\x2b"
+               "\xa7\xe5\x2d\xdb\xc5\xe5\x14\x2f"
+               "\x70\x8a\x31\xe2\xe6\x2b\x1e\x13",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter5",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 5,
+        .key = "password",
+        .nkey = 8,
+        .salt = "\0224VxxV4\022", /* "\x1234567878563412 */
+        .nsalt = 8,
+        .out = "\xd1\xda\xa7\x86\x15\xf2\x87\xe6"
+               "\xa1\xc8\xb1\x20\xd7\x06\x2a\x49"
+               "\x3f\x98\xd2\x03\xe6\xbe\x49\xa6"
+               "\xad\xf4\xfa\x57\x4b\x6e\x64\xee",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter1200b",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 1200,
+        .key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        .nkey = 64,
+        .salt = "pass phrase equals block size",
+        .nsalt = 29,
+        .out = "\x13\x9c\x30\xc0\x96\x6b\xc3\x2b"
+               "\xa5\x5f\xdb\xf2\x12\x53\x0a\xc9"
+               "\xc5\xec\x59\xf1\xa4\x52\xf5\xcc"
+               "\x9a\xd9\x40\xfe\xa0\x59\x8e\xd1",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter1200c",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 1200,
+        .key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        .nkey = 65,
+        .salt = "pass phrase exceeds block size",
+        .nsalt = 30,
+        .out = "\x9c\xca\xd6\xd4\x68\x77\x0c\xd5"
+               "\x1b\x10\xe6\xa6\x87\x21\xbe\x61"
+               "\x1a\x8b\x4d\x28\x26\x01\xdb\x3b"
+               "\x36\xbe\x92\x46\x91\x5e\xc8\x2a",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/rfc3962/sha1/iter50",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 50,
+        .key = "\360\235\204\236", /* g-clef ("\xf09d849e) */
+        .nkey = 4,
+        .salt = "EXAMPLE.COMpianist",
+        .nsalt = 18,
+        .out = "\x6b\x9c\xf2\x6d\x45\x45\x5a\x43"
+               "\xa5\xb8\xbb\x27\x6a\x40\x3b\x39"
+               "\xe7\xfe\x37\xa0\xc4\x1e\x02\xc2"
+               "\x81\xff\x30\x69\xe1\xe9\x4f\x52",
+        .nout = 32
+    },
+
+    /* RFC-6070 test data */
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter1",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 1,
+        .key = "password",
+        .nkey = 8,
+        .salt = "salt",
+        .nsalt = 4,
+        .out = "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9"
+               "\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6",
+        .nout = 20
+    },
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter2",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 2,
+        .key = "password",
+        .nkey = 8,
+        .salt = "salt",
+        .nsalt = 4,
+        .out = "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e"
+               "\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57",
+        .nout = 20
+    },
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter4096",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 4096,
+        .key = "password",
+        .nkey = 8,
+        .salt = "salt",
+        .nsalt = 4,
+        .out = "\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad"
+               "\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1",
+        .nout = 20
+    },
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter16777216",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 16777216,
+        .key = "password",
+        .nkey = 8,
+        .salt = "salt",
+        .nsalt = 4,
+        .out = "\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94"
+               "\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84",
+        .nout = 20
+    },
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter4096a",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 4096,
+        .key = "passwordPASSWORDpassword",
+        .nkey = 24,
+        .salt = "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+        .nsalt = 36,
+        .out = "\x3d\x2e\xec\x4f\xe4\x1c\x84\x9b\x80\xc8"
+               "\xd8\x36\x62\xc0\xe4\x4a\x8b\x29\x1a\x96"
+               "\x4c\xf2\xf0\x70\x38",
+        .nout = 25
+    },
+    {
+        .path = "/crypto/pbkdf/rfc6070/sha1/iter4096b",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 4096,
+        .key = "pass\0word",
+        .nkey = 9,
+        .salt = "sa\0lt",
+        .nsalt = 5,
+        .out = "\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37"
+               "\xd7\xf0\x34\x25\xe0\xc3",
+        .nout = 16
+    },
+
+    /* non-RFC misc test data */
+    {
+        /* empty password test */
+        .path = "/crypto/pbkdf/nonrfc/sha1/iter2",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .iterations = 2,
+        .key = "",
+        .nkey = 0,
+        .salt = "salt",
+        .nsalt = 4,
+        .out = "\x13\x3a\x4c\xe8\x37\xb4\xd2\x52\x1e\xe2"
+               "\xbf\x03\xe1\x1c\x71\xca\x79\x4e\x07\x97",
+        .nout = 20
+    }, {
+        /* Password exceeds block size test */
+        .path = "/crypto/pbkdf/nonrfc/sha256/iter1200",
+        .hash = QCRYPTO_HASH_ALG_SHA256,
+        .iterations = 1200,
+        .key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        .nkey = 65,
+        .salt = "pass phrase exceeds block size",
+        .nsalt = 30,
+        .out = "\x22\x34\x4b\xc4\xb6\xe3\x26\x75"
+               "\xa8\x09\x0f\x3e\xa8\x0b\xe0\x1d"
+               "\x5f\x95\x12\x6a\x2c\xdd\xc3\xfa"
+               "\xcc\x4a\x5e\x6d\xca\x04\xec\x58",
+        .nout = 32
+    },
+#if 0
+    {
+        .path = "/crypto/pbkdf/nonrfc/sha512/iter1200",
+        .hash = QCRYPTO_HASH_ALG_SHA512,
+        .iterations = 1200,
+        .key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        .nkey = 129,
+        .salt = "pass phrase exceeds block size",
+        .nsalt = 30,
+        .out = "\x0f\xb2\xed\x2c\x0e\x6e\xfb\x7d"
+               "\x7d\x8e\xdd\x58\x01\xb4\x59\x72"
+               "\x99\x92\x16\x30\x5e\xa4\x36\x8d"
+               "\x76\x14\x80\xf3\xe3\x7a\x22\xb9",
+        .nout = 32
+    },
+    {
+        .path = "/crypto/pbkdf/nonrfc/whirlpool/iter1200",
+        .hash = QCRYPTO_HASH_ALG_WHIRLPOOL,
+        .iterations = 1200,
+        .key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+               "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+        .nkey = 65,
+        .salt = "pass phrase exceeds block size",
+        .nsalt = 30,
+        .out = "\x9c\x1c\x74\xf5\x88\x26\xe7\x6a"
+               "\x53\x58\xf4\x0c\x39\xe7\x80\x89"
+               "\x07\xc0\x31\x19\x9a\x50\xa2\x48"
+               "\xf1\xd9\xfe\x78\x64\xe5\x84\x50",
+        .nout = 32
+    }
+#endif
+};
+
+
+static inline char hex(int i)
+{
+    if (i < 10) {
+        return '0' + i;
+    }
+    return 'a' + (i - 10);
+}
+
+static char *hex_string(const uint8_t *bytes,
+                        size_t len)
+{
+    char *hexstr = g_new0(char, len * 2 + 1);
+    size_t i;
+
+    for (i = 0; i < len; i++) {
+        hexstr[i * 2] = hex((bytes[i] >> 4) & 0xf);
+        hexstr[i * 2 + 1] = hex(bytes[i] & 0xf);
+    }
+    hexstr[len * 2] = '\0';
+
+    return hexstr;
+}
+
+static void test_pbkdf(const void *opaque)
+{
+    const QCryptoPbkdfTestData *data = opaque;
+    size_t nout = data->nout;
+    uint8_t *out = g_new0(uint8_t, nout);
+    gchar *expect, *actual;
+
+    qcrypto_pbkdf2(data->hash,
+                   (uint8_t *)data->key, data->nkey,
+                   (uint8_t *)data->salt, data->nsalt,
+                   data->iterations,
+                   (uint8_t *)out, nout,
+                   &error_abort);
+
+    expect = hex_string((const uint8_t *)data->out, data->nout);
+    actual = hex_string(out, nout);
+
+    g_assert_cmpstr(actual, ==, expect);
+
+    g_free(actual);
+    g_free(expect);
+    g_free(out);
+}
+
+
+static void test_pbkdf_timing(void)
+{
+    uint8_t key[32];
+    uint8_t salt[32];
+    int iters;
+
+    memset(key, 0x5d, sizeof(key));
+    memset(salt, 0x7c, sizeof(salt));
+
+    iters = qcrypto_pbkdf2_count_iters(QCRYPTO_HASH_ALG_SHA256,
+                                       key, sizeof(key),
+                                       salt, sizeof(salt),
+                                       &error_abort);
+
+    g_assert(iters >= (1 << 15));
+}
+
+
+int main(int argc, char **argv)
+{
+    size_t i;
+
+    g_test_init(&argc, &argv, NULL);
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
+        g_test_add_data_func(test_data[i].path, &test_data[i], test_pbkdf);
+    }
+
+    g_test_add_func("/crypt/pbkdf/timing", test_pbkdf_timing);
+
+    return g_test_run();
+}
+#else
+int main(int argc, char **argv)
+{
+    return 0;
+}
+#endif
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (2 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  7:51   ` Fam Zheng
  2016-02-04 22:57   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
                   ` (12 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

There are a number of different algorithms that can be used
to generate initialization vectors for disk encryption. This
introduces a simple internal QCryptoBlockIV object to provide
a consistent internal API to the different algorithms. The
initially implemented algorithms are 'plain', 'plain64' and
'essiv', each matching the same named algorithm provided
by the Linux kernel dm-crypt driver.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs      |   4 +
 crypto/ivgen-essiv.c      | 112 +++++++++++++++++++++++++
 crypto/ivgen-essiv.h      |  28 +++++++
 crypto/ivgen-plain.c      |  58 +++++++++++++
 crypto/ivgen-plain.h      |  28 +++++++
 crypto/ivgen-plain64.c    |  58 +++++++++++++
 crypto/ivgen-plain64.h    |  28 +++++++
 crypto/ivgen.c            |  98 ++++++++++++++++++++++
 crypto/ivgenpriv.h        |  49 +++++++++++
 include/crypto/ivgen.h    | 203 ++++++++++++++++++++++++++++++++++++++++++++++
 qapi/crypto.json          |  16 ++++
 tests/.gitignore          |   1 +
 tests/Makefile            |   2 +
 tests/test-crypto-ivgen.c | 168 ++++++++++++++++++++++++++++++++++++++
 14 files changed, 853 insertions(+)
 create mode 100644 crypto/ivgen-essiv.c
 create mode 100644 crypto/ivgen-essiv.h
 create mode 100644 crypto/ivgen-plain.c
 create mode 100644 crypto/ivgen-plain.h
 create mode 100644 crypto/ivgen-plain64.c
 create mode 100644 crypto/ivgen-plain64.h
 create mode 100644 crypto/ivgen.c
 create mode 100644 crypto/ivgenpriv.h
 create mode 100644 include/crypto/ivgen.h
 create mode 100644 tests/test-crypto-ivgen.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 4d2cd3e..3040989 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -13,6 +13,10 @@ crypto-obj-$(CONFIG_GNUTLS) += random-gnutls.o
 crypto-obj-y += pbkdf.o
 crypto-obj-$(CONFIG_NETTLE) += pbkdf-nettle.o
 crypto-obj-$(if $(CONFIG_NETTLE),n,$(CONFIG_GCRYPT)) += pbkdf-gcrypt.o
+crypto-obj-y += ivgen.o
+crypto-obj-y += ivgen-essiv.o
+crypto-obj-y += ivgen-plain.o
+crypto-obj-y += ivgen-plain64.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/ivgen-essiv.c b/crypto/ivgen-essiv.c
new file mode 100644
index 0000000..fc62605
--- /dev/null
+++ b/crypto/ivgen-essiv.c
@@ -0,0 +1,112 @@
+/*
+ * QEMU Crypto block IV generator - essiv
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgen-essiv.h"
+
+typedef struct QCryptoIVGenESSIV QCryptoIVGenESSIV;
+struct QCryptoIVGenESSIV {
+    QCryptoCipher *cipher;
+};
+
+static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
+                                    const uint8_t *key, size_t nkey,
+                                    Error **errp)
+{
+    uint8_t *salt;
+    size_t nhash;
+    QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
+
+    nhash = qcrypto_hash_digest_len(ivgen->hash);
+    /* Salt must be larger of hash size or key size */
+    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);
+
+    if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey,
+                           &salt, &nhash,
+                           errp) < 0) {
+        g_free(essiv);
+        return -1;
+    }
+
+    essiv->cipher = qcrypto_cipher_new(ivgen->cipher,
+                                       QCRYPTO_CIPHER_MODE_ECB,
+                                       salt, nkey,
+                                       errp);
+    if (!essiv->cipher) {
+        g_free(essiv);
+        g_free(salt);
+        return -1;
+    }
+
+    g_free(salt);
+    ivgen->private = essiv;
+
+    return 0;
+}
+
+static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
+                                         uint64_t sector,
+                                         uint8_t *iv, size_t niv,
+                                         Error **errp)
+{
+    QCryptoIVGenESSIV *essiv = ivgen->private;
+    size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher);
+    uint8_t *data = g_new(uint8_t, ndata);
+
+    sector = cpu_to_le64((uint32_t)sector);
+    memcpy(data, (uint8_t *)&sector, ndata);
+    if (sizeof(sector) < ndata) {
+        memset(data + sizeof(sector), 0, ndata - sizeof(sector));
+    }
+
+    if (qcrypto_cipher_encrypt(essiv->cipher,
+                               data,
+                               data,
+                               ndata,
+                               errp) < 0) {
+        g_free(data);
+        return -1;
+    }
+
+    if (ndata > niv) {
+        ndata = niv;
+    }
+    memcpy(iv, data, ndata);
+    if (ndata < niv) {
+        memset(iv + ndata, 0, niv - ndata);
+    }
+    g_free(data);
+    return 0;
+}
+
+static void qcrypto_ivgen_essiv_cleanup(QCryptoIVGen *ivgen)
+{
+    QCryptoIVGenESSIV *essiv = ivgen->private;
+
+    qcrypto_cipher_free(essiv->cipher);
+    g_free(essiv);
+}
+
+
+struct QCryptoIVGenDriver qcrypto_ivgen_essiv = {
+    qcrypto_ivgen_essiv_init,
+    qcrypto_ivgen_essiv_calculate,
+    qcrypto_ivgen_essiv_cleanup,
+};
+
diff --git a/crypto/ivgen-essiv.h b/crypto/ivgen-essiv.h
new file mode 100644
index 0000000..4a00af8
--- /dev/null
+++ b/crypto/ivgen-essiv.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - essiv
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgenpriv.h"
+
+#ifndef QCRYPTO_IVGEN_ESSIV_H__
+#define QCRYPTO_IVGEN_ESSIV_H__
+
+extern struct QCryptoIVGenDriver qcrypto_ivgen_essiv;
+
+#endif /* QCRYPTO_IVGEN_ESSIV_H__ */
diff --git a/crypto/ivgen-plain.c b/crypto/ivgen-plain.c
new file mode 100644
index 0000000..86976b6
--- /dev/null
+++ b/crypto/ivgen-plain.c
@@ -0,0 +1,58 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgen-plain.h"
+
+static int qcrypto_ivgen_plain_init(QCryptoIVGen *ivgen,
+                                    const uint8_t *key, size_t nkey,
+                                    Error **errp)
+{
+    return 0;
+}
+
+static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen,
+                                         uint64_t sector,
+                                         uint8_t *iv, size_t niv,
+                                         Error **errp)
+{
+    size_t ivprefix;
+    uint32_t shortsector = cpu_to_le32((uint32_t)(sector & 0xffffffff));
+    ivprefix = sizeof(shortsector);
+    if (ivprefix > niv) {
+        ivprefix = niv;
+    }
+    memcpy(iv, &shortsector, ivprefix);
+    if (ivprefix < niv) {
+        memset(iv + ivprefix, 0, niv - ivprefix);
+    }
+    return 0;
+}
+
+static void qcrypto_ivgen_plain_cleanup(QCryptoIVGen *ivgen)
+{
+}
+
+
+struct QCryptoIVGenDriver qcrypto_ivgen_plain = {
+    qcrypto_ivgen_plain_init,
+    qcrypto_ivgen_plain_calculate,
+    qcrypto_ivgen_plain_cleanup,
+};
+
diff --git a/crypto/ivgen-plain.h b/crypto/ivgen-plain.h
new file mode 100644
index 0000000..0fe8835
--- /dev/null
+++ b/crypto/ivgen-plain.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgenpriv.h"
+
+#ifndef QCRYPTO_IVGEN_PLAIN_H__
+#define QCRYPTO_IVGEN_PLAIN_H__
+
+extern struct QCryptoIVGenDriver qcrypto_ivgen_plain;
+
+#endif /* QCRYPTO_IVGEN_PLAIN_H__ */
diff --git a/crypto/ivgen-plain64.c b/crypto/ivgen-plain64.c
new file mode 100644
index 0000000..8228b86
--- /dev/null
+++ b/crypto/ivgen-plain64.c
@@ -0,0 +1,58 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgen-plain.h"
+
+static int qcrypto_ivgen_plain_init(QCryptoIVGen *ivgen,
+                                    const uint8_t *key, size_t nkey,
+                                    Error **errp)
+{
+    return 0;
+}
+
+static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen,
+                                         uint64_t sector,
+                                         uint8_t *iv, size_t niv,
+                                         Error **errp)
+{
+    size_t ivprefix;
+    ivprefix = sizeof(sector);
+    sector = cpu_to_le64(sector);
+    if (ivprefix > niv) {
+        ivprefix = niv;
+    }
+    memcpy(iv, &sector, ivprefix);
+    if (ivprefix < niv) {
+        memset(iv + ivprefix, 0, niv - ivprefix);
+    }
+    return 0;
+}
+
+static void qcrypto_ivgen_plain_cleanup(QCryptoIVGen *ivgen)
+{
+}
+
+
+struct QCryptoIVGenDriver qcrypto_ivgen_plain64 = {
+    qcrypto_ivgen_plain_init,
+    qcrypto_ivgen_plain_calculate,
+    qcrypto_ivgen_plain_cleanup,
+};
+
diff --git a/crypto/ivgen-plain64.h b/crypto/ivgen-plain64.h
new file mode 100644
index 0000000..c410445
--- /dev/null
+++ b/crypto/ivgen-plain64.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - plain64
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgenpriv.h"
+
+#ifndef QCRYPTO_IVGEN_PLAIN64_H__
+#define QCRYPTO_IVGEN_PLAIN64_H__
+
+extern struct QCryptoIVGenDriver qcrypto_ivgen_plain64;
+
+#endif /* QCRYPTO_IVGEN_PLAIN64_H__ */
diff --git a/crypto/ivgen.c b/crypto/ivgen.c
new file mode 100644
index 0000000..ae6a2e7
--- /dev/null
+++ b/crypto/ivgen.c
@@ -0,0 +1,98 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgenpriv.h"
+#include "crypto/ivgen-plain.h"
+#include "crypto/ivgen-plain64.h"
+#include "crypto/ivgen-essiv.h"
+
+
+QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgorithm alg,
+                                QCryptoCipherAlgorithm cipheralg,
+                                QCryptoHashAlgorithm hash,
+                                const uint8_t *key, size_t nkey,
+                                Error **errp)
+{
+    QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
+
+    ivgen->algorithm = alg;
+    ivgen->cipher = cipheralg;
+    ivgen->hash = hash;
+
+    switch (alg) {
+    case QCRYPTO_IVGEN_ALG_PLAIN:
+        ivgen->driver = &qcrypto_ivgen_plain;
+        break;
+    case QCRYPTO_IVGEN_ALG_PLAIN64:
+        ivgen->driver = &qcrypto_ivgen_plain64;
+        break;
+    case QCRYPTO_IVGEN_ALG_ESSIV:
+        ivgen->driver = &qcrypto_ivgen_essiv;
+        break;
+    default:
+        error_setg(errp, "Unknown block IV generator algorithm %d", alg);
+        g_free(ivgen);
+        return NULL;
+    }
+
+    if (ivgen->driver->init(ivgen, key, nkey, errp) < 0) {
+        g_free(ivgen);
+        return NULL;
+    }
+
+    return ivgen;
+}
+
+
+int qcrypto_ivgen_calculate(QCryptoIVGen *ivgen,
+                            uint64_t sector,
+                            uint8_t *iv, size_t niv,
+                            Error **errp)
+{
+    return ivgen->driver->calculate(ivgen, sector, iv, niv, errp);
+}
+
+
+QCryptoIVGenAlgorithm qcrypto_ivgen_get_algorithm(QCryptoIVGen *ivgen)
+{
+    return ivgen->algorithm;
+}
+
+
+QCryptoCipherAlgorithm qcrypto_ivgen_get_cipher(QCryptoIVGen *ivgen)
+{
+    return ivgen->cipher;
+}
+
+
+QCryptoHashAlgorithm qcrypto_ivgen_get_hash(QCryptoIVGen *ivgen)
+{
+    return ivgen->hash;
+}
+
+
+void qcrypto_ivgen_free(QCryptoIVGen *ivgen)
+{
+    if (!ivgen) {
+        return;
+    }
+    ivgen->driver->cleanup(ivgen);
+    g_free(ivgen);
+}
diff --git a/crypto/ivgenpriv.h b/crypto/ivgenpriv.h
new file mode 100644
index 0000000..2f68341
--- /dev/null
+++ b/crypto/ivgenpriv.h
@@ -0,0 +1,49 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_IVGEN_PRIV_H__
+#define QCRYPTO_IVGEN_PRIV_H__
+
+#include "crypto/ivgen.h"
+
+typedef struct QCryptoIVGenDriver QCryptoIVGenDriver;
+
+struct QCryptoIVGenDriver {
+    int (*init)(QCryptoIVGen *biv,
+                const uint8_t *key, size_t nkey,
+                Error **errp);
+    int (*calculate)(QCryptoIVGen *biv,
+                     uint64_t sector,
+                     uint8_t *iv, size_t niv,
+                     Error **errp);
+    void (*cleanup)(QCryptoIVGen *biv);
+};
+
+struct QCryptoIVGen {
+    QCryptoIVGenDriver *driver;
+    void *private;
+
+    QCryptoIVGenAlgorithm algorithm;
+    QCryptoCipherAlgorithm cipher;
+    QCryptoHashAlgorithm hash;
+};
+
+
+#endif /* QCRYPTO_IVGEN_PRIV_H__ */
diff --git a/include/crypto/ivgen.h b/include/crypto/ivgen.h
new file mode 100644
index 0000000..63306b1
--- /dev/null
+++ b/include/crypto/ivgen.h
@@ -0,0 +1,203 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_IVGEN_H__
+#define QCRYPTO_IVGEN_H__
+
+#include "crypto/cipher.h"
+#include "crypto/hash.h"
+
+/**
+ * This module provides a framework for generating initialization
+ * vectors for block encryption schemes using chained cipher modes
+ * CBC. The principle is that each disk sector is assigned a unique
+ * initialization vector for use for encryption of data in that
+ * sector.
+ *
+ * <example>
+ *   <title>Encrypting block data with initialiation vectors</title>
+ *   <programlisting>
+ * uint8_t *data = ....data to encrypt...
+ * size_t ndata = XXX;
+ * uint8_t *key = ....some encryption key...
+ * size_t nkey = XXX;
+ * uint8_t *iv;
+ * size_t niv;
+ * size_t sector = 0;
+ *
+ * g_assert((ndata % 512) == 0);
+ *
+ * QCryptoIVGen *ivgen = qcrypto_ivgen_new(QCRYPTO_IVGEN_ALG_ESSIV,
+ *                                         QCRYPTO_CIPHER_ALG_AES_128,
+ *                                         QCRYPTO_HASH_ALG_SHA256,
+ *                                         key, nkey, errp);
+ * if (!ivgen) {
+ *    return -1;
+ * }
+ *
+ * QCryptoCipher *cipher = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_128,
+ *                                            QCRYPTO_CIPHER_MODE_CBC,
+ *                                            key, nkey, errp);
+ * if (!cipher) {
+ *     goto error;
+ * }
+ *
+ * niv =  qcrypto_cipher_get_iv_len(QCRYPTO_CIPHER_ALG_AES_128,
+ *                                  QCRYPTO_CIPHER_MODE_CBC);
+ * iv = g_new0(uint8_t, niv);
+ *
+ *
+ * while (ndata) {
+ *     if (qcrypto_ivgen_calculate(ivgen, sector, iv, niv, errp) < 0) {
+ *         goto error;
+ *     }
+ *     if (qcrypto_cipher_setiv(cipher, iv, niv, errp) < 0) {
+ *         goto error;
+ *     }
+ *     if (qcrypto_cipher_encrypt(cipher,
+ *                                data + (sector * 512),
+ *                                data + (sector * 512),
+ *                                512, errp) < 0) {
+ *         goto error;
+ *     }
+ * }
+ *
+ * g_free(iv);
+ * qcrypto_ivgen_free(ivgen);
+ * qcrypto_cipher_free(cipher);
+ * return 0;
+ *
+ *error:
+ * g_free(iv);
+ * qcrypto_ivgen_free(ivgen);
+ * qcrypto_cipher_free(cipher);
+ * return -1;
+ *   </programlisting>
+ * </example>
+ */
+
+typedef struct QCryptoIVGen QCryptoIVGen;
+
+/* See also QCryptoIVGenAlgorithm enum in qapi/crypto.json */
+
+
+/**
+ * qcrypto_ivgen_new:
+ * @alg: the initialization vector generation algorithm
+ * @cipheralg: the cipher algorithm or 0
+ * @hash: the hash algorithm or 0,
+ * @key: the encryption key or NULL
+ * @nkey: the size of @key in bytes
+ *
+ * Create a new initialization vector generator that uses
+ * the algorithm @alg. Whether the remaining parameters
+ * are required or not depends on the choice of @alg
+ * requested.
+ *
+ * - QCRYPTO_IVGEN_ALG_PLAIN
+ *
+ * The IVs are generated by the 32-bit truncated sector
+ * number. This should never be used for block devices
+ * that are larger than 2^32 sectors in size
+ * All the other parameters are unused.
+ *
+ * - QCRYPTO_IVGEN_ALG_PLAIN64
+ *
+ * The IVs are generated by the 64-bit sector number.
+ * All the other parameters are unused.
+ *
+ * - QCRYPTO_IVGEN_ALG_ESSIV:
+ *
+ * The IVs are generated by encrypting the 64-bit sector
+ * number with a hash of an encryption key. The @cipheralg,
+ * @hash, @key and @nkey parameters are all required.
+ *
+ * Returns: a new IV generator, or NULL on error
+ */
+QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgorithm alg,
+                                QCryptoCipherAlgorithm cipheralg,
+                                QCryptoHashAlgorithm hash,
+                                const uint8_t *key, size_t nkey,
+                                Error **errp);
+
+/**
+ * qcrypto_ivgen_calculate:
+ * @ivgen: the IV generator object
+ * @sector: the 64-bit sector number
+ * @iv: a pre-allocated buffer to hold the generated IV
+ * @niv: the number of bytes in @iv
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Calculate a new initialiation vector for the data
+ * to be stored in sector @sector. The IV will be
+ * written into the buffer @iv of size @niv.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_ivgen_calculate(QCryptoIVGen *ivgen,
+                            uint64_t sector,
+                            uint8_t *iv, size_t niv,
+                            Error **errp);
+
+
+/**
+ * qcrypto_ivgen_get_algorithm:
+ * @ivgen: the IV generator object
+ *
+ * Get the algorithm used by this IV generator
+ *
+ * Returns: the IV generator algorithm
+ */
+QCryptoIVGenAlgorithm qcrypto_ivgen_get_algorithm(QCryptoIVGen *ivgen);
+
+
+/**
+ * qcrypto_ivgen_get_cipher:
+ * @ivgen: the IV generator object
+ *
+ * Get the cipher algorithm used by this IV generator (if
+ * applicable)
+ *
+ * Returns: the cipher algorithm
+ */
+QCryptoCipherAlgorithm qcrypto_ivgen_get_cipher(QCryptoIVGen *ivgen);
+
+
+/**
+ * qcrypto_ivgen_get_algorithm:
+ * @ivgen: the IV generator object
+ *
+ * Get the hash algorithm used by this IV generator (if
+ * applicable)
+ *
+ * Returns: the hash algorithm
+ */
+QCryptoHashAlgorithm qcrypto_ivgen_get_hash(QCryptoIVGen *ivgen);
+
+
+/**
+ * qcrypto_ivgen_free:
+ * @ivgen: the IV generator object
+ *
+ * Release all resources associated with @ivgen
+ */
+void qcrypto_ivgen_free(QCryptoIVGen *ivgen);
+
+#endif /* QCRYPTO_IVGEN_H__ */
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 4bd690f..48946b0 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -78,3 +78,19 @@
 { 'enum': 'QCryptoCipherMode',
   'prefix': 'QCRYPTO_CIPHER_MODE',
   'data': ['ecb', 'cbc']}
+
+
+##
+# QCryptoIVGenAlgorithm:
+#
+# The supported algorithms for generating initialization
+# vectors for full disk encryption
+#
+# @plain: 64-bit sector number truncated to 32-bits
+# @plain64: 64-bit sector number
+# @essiv: 64-bit sector number encrypted with a hash of the encryption key
+# Since: 2.6
+##
+{ 'enum': 'QCryptoIVGenAlgorithm',
+  'prefix': 'QCRYPTO_IVGEN_ALG',
+  'data': ['plain', 'plain64', 'essiv']}
diff --git a/tests/.gitignore b/tests/.gitignore
index db6b9be..369f848 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -14,6 +14,7 @@ test-blockjob-txn
 test-coroutine
 test-crypto-cipher
 test-crypto-hash
+test-crypto-ivgen
 test-crypto-pbkdf
 test-crypto-secret
 test-crypto-tlscredsx509
diff --git a/tests/Makefile b/tests/Makefile
index 833a290..5fdbaf0 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -93,6 +93,7 @@ check-unit-y += tests/test-io-channel-command$(EXESUF)
 check-unit-y += tests/test-io-channel-buffer$(EXESUF)
 check-unit-y += tests/test-base64$(EXESUF)
 check-unit-y += tests/test-crypto-pbkdf$(EXESUF)
+check-unit-y += tests/test-crypto-ivgen$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -497,6 +498,7 @@ tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \
 tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
 tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y)
+tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y)
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-ivgen.c b/tests/test-crypto-ivgen.c
new file mode 100644
index 0000000..55c5984
--- /dev/null
+++ b/tests/test-crypto-ivgen.c
@@ -0,0 +1,168 @@
+/*
+ * QEMU Crypto IV generator algorithms
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/ivgen.h"
+
+
+struct QCryptoIVGenTestData {
+    const char *path;
+    uint64_t sector;
+    QCryptoIVGenAlgorithm ivalg;
+    QCryptoHashAlgorithm hashalg;
+    QCryptoCipherAlgorithm cipheralg;
+    const uint8_t *key;
+    size_t nkey;
+    const uint8_t *iv;
+    size_t niv;
+} test_data[] = {
+    /* Small */
+    {
+        "/crypto/ivgen/plain/1",
+        .sector = 0x1,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
+        .iv = (const uint8_t *)"\x01\x00\x00\x00\x00\x00\x00\x00"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* Big ! */
+    {
+        "/crypto/ivgen/plain/1f2e3d4c",
+        .sector = 0x1f2e3d4cULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
+        .iv = (const uint8_t *)"\x4c\x3d\x2e\x1f\x00\x00\x00\x00"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* Truncation */
+    {
+        "/crypto/ivgen/plain/1f2e3d4c5b6a7988",
+        .sector = 0x1f2e3d4c5b6a7988ULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
+        .iv = (const uint8_t *)"\x88\x79\x6a\x5b\x00\x00\x00\x00"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* Small */
+    {
+        "/crypto/ivgen/plain64/1",
+        .sector = 0x1,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
+        .iv = (const uint8_t *)"\x01\x00\x00\x00\x00\x00\x00\x00"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* Big ! */
+    {
+        "/crypto/ivgen/plain64/1f2e3d4c",
+        .sector = 0x1f2e3d4cULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
+        .iv = (const uint8_t *)"\x4c\x3d\x2e\x1f\x00\x00\x00\x00"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* No Truncation */
+    {
+        "/crypto/ivgen/plain64/1f2e3d4c5b6a7988",
+        .sector = 0x1f2e3d4c5b6a7988ULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
+        .iv = (const uint8_t *)"\x88\x79\x6a\x5b\x4c\x3d\x2e\x1f"
+                               "\x00\x00\x00\x00\x00\x00\x00\x00",
+        .niv = 16,
+    },
+    /* Small */
+    {
+        "/crypto/ivgen/essiv/1",
+        .sector = 0x1,
+        .ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
+        .cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
+        .hashalg = QCRYPTO_HASH_ALG_SHA256,
+        .key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
+                                "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+        .nkey = 16,
+        .iv = (const uint8_t *)"\xd4\x83\x71\xb2\xa1\x94\x53\x88"
+                               "\x1c\x7a\x2d\06\x2d\x0b\x65\x46",
+        .niv = 16,
+    },
+    /* Big ! */
+    {
+        "/crypto/ivgen/essiv/1f2e3d4c",
+        .sector = 0x1f2e3d4cULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
+        .cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
+        .hashalg = QCRYPTO_HASH_ALG_SHA256,
+        .key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
+                                "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+        .nkey = 16,
+        .iv = (const uint8_t *)"\x5d\x36\x09\x5d\xc6\x9e\x5e\xe9"
+                               "\xe3\x02\x8d\xd8\x7a\x3d\xe7\x8f",
+        .niv = 16,
+    },
+    /* No Truncation */
+    {
+        "/crypto/ivgen/essiv/1f2e3d4c5b6a7988",
+        .sector = 0x1f2e3d4c5b6a7988ULL,
+        .ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
+        .cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
+        .hashalg = QCRYPTO_HASH_ALG_SHA256,
+        .key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
+                                "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+        .nkey = 16,
+        .iv = (const uint8_t *)"\xe8\x6d\xb9\x11\x17\x7a\x86\xed"
+                               "\x7e\xe6\xb2\xe7\xbf\x8f\xa1\x8c",
+        .niv = 16,
+    },
+};
+
+
+static void test_ivgen(const void *opaque)
+{
+    const struct QCryptoIVGenTestData *data = opaque;
+    uint8_t *iv = g_new0(uint8_t, data->niv);
+    QCryptoIVGen *ivgen = qcrypto_ivgen_new(
+        data->ivalg,
+        data->cipheralg,
+        data->hashalg,
+        data->key,
+        data->nkey,
+        &error_abort);
+
+    qcrypto_ivgen_calculate(ivgen,
+                            data->sector,
+                            iv,
+                            data->niv,
+                            &error_abort);
+
+    g_assert(memcmp(iv, data->iv, data->niv) == 0);
+
+    qcrypto_ivgen_free(ivgen);
+    g_free(iv);
+}
+
+int main(int argc, char **argv)
+{
+    size_t i;
+    g_test_init(&argc, &argv, NULL);
+    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
+        g_test_add_data_func(test_data[i].path,
+                             &(test_data[i]),
+                             test_ivgen);
+    }
+    return g_test_run();
+}
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (3 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  8:37   ` Fam Zheng
  2016-02-04 23:26   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework Daniel P. Berrange
                   ` (11 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

The LUKS format specifies an anti-forensic split algorithm which
is used to artificially expand the size of the key material on
disk. This is an implementation of that algorithm.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs        |   1 +
 crypto/afsplit.c            | 162 ++++++++++++++++++++++++++++++++++++++++
 include/crypto/afsplit.h    | 135 +++++++++++++++++++++++++++++++++
 tests/.gitignore            |   1 +
 tests/Makefile              |   2 +
 tests/test-crypto-afsplit.c | 176 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 477 insertions(+)
 create mode 100644 crypto/afsplit.c
 create mode 100644 include/crypto/afsplit.h
 create mode 100644 tests/test-crypto-afsplit.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 3040989..5136737 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -17,6 +17,7 @@ crypto-obj-y += ivgen.o
 crypto-obj-y += ivgen-essiv.o
 crypto-obj-y += ivgen-plain.o
 crypto-obj-y += ivgen-plain64.o
+crypto-obj-y += afsplit.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/afsplit.c b/crypto/afsplit.c
new file mode 100644
index 0000000..42529e7
--- /dev/null
+++ b/crypto/afsplit.c
@@ -0,0 +1,162 @@
+/*
+ * QEMU Crypto anti forensic information splitter
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * Derived from cryptsetup package lib/lusk1/af.c
+ *
+ * Copyright (C) 2004, Clemens Fruhwirth <clemens@endorphin.org>
+ * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved.
+ *
+ * This library 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 library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/afsplit.h"
+#include "crypto/random.h"
+
+
+static void qcrypto_afsplit_xor(size_t blocklen,
+                                const uint8_t *in1,
+                                const uint8_t *in2,
+                                uint8_t *out)
+{
+    size_t i;
+    for (i = 0; i < blocklen; i++) {
+        out[i] = in1[i] ^ in2[i];
+    }
+}
+
+
+static int qcrypto_afsplit_hash(QCryptoHashAlgorithm hash,
+                                size_t blocklen,
+                                uint8_t *block,
+                                Error **errp)
+{
+    size_t digestlen = qcrypto_hash_digest_len(hash);
+
+    size_t hashcount = blocklen / digestlen;
+    size_t finallen = blocklen % digestlen;
+    uint32_t i;
+
+    if (finallen) {
+        hashcount++;
+    } else {
+        finallen = blocklen;
+    }
+
+    for (i = 0; i < hashcount; i++) {
+        uint8_t *out = NULL;
+        size_t outlen = 0;
+        uint32_t iv = cpu_to_be32(i);
+        struct iovec in[] = {
+            { .iov_base = &iv,
+              .iov_len = sizeof(iv) },
+            { .iov_base = block + (i * digestlen),
+              .iov_len = (i == (hashcount - 1)) ? finallen : digestlen },
+        };
+
+        if (qcrypto_hash_bytesv(hash,
+                                in,
+                                G_N_ELEMENTS(in),
+                                &out, &outlen,
+                                errp) < 0) {
+            return -1;
+        }
+
+        if (outlen != digestlen) {
+            error_setg(errp, "Hash output %zu not %zu",
+                       outlen, digestlen);
+            g_free(out);
+            return -1;
+        }
+        memcpy(block + (i * digestlen), out,
+               (i == (hashcount - 1)) ? finallen : digestlen);
+        g_free(out);
+    }
+
+    return 0;
+}
+
+
+int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
+                           size_t blocklen,
+                           uint32_t stripes,
+                           const uint8_t *in,
+                           uint8_t *out,
+                           Error **errp)
+{
+    uint8_t *block = g_new0(uint8_t, blocklen);
+    size_t i;
+    int ret = -1;
+
+    for (i = 0; i < (stripes - 1); i++) {
+        if (qcrypto_random_bytes(out + (i * blocklen), blocklen, errp) < 0) {
+            goto cleanup;
+        }
+
+        qcrypto_afsplit_xor(blocklen,
+                            out + (i * blocklen),
+                            block,
+                            block);
+        if (qcrypto_afsplit_hash(hash, blocklen, block,
+                                 errp) < 0) {
+            goto cleanup;
+        }
+    }
+    qcrypto_afsplit_xor(blocklen,
+                        in,
+                        block,
+                        out + (i * blocklen));
+    ret = 0;
+
+ cleanup:
+    g_free(block);
+    return ret;
+}
+
+
+int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
+                           size_t blocklen,
+                           uint32_t stripes,
+                           const uint8_t *in,
+                           uint8_t *out,
+                           Error **errp)
+{
+    uint8_t *block = g_new0(uint8_t, blocklen);
+    size_t i;
+    int ret = -1;
+
+    for (i = 0; i < (stripes - 1); i++) {
+        qcrypto_afsplit_xor(blocklen,
+                            in + (i * blocklen),
+                            block,
+                            block);
+        if (qcrypto_afsplit_hash(hash, blocklen, block,
+                                 errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    qcrypto_afsplit_xor(blocklen,
+                        in + (i * blocklen),
+                        block,
+                        out);
+
+    ret = 0;
+
+ cleanup:
+    g_free(block);
+    return ret;
+}
diff --git a/include/crypto/afsplit.h b/include/crypto/afsplit.h
new file mode 100644
index 0000000..0e0d256
--- /dev/null
+++ b/include/crypto/afsplit.h
@@ -0,0 +1,135 @@
+/*
+ * QEMU Crypto anti forensic information splitter
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library 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 library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_AFSPLIT_H__
+#define QCRYPTO_AFSPLIT_H__
+
+#include "crypto/hash.h"
+
+/**
+ * This module implements the anti-forensic splitter that is specified
+ * as part of the LUKS format:
+ *
+ *   http://clemens.endorphin.org/cryptography
+ *   http://clemens.endorphin.org/TKS1-draft.pdf
+ *
+ * The core idea is to take a short piece of data (key material)
+ * and process it to expand it to a much larger piece of data.
+ * The expansion process is reversable, to obtain the original
+ * short data. The key property of the expansion is that if any
+ * byte in the larger data set is changed / missing, it should be
+ * impossible to recreate the original short data.
+ *
+ * <example>
+ *    <title>Creating a large split key for storage</title>
+ *    <programlisting>
+ * size_t nkey = 32;
+ * uint32_t stripes = 32768; // To produce a 1 MB split key
+ * uint8_t *masterkey = ....a 32-byte AES key...
+ * uint8_t *splitkey;
+ *
+ * splitkey = g_new0(uint8_t, nkey * stripes);
+ *
+ * if (qcrypto_afsplit_encode(QCRYPTO_HASH_ALG_SHA256,
+ *                            nkey, stripes,
+ *                            masterkey, splitkey, errp) < 0) {
+ *     g_free(splitkey);
+ *     g_free(masterkey);
+ *     return -1;
+ * }
+ *
+ * ...store splitkey somewhere...
+ *
+ * g_free(splitkey);
+ * g_free(masterkey);
+ *    </programlisting>
+ * </example>
+ *
+ * <example>
+ *    <title>Retrieving a master key from storage</title>
+ *    <programlisting>
+ * size_t nkey = 32;
+ * uint32_t stripes = 32768; // To produce a 1 MB split key
+ * uint8_t *masterkey;
+ * uint8_t *splitkey = .... read in 1 MB of data...
+ *
+ * masterkey = g_new0(uint8_t, nkey);
+ *
+ * if (qcrypto_afsplit_decode(QCRYPTO_HASH_ALG_SHA256,
+ *                            nkey, stripes,
+ *                            splitkey, masterkey, errp) < 0) {
+ *     g_free(splitkey);
+ *     g_free(masterkey);
+ *     return -1;
+ * }
+ *
+ * ..decrypt data with masterkey...
+ *
+ * g_free(splitkey);
+ * g_free(masterkey);
+ *    </programlisting>
+ * </example>
+ */
+
+/**
+ * qcrypto_afsplit_encode:
+ * @hash: the hash algorithm to use for data expansion
+ * @blocklen: the size of @in in bytes
+ * @stripes: the number of times to expand @in in size
+ * @in: the master key to be expanded in size
+ * @out: preallocted buffer to hold the split key
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Split the data in @in, which is @blocklen bytes in
+ * size, to form a larger piece of data @out, which is
+ * @blocklen * @stripes bytes in size.
+ *
+ * Returns: 0 on success, -1 on error;
+ */
+int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
+                           size_t blocklen,
+                           uint32_t stripes,
+                           const uint8_t *in,
+                           uint8_t *out,
+                           Error **errp);
+
+/**
+ * qcrypto_afsplit_decode:
+ * @hash: the hash algorithm to use for data compression
+ * @blocklen: the size of @out in bytes
+ * @stripes: the number of times to decrease @in in size
+ * @in: the master key to be expanded in size
+ * @out: preallocted buffer to hold the split key
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Join the data in @in, which is @blocklen * @stripes
+ * bytes in size, to form the original small piece o
+ * data @out, which is @blocklen bytes in size.
+ *
+ * Returns: 0 on success, -1 on error;
+ */
+int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
+                           size_t blocklen,
+                           uint32_t stripes,
+                           const uint8_t *in,
+                           uint8_t *out,
+                           Error **errp);
+
+#endif /* QCRYPTO_AFSPLIT_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 369f848..5b97e8c 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -12,6 +12,7 @@ test-base64
 test-bitops
 test-blockjob-txn
 test-coroutine
+test-crypto-afsplit
 test-crypto-cipher
 test-crypto-hash
 test-crypto-ivgen
diff --git a/tests/Makefile b/tests/Makefile
index 5fdbaf0..80847b9 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -94,6 +94,7 @@ check-unit-y += tests/test-io-channel-buffer$(EXESUF)
 check-unit-y += tests/test-base64$(EXESUF)
 check-unit-y += tests/test-crypto-pbkdf$(EXESUF)
 check-unit-y += tests/test-crypto-ivgen$(EXESUF)
+check-unit-y += tests/test-crypto-afsplit$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -499,6 +500,7 @@ tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
 tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y)
 tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y)
+tests/test-crypto-afsplit$(EXESUF): tests/test-crypto-afsplit.o $(test-crypto-obj-y)
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-afsplit.c b/tests/test-crypto-afsplit.c
new file mode 100644
index 0000000..3ef95d4
--- /dev/null
+++ b/tests/test-crypto-afsplit.c
@@ -0,0 +1,176 @@
+/*
+ * QEMU Crypto anti-forensic splitter
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "crypto/init.h"
+#include "crypto/afsplit.h"
+
+typedef struct QCryptoAFSplitTestData QCryptoAFSplitTestData;
+struct QCryptoAFSplitTestData {
+    const char *path;
+    QCryptoHashAlgorithm hash;
+    uint32_t stripes;
+    size_t blocklen;
+    const uint8_t *key;
+    const uint8_t *splitkey;
+};
+
+static QCryptoAFSplitTestData test_data[] = {
+    {
+        .path = "/crypto/afsplit/sha256/5",
+        .hash = QCRYPTO_HASH_ALG_SHA256,
+        .stripes = 5,
+        .blocklen = 32,
+        .key = (const uint8_t *)
+            "\x00\x01\x02\x03\x04\x05\x06\x07"
+            "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+            "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
+            "\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
+        .splitkey = (const uint8_t *)
+            "\xfd\xd2\x73\xb1\x7d\x99\x93\x34"
+            "\x70\xde\xfa\x07\xc5\xac\x58\xd2"
+            "\x30\x67\x2f\x1a\x35\x43\x60\x7d"
+            "\x77\x02\xdb\x62\x3c\xcb\x2c\x33"
+            "\x48\x08\xb6\xf1\x7c\xa3\x20\xa0"
+            "\xad\x2d\x4c\xf3\xcd\x18\x6f\x53"
+            "\xf9\xe8\xe7\x59\x27\x3c\xa9\x54"
+            "\x61\x87\xb3\xaf\xf6\xf7\x7e\x64"
+            "\x86\xaa\x89\x7f\x1f\x9f\xdb\x86"
+            "\xf4\xa2\x16\xff\xa3\x4f\x8c\xa1"
+            "\x59\xc4\x23\x34\x28\xc4\x77\x71"
+            "\x83\xd4\xcd\x8e\x89\x1b\xc7\xc5"
+            "\xae\x4d\xa9\xcd\xc9\x72\x85\x70"
+            "\x13\x68\x52\x83\xfc\xb8\x11\x72"
+            "\xba\x3d\xc6\x4a\x28\xfa\xe2\x86"
+            "\x7b\x27\xab\x58\xe1\xa4\xca\xf6"
+            "\x9e\xbc\xfe\x0c\x92\x79\xb3\xec"
+            "\x1c\x5f\x79\x3b\x0d\x1e\xaa\x1a"
+            "\x77\x0f\x70\x19\x4b\xc8\x80\xee"
+            "\x27\x7c\x6e\x4a\x91\x96\x5c\xf4"
+    },
+    {
+        .path = "/crypto/afsplit/sha256/5000",
+        .hash = QCRYPTO_HASH_ALG_SHA256,
+        .stripes = 5000,
+        .blocklen = 16,
+        .key = (const uint8_t *)
+            "\x00\x01\x02\x03\x04\x05\x06\x07"
+            "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+    },
+    {
+        .path = "/crypto/afsplit/sha1/1000",
+        .hash = QCRYPTO_HASH_ALG_SHA1,
+        .stripes = 1000,
+        .blocklen = 32,
+        .key = (const uint8_t *)
+            "\x00\x01\x02\x03\x04\x05\x06\x07"
+            "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+            "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
+            "\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
+    },
+};
+
+
+static inline char hex(int i)
+{
+    if (i < 10) {
+        return '0' + i;
+    }
+    return 'a' + (i - 10);
+}
+
+static char *hex_string(const uint8_t *bytes,
+                        size_t len)
+{
+    char *hexstr = g_new0(char, len * 2 + 1);
+    size_t i;
+
+    for (i = 0; i < len; i++) {
+        hexstr[i * 2] = hex((bytes[i] >> 4) & 0xf);
+        hexstr[i * 2 + 1] = hex(bytes[i] & 0xf);
+    }
+    hexstr[len * 2] = '\0';
+
+    return hexstr;
+}
+
+static void test_afsplit(const void *opaque)
+{
+    const QCryptoAFSplitTestData *data = opaque;
+    size_t splitlen = data->blocklen * data->stripes;
+    uint8_t *splitkey = g_new0(uint8_t, splitlen);
+    uint8_t *key = g_new0(uint8_t, data->blocklen);
+    gchar *expect, *actual;
+
+    /* First time we round-trip the key */
+    qcrypto_afsplit_encode(data->hash,
+                           data->blocklen, data->stripes,
+                           data->key, splitkey,
+                           &error_abort);
+
+    qcrypto_afsplit_decode(data->hash,
+                           data->blocklen, data->stripes,
+                           splitkey, key,
+                           &error_abort);
+
+    expect = hex_string(data->key, data->blocklen);
+    actual = hex_string(key, data->blocklen);
+
+    g_assert_cmpstr(actual, ==, expect);
+
+    g_free(actual);
+    g_free(expect);
+
+    /* Second time we merely try decoding a previous split */
+    if (data->splitkey) {
+        memset(key, 0, data->blocklen);
+
+        qcrypto_afsplit_decode(data->hash,
+                               data->blocklen, data->stripes,
+                               data->splitkey, key,
+                               &error_abort);
+
+        expect = hex_string(data->key, data->blocklen);
+        actual = hex_string(key, data->blocklen);
+
+        g_assert_cmpstr(actual, ==, expect);
+
+        g_free(actual);
+        g_free(expect);
+    }
+
+    g_free(key);
+    g_free(splitkey);
+}
+
+int main(int argc, char **argv)
+{
+    size_t i;
+
+    g_test_init(&argc, &argv, NULL);
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
+        g_test_add_data_func(test_data[i].path, &test_data[i], test_afsplit);
+    }
+    return g_test_run();
+}
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (4 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-05  0:23   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format Daniel P. Berrange
                   ` (10 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Add a generic framework for support different block encryption
formats. Upon instantiating a QCryptoBlock object, it will read
the encryption header and extract the encryption keys. It is
then possible to call methods to encrypt/decrypt data buffers.

There is also a mode whereby it will create/initialize a new
encryption header on a previously unformatted volume.

The initial framework comes with support for the legacy QCow
AES based encryption. This enables code in the QCow driver to
be consolidated later.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs      |   2 +
 crypto/block-qcow.c       | 167 +++++++++++++++++++++++++++++
 crypto/block-qcow.h       |  28 +++++
 crypto/block.c            | 263 ++++++++++++++++++++++++++++++++++++++++++++++
 crypto/blockpriv.h        |  90 ++++++++++++++++
 include/crypto/block.h    | 233 ++++++++++++++++++++++++++++++++++++++++
 qapi/crypto.json          |  67 ++++++++++++
 tests/.gitignore          |   1 +
 tests/Makefile            |   2 +
 tests/test-crypto-block.c | 239 +++++++++++++++++++++++++++++++++++++++++
 10 files changed, 1092 insertions(+)
 create mode 100644 crypto/block-qcow.c
 create mode 100644 crypto/block-qcow.h
 create mode 100644 crypto/block.c
 create mode 100644 crypto/blockpriv.h
 create mode 100644 include/crypto/block.h
 create mode 100644 tests/test-crypto-block.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 5136737..25ca0c7 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -18,6 +18,8 @@ crypto-obj-y += ivgen-essiv.o
 crypto-obj-y += ivgen-plain.o
 crypto-obj-y += ivgen-plain64.o
 crypto-obj-y += afsplit.o
+crypto-obj-y += block.o
+crypto-obj-y += block-qcow.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/block-qcow.c b/crypto/block-qcow.c
new file mode 100644
index 0000000..b2bd8ce
--- /dev/null
+++ b/crypto/block-qcow.c
@@ -0,0 +1,167 @@
+/*
+ * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config-host.h"
+
+#include "crypto/block-qcow.h"
+#include "crypto/secret.h"
+
+static gboolean
+qcrypto_block_qcow_has_format(const uint8_t *buf G_GNUC_UNUSED,
+                              size_t buf_size G_GNUC_UNUSED)
+{
+    return false;
+}
+
+
+static int
+qcrypto_block_qcow_init(QCryptoBlock *block,
+                        const char *keysecret,
+                        Error **errp)
+{
+    char *password;
+    int ret;
+    uint8_t keybuf[16];
+    int len, i;
+
+    memset(keybuf, 0, 16);
+
+    password = qcrypto_secret_lookup_as_utf8(keysecret, errp);
+    if (!password) {
+        return -1;
+    }
+
+    len = strlen(password);
+    if (len > 16) {
+        len = 16;
+    }
+    for (i = 0; i < len; i++) {
+        keybuf[i] = password[i];
+    }
+    g_free(password);
+
+    block->niv = qcrypto_cipher_get_iv_len(QCRYPTO_CIPHER_ALG_AES_128,
+                                           QCRYPTO_CIPHER_MODE_CBC);
+    block->ivgen = qcrypto_ivgen_new(QCRYPTO_IVGEN_ALG_PLAIN64,
+                                     0, 0, NULL, 0, errp);
+    if (!block->ivgen) {
+        ret = -ENOTSUP;
+        goto fail;
+    }
+
+    block->cipher = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_128,
+                                       QCRYPTO_CIPHER_MODE_CBC,
+                                       keybuf, G_N_ELEMENTS(keybuf),
+                                       errp);
+    if (!block->cipher) {
+        ret = -ENOTSUP;
+        goto fail;
+    }
+
+    block->payload_offset = 0;
+
+    return 0;
+
+ fail:
+    qcrypto_cipher_free(block->cipher);
+    qcrypto_ivgen_free(block->ivgen);
+    return ret;
+}
+
+
+static int
+qcrypto_block_qcow_open(QCryptoBlock *block,
+                        QCryptoBlockOpenOptions *options,
+                        QCryptoBlockReadFunc readfunc G_GNUC_UNUSED,
+                        void *opaque G_GNUC_UNUSED,
+                        unsigned int flags,
+                        Error **errp)
+{
+    if (flags & QCRYPTO_BLOCK_OPEN_NO_IO) {
+        return 0;
+    } else {
+        if (!options->u.qcow->key_secret) {
+            error_setg(errp,
+                       "Parameter 'key-secret' is required for cipher");
+            return -1;
+        }
+        return qcrypto_block_qcow_init(block,
+                                       options->u.qcow->key_secret, errp);
+    }
+}
+
+
+static int
+qcrypto_block_qcow_create(QCryptoBlock *block,
+                          QCryptoBlockCreateOptions *options,
+                          QCryptoBlockInitFunc initfunc G_GNUC_UNUSED,
+                          QCryptoBlockWriteFunc writefunc G_GNUC_UNUSED,
+                          void *opaque G_GNUC_UNUSED,
+                          Error **errp)
+{
+    if (!options->u.qcow->key_secret) {
+        error_setg(errp, "Parameter 'key-secret' is required for cipher");
+        return -1;
+    }
+    /* QCow2 has no special header, since everything is hardwired */
+    return qcrypto_block_qcow_init(block, options->u.qcow->key_secret, errp);
+}
+
+
+static void
+qcrypto_block_qcow_cleanup(QCryptoBlock *block)
+{
+}
+
+
+static int
+qcrypto_block_qcow_decrypt(QCryptoBlock *block,
+                           uint64_t startsector,
+                           uint8_t *buf,
+                           size_t len,
+                           Error **errp)
+{
+    return qcrypto_block_decrypt_helper(block->cipher,
+                                        block->niv, block->ivgen,
+                                        startsector, buf, len, errp);
+}
+
+
+static int
+qcrypto_block_qcow_encrypt(QCryptoBlock *block,
+                           uint64_t startsector,
+                           uint8_t *buf,
+                           size_t len,
+                           Error **errp)
+{
+    return qcrypto_block_encrypt_helper(block->cipher,
+                                        block->niv, block->ivgen,
+                                        startsector, buf, len, errp);
+}
+
+
+const QCryptoBlockDriver qcrypto_block_driver_qcow = {
+    .open = qcrypto_block_qcow_open,
+    .create = qcrypto_block_qcow_create,
+    .cleanup = qcrypto_block_qcow_cleanup,
+    .decrypt = qcrypto_block_qcow_decrypt,
+    .encrypt = qcrypto_block_qcow_encrypt,
+    .has_format = qcrypto_block_qcow_has_format,
+};
diff --git a/crypto/block-qcow.h b/crypto/block-qcow.h
new file mode 100644
index 0000000..569f836
--- /dev/null
+++ b/crypto/block-qcow.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_BLOCK_QCOW_H__
+#define QCRYPTO_BLOCK_QCOW_H__
+
+#include "crypto/blockpriv.h"
+
+extern const QCryptoBlockDriver qcrypto_block_driver_qcow;
+
+#endif /* QCRYPTO_BLOCK_QCOW_H__ */
diff --git a/crypto/block.c b/crypto/block.c
new file mode 100644
index 0000000..757e28a
--- /dev/null
+++ b/crypto/block.c
@@ -0,0 +1,263 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/blockpriv.h"
+#include "crypto/block-qcow.h"
+
+static const QCryptoBlockDriver *qcrypto_block_drivers[] = {
+    [Q_CRYPTO_BLOCK_FORMAT_QCOW] = &qcrypto_block_driver_qcow,
+};
+
+
+gboolean qcrypto_block_has_format(QCryptoBlockFormat format,
+                                  const uint8_t *buf,
+                                  size_t len)
+{
+    const QCryptoBlockDriver *driver;
+
+    if (format >= G_N_ELEMENTS(qcrypto_block_drivers) ||
+        !qcrypto_block_drivers[format]) {
+        return false;
+    }
+
+    driver = qcrypto_block_drivers[format];
+
+    return driver->has_format(buf, len);
+}
+
+
+QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
+                                 QCryptoBlockReadFunc readfunc,
+                                 void *opaque,
+                                 unsigned int flags,
+                                 Error **errp)
+{
+    QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+
+    block->format = options->format;
+
+    if (options->format >= G_N_ELEMENTS(qcrypto_block_drivers) ||
+        !qcrypto_block_drivers[options->format]) {
+        error_setg(errp, "Unsupported block driver %d", options->format);
+        g_free(block);
+        return NULL;
+    }
+
+    block->driver = qcrypto_block_drivers[options->format];
+
+    if (block->driver->open(block, options,
+                            readfunc, opaque, flags, errp) < 0) {
+        g_free(block);
+        return NULL;
+    }
+
+    return block;
+}
+
+
+QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
+                                   QCryptoBlockInitFunc initfunc,
+                                   QCryptoBlockWriteFunc writefunc,
+                                   void *opaque,
+                                   Error **errp)
+{
+    QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+
+    block->format = options->format;
+
+    if (options->format >= G_N_ELEMENTS(qcrypto_block_drivers) ||
+        !qcrypto_block_drivers[options->format]) {
+        error_setg(errp, "Unsupported block driver %d", options->format);
+        g_free(block);
+        return NULL;
+    }
+
+    block->driver = qcrypto_block_drivers[options->format];
+
+    if (block->driver->create(block, options, initfunc,
+                              writefunc, opaque, errp) < 0) {
+        g_free(block);
+        return NULL;
+    }
+
+    return block;
+}
+
+
+int qcrypto_block_decrypt(QCryptoBlock *block,
+                          uint64_t startsector,
+                          uint8_t *buf,
+                          size_t len,
+                          Error **errp)
+{
+    return block->driver->decrypt(block, startsector, buf, len, errp);
+}
+
+
+int qcrypto_block_encrypt(QCryptoBlock *block,
+                          uint64_t startsector,
+                          uint8_t *buf,
+                          size_t len,
+                          Error **errp)
+{
+    return block->driver->encrypt(block, startsector, buf, len, errp);
+}
+
+
+QCryptoCipher *qcrypto_block_get_cipher(QCryptoBlock *block)
+{
+    return block->cipher;
+}
+
+
+QCryptoIVGen *qcrypto_block_get_ivgen(QCryptoBlock *block)
+{
+    return block->ivgen;
+}
+
+
+QCryptoHashAlgorithm qcrypto_block_get_kdf_hash(QCryptoBlock *block)
+{
+    return block->kdfhash;
+}
+
+
+uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block)
+{
+    return block->payload_offset;
+}
+
+
+void qcrypto_block_free(QCryptoBlock *block)
+{
+    if (!block) {
+        return;
+    }
+
+    block->driver->cleanup(block);
+
+    qcrypto_cipher_free(block->cipher);
+    qcrypto_ivgen_free(block->ivgen);
+    g_free(block);
+}
+
+
+int qcrypto_block_decrypt_helper(QCryptoCipher *cipher,
+                                 size_t niv,
+                                 QCryptoIVGen *ivgen,
+                                 uint64_t startsector,
+                                 uint8_t *buf,
+                                 size_t len,
+                                 Error **errp)
+{
+    size_t nsectors = len / 512;
+    size_t i;
+    uint8_t *iv;
+    int ret = -1;
+
+    if (len % 512) {
+        error_setg(errp, "Length %zu must be a multiple of sector size",
+                   len);
+        return -1;
+    }
+
+    iv = niv ? g_new0(uint8_t, niv) : NULL;
+
+    for (i = 0; i < nsectors; i++) {
+        if (niv) {
+            if (qcrypto_ivgen_calculate(ivgen,
+                                        startsector + i,
+                                        iv, niv,
+                                        errp) < 0) {
+                goto cleanup;
+            }
+
+            if (qcrypto_cipher_setiv(cipher,
+                                     iv, niv,
+                                     errp) < 0) {
+                goto cleanup;
+            }
+        }
+        if (qcrypto_cipher_decrypt(cipher,
+                                   buf + (i * 512),
+                                   buf + (i * 512),
+                                   512,
+                                   errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    g_free(iv);
+    return ret;
+}
+
+
+int qcrypto_block_encrypt_helper(QCryptoCipher *cipher,
+                                 size_t niv,
+                                 QCryptoIVGen *ivgen,
+                                 uint64_t startsector,
+                                 uint8_t *buf,
+                                 size_t len,
+                                 Error **errp)
+{
+    size_t nsectors = len / 512;
+    size_t i;
+    uint8_t *iv;
+    int ret = -1;
+
+    if (len % 512) {
+        error_setg(errp, "Length %zu must be a multiple of sector size",
+                   len);
+        return -1;
+    }
+
+    iv = niv ? g_new0(uint8_t, niv) : NULL;
+
+    for (i = 0; i < nsectors; i++) {
+        if (niv) {
+            if (qcrypto_ivgen_calculate(ivgen,
+                                        startsector + i,
+                                        iv, niv,
+                                        errp) < 0) {
+                goto cleanup;
+            }
+
+            if (qcrypto_cipher_setiv(cipher,
+                                     iv, niv,
+                                     errp) < 0) {
+                goto cleanup;
+            }
+        }
+        if (qcrypto_cipher_encrypt(cipher,
+                                   buf + (i * 512),
+                                   buf + (i * 512),
+                                   512,
+                                   errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    g_free(iv);
+    return ret;
+}
diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h
new file mode 100644
index 0000000..3f0ff5e
--- /dev/null
+++ b/crypto/blockpriv.h
@@ -0,0 +1,90 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_BLOCK_PRIV_H__
+#define QCRYPTO_BLOCK_PRIV_H__
+
+#include "crypto/block.h"
+
+typedef struct QCryptoBlockDriver QCryptoBlockDriver;
+
+struct QCryptoBlock {
+    QCryptoBlockFormat format;
+
+    const QCryptoBlockDriver *driver;
+    void *opaque;
+
+    QCryptoCipher *cipher;
+    QCryptoIVGen *ivgen;
+    QCryptoHashAlgorithm kdfhash;
+    size_t niv;
+    uint64_t payload_offset; /* In 512 byte sectors */
+};
+
+struct QCryptoBlockDriver {
+    int (*open)(QCryptoBlock *block,
+                QCryptoBlockOpenOptions *options,
+                QCryptoBlockReadFunc readfunc,
+                void *opaque,
+                unsigned int flags,
+                Error **errp);
+
+    int (*create)(QCryptoBlock *block,
+                  QCryptoBlockCreateOptions *options,
+                  QCryptoBlockInitFunc initfunc,
+                  QCryptoBlockWriteFunc writefunc,
+                  void *opaque,
+                  Error **errp);
+
+    void (*cleanup)(QCryptoBlock *block);
+
+    int (*encrypt)(QCryptoBlock *block,
+                   uint64_t startsector,
+                   uint8_t *buf,
+                   size_t len,
+                   Error **errp);
+    int (*decrypt)(QCryptoBlock *block,
+                   uint64_t startsector,
+                   uint8_t *buf,
+                   size_t len,
+                   Error **errp);
+
+    gboolean (*has_format)(const uint8_t *buf,
+                           size_t buflen);
+};
+
+
+int qcrypto_block_decrypt_helper(QCryptoCipher *cipher,
+                                 size_t niv,
+                                 QCryptoIVGen *ivgen,
+                                 uint64_t startsector,
+                                 uint8_t *buf,
+                                 size_t len,
+                                 Error **errp);
+
+int qcrypto_block_encrypt_helper(QCryptoCipher *cipher,
+                                 size_t niv,
+                                 QCryptoIVGen *ivgen,
+                                 uint64_t startsector,
+                                 uint8_t *buf,
+                                 size_t len,
+                                 Error **errp);
+
+#endif /* QCRYPTO_BLOCK_PRIV_H__ */
diff --git a/include/crypto/block.h b/include/crypto/block.h
new file mode 100644
index 0000000..8b49b1e
--- /dev/null
+++ b/include/crypto/block.h
@@ -0,0 +1,233 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_BLOCK_H__
+#define QCRYPTO_BLOCK_H__
+
+#include "crypto/cipher.h"
+#include "crypto/ivgen.h"
+
+typedef struct QCryptoBlock QCryptoBlock;
+
+/* See also QCryptoBlockFormat, QCryptoBlockCreateOptions
+ * and QCryptoBlockOpenOptions in qapi/crypto.json */
+
+typedef ssize_t (*QCryptoBlockReadFunc)(QCryptoBlock *block,
+                                        size_t offset,
+                                        uint8_t *buf,
+                                        size_t buflen,
+                                        Error **errp,
+                                        void *opaque);
+
+typedef ssize_t (*QCryptoBlockInitFunc)(QCryptoBlock *block,
+                                        size_t headerlen,
+                                        Error **errp,
+                                        void *opaque);
+
+typedef ssize_t (*QCryptoBlockWriteFunc)(QCryptoBlock *block,
+                                         size_t offset,
+                                         const uint8_t *buf,
+                                         size_t buflen,
+                                         Error **errp,
+                                         void *opaque);
+
+/**
+ * qcrypto_block_has_format:
+ * @format: the encryption format
+ * @buf: the data from head of the volume
+ * @len: the length of @buf in bytes
+ *
+ * Given @len bytes of data from the head of a storage volume
+ * in @buf, probe to determine if the volume has the encryption
+ * format specified in @format.
+ *
+ * Returns: true if the data in @buf matches @format
+ */
+gboolean qcrypto_block_has_format(QCryptoBlockFormat format,
+                                  const uint8_t *buf,
+                                  size_t buflen);
+
+typedef enum {
+    QCRYPTO_BLOCK_OPEN_NO_IO = (1 << 0),
+} QCryptoBlockOpenFlags;
+
+/**
+ * qcrypto_block_open:
+ * @options: the encryption options
+ * @readfunc: callback for reading data from the volume
+ * @opaque: data to pass to @readfunc
+ * @flags: bitmask of QCryptoBlockOpenFlags values
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Create a new block encryption object for an existing
+ * storage volume encrypted with format identified by
+ * the parameters in @options.
+ *
+ * This will use @readfunc to initialize the encryption
+ * context based on the volume header(s), extracting the
+ * master key(s) as required.
+ *
+ * If @flags contains QCRYPTO_BLOCK_OPEN_NO_IO then
+ * the open process will be optimized to skip any parts
+ * that are only required to perform I/O. In particular
+ * this would usually avoid the need to decrypt any
+ * master keys. The only thing that can be done with
+ * the resulting QCryptoBlock object would be to query
+ * metadata such as the payload offset. There will be
+ * no cipher or ivgen objects available.
+ *
+ * If any part of initializing the encryption context
+ * fails an error will be returned. This could be due
+ * to the volume being in the wrong format, an cipher
+ * or IV generator algorithm that is not supoported,
+ * or incorrect passphrases.
+ *
+ * Returns: a block encryption format, or NULL on error
+ */
+QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
+                                 QCryptoBlockReadFunc readfunc,
+                                 void *opaque,
+                                 unsigned int flags,
+                                 Error **errp);
+
+/**
+ * qcrypto_block_create:
+ * @format: the encryption format
+ * @initfunc: callback for initializing volume header
+ * @writefunc: callback for writing data to the volume header
+ * @opaque: data to pass to @initfunc & @writefunc
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Create a new block encryption object for initializing
+ * a storage volume to be encrypted with format identified
+ * by the parameters in @options.
+ *
+ * This method will allocate space for a new volume header
+ * using @initfunc and then write header data using @writefunc,
+ * generating new master keys, etc as required. Any existing
+ * data present on the volume will be irrevokably destroyed.
+ *
+ * If any part of initializing the encryption context
+ * fails an error will be returned. This could be due
+ * to the volume being in the wrong format, an cipher
+ * or IV generator algorithm that is not supoported,
+ * or incorrect passphrases.
+ *
+ * Returns: a block encryption format, or NULL on error
+ */
+QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
+                                   QCryptoBlockInitFunc initfunc,
+                                   QCryptoBlockWriteFunc writefunc,
+                                   void *opaque,
+                                   Error **errp);
+
+/**
+ * @qcrypto_block_decrypt:
+ * @block: the block encryption object
+ * @startsector: the sector from which @buf was read
+ * @buf: the buffer to decrypt
+ * @len: the length of @buf in bytes
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Decrypt @len bytes of cipher text in @buf, writing
+ * plain text back into @buf
+ *
+ * Returns 0 on success, -1 on failure
+ */
+int qcrypto_block_decrypt(QCryptoBlock *block,
+                          uint64_t startsector,
+                          uint8_t *buf,
+                          size_t len,
+                          Error **errp);
+
+/**
+ * @qcrypto_block_encrypt:
+ * @block: the block encryption object
+ * @startsector: the sector to which @buf will be written
+ * @buf: the buffer to decrypt
+ * @len: the length of @buf in bytes
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Encrypt @len bytes of plain text in @buf, writing
+ * cipher text back into @buf
+ *
+ * Returns 0 on success, -1 on failure
+ */
+int qcrypto_block_encrypt(QCryptoBlock *block,
+                          uint64_t startsector,
+                          uint8_t *buf,
+                          size_t len,
+                          Error **errp);
+
+/**
+ * qcrypto_block_get_cipher:
+ * @block: the block encryption object
+ *
+ * Get the cipher to use for payload encryption
+ *
+ * Returns: the cipher object
+ */
+QCryptoCipher *qcrypto_block_get_cipher(QCryptoBlock *block);
+
+/**
+ * qcrypto_block_get_ivgen:
+ * @block: the block encryption object
+ *
+ * Get the initialization vector generator to use for
+ * payload encryption
+ *
+ * Returns: the IV generator object
+ */
+QCryptoIVGen *qcrypto_block_get_ivgen(QCryptoBlock *block);
+
+
+/**
+ * qcrypto_block_get_kdf_hash:
+ * @block: the block encryption object
+ *
+ * Get the hash algorithm used with the key derivation
+ * function
+ *
+ * Returns: the hash algorithm
+ */
+QCryptoHashAlgorithm qcrypto_block_get_kdf_hash(QCryptoBlock *block);
+
+/**
+ * qcrypto_block_get_payload_offset:
+ * @block: the block encryption object
+ *
+ * Get the offset to the payload indicated by the
+ * encryption header. The offset is measured in
+ * 512 byte sectors
+ *
+ * Returns: the payload offset in sectors.
+ */
+uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block);
+
+/**
+ * qcrypto_block_free:
+ * @block: the block encryption object
+ *
+ * Release all resources associated with the encryption
+ * object
+ */
+void qcrypto_block_free(QCryptoBlock *block);
+
+#endif /* QCRYPTO_BLOCK_H__ */
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 48946b0..b4b0a95 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -94,3 +94,70 @@
 { 'enum': 'QCryptoIVGenAlgorithm',
   'prefix': 'QCRYPTO_IVGEN_ALG',
   'data': ['plain', 'plain64', 'essiv']}
+
+##
+# QCryptoBlockFormat:
+#
+# The supported full disk encryption formats
+#
+# @qcow: QCow/QCow2 built-in AES-CBC encryption. Use only
+#        for liberating data from old images.
+#
+# Since: 2.6
+##
+{ 'enum': 'QCryptoBlockFormat',
+#  'prefix': 'QCRYPTO_BLOCK_FORMAT',
+  'data': ['qcow']}
+
+##
+# QCryptoBlockOptionsBase:
+#
+# The common options that apply to all full disk
+# encryption formats
+#
+# @format: the encryption format
+#
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsBase',
+  'data': { 'format': 'QCryptoBlockFormat' }}
+
+##
+# QCryptoBlockOptionsQCow:
+#
+# The options that apply to QCow/QCow2 AES-CBC encryption format
+#
+# @key-secret: #optional the ID of a QCryptoSecret object providing the
+#              decryption key
+#
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsQCow',
+  'data': { '*key-secret': 'str' }}
+
+##
+# QCryptoBlockOpenOptions:
+#
+# The options that are available for all encryption formats
+# when opening an existing volume
+#
+# Since: 2.6
+##
+{ 'union': 'QCryptoBlockOpenOptions',
+  'base': 'QCryptoBlockOptionsBase',
+  'discriminator': 'format',
+  'data': { 'qcow': 'QCryptoBlockOptionsQCow' } }
+
+
+##
+# QCryptoBlockCreateOptions:
+#
+# The options that are available for all encryption formats
+# when initializing a new volume
+#
+# Since: 2.6
+##
+{ 'union': 'QCryptoBlockCreateOptions',
+  'base': 'QCryptoBlockOptionsBase',
+  'discriminator': 'format',
+  'data': { 'qcow': 'QCryptoBlockOptionsQCow' } }
diff --git a/tests/.gitignore b/tests/.gitignore
index 5b97e8c..179c6ba 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,6 +13,7 @@ test-bitops
 test-blockjob-txn
 test-coroutine
 test-crypto-afsplit
+test-crypto-block
 test-crypto-cipher
 test-crypto-hash
 test-crypto-ivgen
diff --git a/tests/Makefile b/tests/Makefile
index 80847b9..20de4e4 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -95,6 +95,7 @@ check-unit-y += tests/test-base64$(EXESUF)
 check-unit-y += tests/test-crypto-pbkdf$(EXESUF)
 check-unit-y += tests/test-crypto-ivgen$(EXESUF)
 check-unit-y += tests/test-crypto-afsplit$(EXESUF)
+check-unit-y += tests/test-crypto-block$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -501,6 +502,7 @@ tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
 tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y)
 tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y)
 tests/test-crypto-afsplit$(EXESUF): tests/test-crypto-afsplit.o $(test-crypto-obj-y)
+tests/test-crypto-block$(EXESUF): tests/test-crypto-block.o $(test-crypto-obj-y)
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-block.c b/tests/test-crypto-block.c
new file mode 100644
index 0000000..54216e9
--- /dev/null
+++ b/tests/test-crypto-block.c
@@ -0,0 +1,239 @@
+/*
+ * QEMU Crypto block encryption
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "crypto/init.h"
+#include "crypto/block.h"
+#include "qemu/buffer.h"
+#include "crypto/secret.h"
+
+static QCryptoBlockOptionsQCow qcow_opts = {
+    .has_key_secret = true,
+    .key_secret = (char *)"sec0",
+};
+
+static QCryptoBlockCreateOptions qcow_create_opts = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_QCOW,
+    .u.qcow = &qcow_opts,
+};
+
+static QCryptoBlockOpenOptions qcow_open_opts = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_QCOW,
+    .u.qcow = &qcow_opts,
+};
+
+static struct QCryptoBlockTestData {
+    const char *path;
+    QCryptoBlockCreateOptions *create_opts;
+    QCryptoBlockOpenOptions *open_opts;
+
+    bool expect_header;
+
+    QCryptoCipherAlgorithm cipher_alg;
+    QCryptoCipherMode cipher_mode;
+    QCryptoHashAlgorithm hash_alg;
+
+    QCryptoIVGenAlgorithm ivgen_alg;
+    QCryptoCipherAlgorithm ivgen_hash;
+} test_data[] = {
+    {
+        .path = "/crypto/block/qcow",
+        .create_opts = &qcow_create_opts,
+        .open_opts = &qcow_open_opts,
+
+        .expect_header = false,
+
+        .cipher_alg = QCRYPTO_CIPHER_ALG_AES_128,
+        .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+
+        .ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
+    },
+};
+
+
+static ssize_t test_block_read_func(QCryptoBlock *block,
+                                    size_t offset,
+                                    uint8_t *buf,
+                                    size_t buflen,
+                                    Error **errp,
+                                    void *opaque)
+{
+    Buffer *header = opaque;
+
+    g_assert_cmpint(offset + buflen, <=, header->capacity);
+
+    memcpy(buf, header->buffer + offset, buflen);
+
+    return buflen;
+}
+
+
+static ssize_t test_block_init_func(QCryptoBlock *block,
+                                    size_t headerlen,
+                                    Error **errp,
+                                    void *opaque)
+{
+    Buffer *header = opaque;
+
+    g_assert_cmpint(header->capacity, ==, 0);
+
+    buffer_reserve(header, headerlen);
+
+    return headerlen;
+}
+
+
+static ssize_t test_block_write_func(QCryptoBlock *block,
+                                     size_t offset,
+                                     const uint8_t *buf,
+                                     size_t buflen,
+                                     Error **errp,
+                                     void *opaque)
+{
+    Buffer *header = opaque;
+
+    g_assert_cmpint(buflen + offset, <=, header->capacity);
+
+    memcpy(header->buffer + offset, buf, buflen);
+    header->offset = offset + buflen;
+
+    return buflen;
+}
+
+
+static Object *test_block_secret(void)
+{
+    return object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "123456",
+        NULL);
+}
+
+static void test_block_assert_setup(const struct QCryptoBlockTestData *data,
+                                    QCryptoBlock *blk)
+{
+    QCryptoIVGen *ivgen;
+    QCryptoCipher *cipher;
+
+    ivgen = qcrypto_block_get_ivgen(blk);
+    cipher = qcrypto_block_get_cipher(blk);
+
+    g_assert(ivgen);
+    g_assert(cipher);
+
+    g_assert_cmpint(data->cipher_alg, ==, cipher->alg);
+    g_assert_cmpint(data->cipher_mode, ==, cipher->mode);
+    g_assert_cmpint(data->hash_alg, ==,
+                    qcrypto_block_get_kdf_hash(blk));
+
+    g_assert_cmpint(data->ivgen_alg, ==,
+                    qcrypto_ivgen_get_algorithm(ivgen));
+    g_assert_cmpint(data->ivgen_hash, ==,
+                    qcrypto_ivgen_get_hash(ivgen));
+}
+
+
+static void test_block(gconstpointer opaque)
+{
+    const struct QCryptoBlockTestData *data = opaque;
+    QCryptoBlock *blk;
+    Buffer header;
+    Object *sec = test_block_secret();
+
+    memset(&header, 0, sizeof(header));
+    buffer_init(&header, "header");
+
+    blk = qcrypto_block_create(data->create_opts,
+                               test_block_init_func,
+                               test_block_write_func,
+                               &header,
+                               &error_abort);
+    g_assert(blk);
+
+    if (data->expect_header) {
+        g_assert_cmpint(header.capacity, >, 0);
+    } else {
+        g_assert_cmpint(header.capacity, ==, 0);
+    }
+
+    test_block_assert_setup(data, blk);
+
+    qcrypto_block_free(blk);
+    object_unparent(sec);
+
+    /* Ensure we can't open without the secret */
+    blk = qcrypto_block_open(data->open_opts,
+                             test_block_read_func,
+                             &header,
+                             0,
+                             NULL);
+    g_assert(blk == NULL);
+
+    /* Ensure we can't open without the secret, unless NO_IO */
+    blk = qcrypto_block_open(data->open_opts,
+                             test_block_read_func,
+                             &header,
+                             QCRYPTO_BLOCK_OPEN_NO_IO,
+                             &error_abort);
+
+    g_assert(qcrypto_block_get_cipher(blk) == NULL);
+    g_assert(qcrypto_block_get_ivgen(blk) == NULL);
+
+    qcrypto_block_free(blk);
+
+
+    /* Now open for real with secret */
+    sec = test_block_secret();
+    blk = qcrypto_block_open(data->open_opts,
+                             test_block_read_func,
+                             &header,
+                             0,
+                             &error_abort);
+    g_assert(blk);
+
+    test_block_assert_setup(data, blk);
+
+    qcrypto_block_free(blk);
+
+    object_unparent(sec);
+
+    buffer_free(&header);
+}
+
+
+int main(int argc, char **argv)
+{
+    gsize i;
+
+    module_call_init(MODULE_INIT_QOM);
+    g_test_init(&argc, &argv, NULL);
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
+        g_test_add_data_func(test_data[i].path, &test_data[i], test_block);
+    }
+
+    return g_test_run();
+}
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (5 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-05 17:38   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed Daniel P. Berrange
                   ` (9 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Provide a block encryption implementation that follows the
LUKS/dm-crypt specification.

This supports all combinations of hash, cipher algorithm,
cipher mode and iv generator that are implemented by the
current crypto layer.

The notable missing feature is support for the 'xts'
cipher mode, which is commonly used for disk encryption
instead of 'cbc'. This is because it is not provided by
either nettle or libgcrypt. A suitable implementation
will be identified & integrated later.

There is support for opening existing volumes formatted
by dm-crypt, and for formatting new volumes. In the latter
case it will only use key slot 0.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs      |    1 +
 crypto/block-luks.c       | 1105 +++++++++++++++++++++++++++++++++++++++++++++
 crypto/block-luks.h       |   28 ++
 crypto/block.c            |    2 +
 qapi/crypto.json          |   43 +-
 tests/test-crypto-block.c |  104 +++++
 6 files changed, 1280 insertions(+), 3 deletions(-)
 create mode 100644 crypto/block-luks.c
 create mode 100644 crypto/block-luks.h

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 25ca0c7..3299923 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -20,6 +20,7 @@ crypto-obj-y += ivgen-plain64.o
 crypto-obj-y += afsplit.o
 crypto-obj-y += block.o
 crypto-obj-y += block-qcow.o
+crypto-obj-y += block-luks.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/block-luks.c b/crypto/block-luks.c
new file mode 100644
index 0000000..47630ee
--- /dev/null
+++ b/crypto/block-luks.c
@@ -0,0 +1,1105 @@
+/*
+ * QEMU Crypto block device encryption LUKS format
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config-host.h"
+
+#include "crypto/block-luks.h"
+
+#include "crypto/hash.h"
+#include "crypto/afsplit.h"
+#include "crypto/pbkdf.h"
+#include "crypto/secret.h"
+#include "crypto/random.h"
+
+#ifdef CONFIG_UUID
+#include <uuid/uuid.h>
+#endif
+
+#include "qemu/coroutine.h"
+
+/*
+ * Reference for format is
+ *
+ * docs/on-disk-format.pdf
+ *
+ * In 'cryptsetup' package source code
+ *
+ */
+
+typedef struct QCryptoBlockLUKS QCryptoBlockLUKS;
+typedef struct QCryptoBlockLUKSHeader QCryptoBlockLUKSHeader;
+typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
+
+#define QCRYPTO_BLOCK_LUKS_VERSION 1
+
+#define QCRYPTO_BLOCK_LUKS_MAGIC_LEN 6
+#define QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN 32
+#define QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN 32
+#define QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN 32
+#define QCRYPTO_BLOCK_LUKS_DIGEST_LEN 20
+#define QCRYPTO_BLOCK_LUKS_SALT_LEN 32
+#define QCRYPTO_BLOCK_LUKS_UUID_LEN 40
+#define QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS 8
+#define QCRYPTO_BLOCK_LUKS_STRIPES 4000
+#define QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS 1000
+#define QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS 1000
+#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET 4096
+
+#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED 0x0000DEAD
+#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED 0x00AC71F3
+
+static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE
+};
+
+typedef struct QCryptoBlockLUKSNameMap QCryptoBlockLUKSNameMap;
+struct QCryptoBlockLUKSNameMap {
+    const char *name;
+    int id;
+};
+
+typedef struct QCryptoBlockLUKSCipherSizeMap QCryptoBlockLUKSCipherSizeMap;
+struct QCryptoBlockLUKSCipherSizeMap {
+    uint32_t key_bytes;
+    int id;
+};
+typedef struct QCryptoBlockLUKSCipherNameMap QCryptoBlockLUKSCipherNameMap;
+struct QCryptoBlockLUKSCipherNameMap {
+    const char *name;
+    const QCryptoBlockLUKSCipherSizeMap *sizes;
+};
+
+
+static const QCryptoBlockLUKSCipherSizeMap
+qcrypto_block_luks_cipher_size_map_aes[] = {
+    { 16, QCRYPTO_CIPHER_ALG_AES_128 },
+    { 24, QCRYPTO_CIPHER_ALG_AES_192 },
+    { 32, QCRYPTO_CIPHER_ALG_AES_256 },
+    { 0, 0 },
+};
+
+static const QCryptoBlockLUKSCipherNameMap
+qcrypto_block_luks_cipher_name_map[] = {
+    { "aes", qcrypto_block_luks_cipher_size_map_aes, },
+};
+
+
+struct QCryptoBlockLUKSKeySlot {
+    /* state of keyslot, enabled/disable */
+    uint32_t active;
+    /* iterations for PBKDF2 */
+    uint32_t iterations;
+    /* salt for PBKDF2 */
+    uint8_t salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
+    /* start sector of key material */
+    uint32_t key_offset;
+    /* number of anti-forensic stripes */
+    uint32_t stripes;
+} QEMU_PACKED;
+
+struct QCryptoBlockLUKSHeader {
+    /* 'L', 'U', 'K', 'S', '0xBA', '0xBE' */
+    char magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN];
+
+    /* LUKS version, currently 1 */
+    uint16_t version;
+
+    /* cipher name specification (aes, etc */
+    char cipher_name[QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN];
+
+    /* cipher mode specification () */
+    char cipher_mode[QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN];
+
+    /* hash specication () */
+    char hash_spec[QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN];
+
+    /* start offset of the volume data (in sectors) */
+    uint32_t payload_offset;
+
+    /* Number of key bytes */
+    uint32_t key_bytes;
+
+    /* master key checksum after PBKDF2 */
+    uint8_t master_key_digest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
+
+    /* salt for master key PBKDF2 */
+    uint8_t master_key_salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
+
+    /* iterations for master key PBKDF2 */
+    uint32_t master_key_iterations;
+
+    /* UUID of the partition */
+    uint8_t uuid[QCRYPTO_BLOCK_LUKS_UUID_LEN];
+
+    /* key slots */
+    QCryptoBlockLUKSKeySlot key_slots[QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS];
+} QEMU_PACKED;
+
+struct QCryptoBlockLUKS {
+    QCryptoBlockLUKSHeader header;
+};
+
+
+static int qcrypto_block_luks_cipher_name_lookup(const char *name,
+                                                 uint32_t key_bytes,
+                                                 Error **errp)
+{
+    const QCryptoBlockLUKSCipherNameMap *map =
+        qcrypto_block_luks_cipher_name_map;
+    size_t maplen = G_N_ELEMENTS(qcrypto_block_luks_cipher_name_map);
+    size_t i, j;
+    for (i = 0; i < maplen; i++) {
+        if (!g_str_equal(map[i].name, name)) {
+            continue;
+        }
+        for (j = 0; j < map[i].sizes[j].key_bytes; j++) {
+            if (map[i].sizes[j].key_bytes == key_bytes) {
+                return map[i].sizes[j].id;
+            }
+        }
+    }
+
+    error_setg(errp, "Algorithm %s with key size %d bytes not supported",
+               name, key_bytes);
+    return 0;
+}
+
+/* XXX doesn't QEMU already have a string -> enum val convertor ? */
+static int qcrypto_block_luks_name_lookup(const char *name,
+                                          const char *const *map,
+                                          size_t maplen,
+                                          const char *type,
+                                          Error **errp)
+{
+    size_t i;
+    for (i = 0; i < maplen; i++) {
+        if (g_str_equal(map[i], name)) {
+            return i;
+        }
+    }
+
+    error_setg(errp, "%s %s not supported", type, name);
+    return 0;
+}
+
+#define qcrypto_block_luks_cipher_mode_lookup(name, errp)               \
+    qcrypto_block_luks_name_lookup(name,                                \
+                                   QCryptoCipherMode_lookup,            \
+                                   QCRYPTO_CIPHER_MODE__MAX,            \
+                                   "Cipher mode",                       \
+                                   errp)
+
+#define qcrypto_block_luks_hash_name_lookup(name, errp)                 \
+    qcrypto_block_luks_name_lookup(name,                                \
+                                   QCryptoHashAlgorithm_lookup,         \
+                                   QCRYPTO_HASH_ALG__MAX,               \
+                                   "Hash algorithm",                    \
+                                   errp)
+
+#define qcrypto_block_luks_ivgen_name_lookup(name, errp)                \
+    qcrypto_block_luks_name_lookup(name,                                \
+                                   QCryptoIVGenAlgorithm_lookup,        \
+                                   QCRYPTO_IVGEN_ALG__MAX,              \
+                                   "IV generator",                      \
+                                   errp)
+
+
+static gboolean
+qcrypto_block_luks_has_format(const uint8_t *buf,
+                              size_t buf_size)
+{
+    const QCryptoBlockLUKSHeader *luks_header = (const void *)buf;
+
+    if (buf_size >= offsetof(QCryptoBlockLUKSHeader, cipher_name) &&
+        memcmp(luks_header->magic, qcrypto_block_luks_magic,
+               QCRYPTO_BLOCK_LUKS_MAGIC_LEN) == 0 &&
+        be16_to_cpu(luks_header->version) == QCRYPTO_BLOCK_LUKS_VERSION) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+
+/*
+ * Given a key slot, and user password, this will attempt to unlock
+ * the master encryption key from the key slot
+ */
+static int
+qcrypto_block_luks_load_key(QCryptoBlock *block,
+                            QCryptoBlockLUKSKeySlot *slot,
+                            const char *password,
+                            QCryptoCipherAlgorithm cipheralg,
+                            QCryptoCipherMode ciphermode,
+                            QCryptoHashAlgorithm hash,
+                            QCryptoIVGenAlgorithm ivalg,
+                            QCryptoHashAlgorithm ivhash,
+                            uint8_t *masterkey,
+                            size_t masterkeylen,
+                            QCryptoBlockReadFunc readfunc,
+                            void *opaque,
+                            Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    uint8_t *splitkey;
+    size_t splitkeylen;
+    uint8_t *possiblekey;
+    int ret = -1;
+    ssize_t rv;
+    QCryptoCipher *cipher = NULL;
+    uint8_t keydigest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
+    QCryptoIVGen *ivgen = NULL;
+    size_t niv;
+
+    if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) {
+        return 0;
+    }
+
+    splitkeylen = masterkeylen * slot->stripes;
+    splitkey = g_new0(uint8_t, splitkeylen);
+    possiblekey = g_new0(uint8_t, masterkeylen);
+
+    /*
+     * The user password is used to generate a (possible)
+     * decryption key. This may or may not successfully
+     * decrypt the master key - we just blindly assume
+     * the key is correct and validate the results of
+     * decryption later.
+     */
+    if (qcrypto_pbkdf2(hash,
+                       (const uint8_t *)password, strlen(password),
+                       slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                       slot->iterations,
+                       possiblekey, masterkeylen,
+                       errp) < 0) {
+        goto cleanup;
+    }
+
+    /*
+     * We need to read the master key material from the
+     * LUKS key material header. What we're reading is
+     * not the raw master key, but rather the data after
+     * it has been passed through AFSplit and the result
+     * then encrypted.
+     */
+    rv = readfunc(block,
+                  slot->key_offset * 512ll,
+                  splitkey, splitkeylen,
+                  errp,
+                  opaque);
+    if (rv < 0) {
+        goto cleanup;
+    }
+
+
+    /* Setup the cipher/ivgen that we'll use to try to decrypt
+     * the split master key material */
+    cipher = qcrypto_cipher_new(cipheralg, ciphermode,
+                                possiblekey, masterkeylen,
+                                errp);
+    if (!cipher) {
+        goto cleanup;
+    }
+
+    niv = qcrypto_cipher_get_iv_len(cipheralg,
+                                    ciphermode);
+    ivgen = qcrypto_ivgen_new(ivalg,
+                              cipheralg,
+                              ivhash,
+                              possiblekey, masterkeylen,
+                              errp);
+    if (!ivgen) {
+        goto cleanup;
+    }
+
+
+    /*
+     * The master key needs to be decrypted in the same
+     * way that the block device payload will be decrypted
+     * later. In particular we'll be using the IV generator
+     * to reset the encryption cipher every time the master
+     * key crosses a sector boundary.
+     */
+    if (qcrypto_block_decrypt_helper(cipher,
+                                     niv,
+                                     ivgen,
+                                     0,
+                                     splitkey,
+                                     splitkeylen,
+                                     errp) < 0) {
+        goto cleanup;
+    }
+
+    /*
+     * Now we're decrypted the split master key, join
+     * it back together to get the actual master key.
+     */
+    if (qcrypto_afsplit_decode(hash,
+                               masterkeylen,
+                               slot->stripes,
+                               splitkey,
+                               masterkey,
+                               errp) < 0) {
+        goto cleanup;
+    }
+
+
+    /*
+     * We still don't know that the masterkey we got is valid,
+     * because we just blindly assumed the user's password
+     * was correct. This is where we now verify it. We are
+     * creating a hash of the master key using PBKDF and
+     * then comparing that to the hash stored in the key slot
+     * header
+     */
+    if (qcrypto_pbkdf2(hash,
+                       masterkey, masterkeylen,
+                       luks->header.master_key_salt,
+                       QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                       luks->header.master_key_iterations,
+                       keydigest, G_N_ELEMENTS(keydigest),
+                       errp) < 0) {
+        goto cleanup;
+    }
+
+    if (memcmp(keydigest, luks->header.master_key_digest,
+               QCRYPTO_BLOCK_LUKS_DIGEST_LEN) == 0) {
+        /* Success, we got the right master key */
+        ret = 1;
+        goto cleanup;
+    }
+
+    /* Fail, user's password was not valid for this key slot,
+     * tell caller to try another slot */
+    ret = 0;
+
+ cleanup:
+    qcrypto_ivgen_free(ivgen);
+    qcrypto_cipher_free(cipher);
+    g_free(splitkey);
+    g_free(possiblekey);
+    return ret;
+}
+
+
+/*
+ * Given a user password, this will iterate over all key
+ * slots and try to unlock each active key slot using the
+ * password until it successfully obtains a master key.
+ */
+static int
+qcrypto_block_luks_find_key(QCryptoBlock *block,
+                            const char *password,
+                            QCryptoCipherAlgorithm cipheralg,
+                            QCryptoCipherMode ciphermode,
+                            QCryptoHashAlgorithm hash,
+                            QCryptoIVGenAlgorithm ivalg,
+                            QCryptoHashAlgorithm ivhash,
+                            uint8_t **masterkey,
+                            size_t *masterkeylen,
+                            QCryptoBlockReadFunc readfunc,
+                            void *opaque,
+                            Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    size_t i;
+    int rv;
+
+    *masterkey = g_new0(uint8_t, luks->header.key_bytes);
+    *masterkeylen = luks->header.key_bytes;
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        rv = qcrypto_block_luks_load_key(block,
+                                         &luks->header.key_slots[i],
+                                         password,
+                                         cipheralg,
+                                         ciphermode,
+                                         hash,
+                                         ivalg,
+                                         ivhash,
+                                         *masterkey,
+                                         *masterkeylen,
+                                         readfunc,
+                                         opaque,
+                                         errp);
+        if (rv < 0) {
+            goto error;
+        }
+        if (rv == 1) {
+            return 0;
+        }
+    }
+
+    error_setg(errp, "Invalid password, cannot unlock any keyslot");
+
+ error:
+    g_free(*masterkey);
+    *masterkey = NULL;
+    *masterkeylen = 0;
+    return -1;
+}
+
+
+static int
+qcrypto_block_luks_open(QCryptoBlock *block,
+                        QCryptoBlockOpenOptions *options,
+                        QCryptoBlockReadFunc readfunc,
+                        void *opaque,
+                        unsigned int flags,
+                        Error **errp)
+{
+    QCryptoBlockLUKS *luks;
+    Error *local_err = NULL;
+    int ret = 0;
+    size_t i;
+    ssize_t rv;
+    uint8_t *masterkey = NULL;
+    size_t masterkeylen;
+    char *ivgen_name, *ivhash_name;
+    QCryptoCipherMode ciphermode;
+    QCryptoCipherAlgorithm cipheralg;
+    QCryptoIVGenAlgorithm ivalg;
+    QCryptoHashAlgorithm hash;
+    QCryptoHashAlgorithm ivhash;
+    char *password = NULL;
+
+    if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) {
+        if (!options->u.luks->key_secret) {
+            error_setg(errp, "Parameter 'key-secret' is required for cipher");
+            return -1;
+        }
+        password = qcrypto_secret_lookup_as_utf8(
+            options->u.luks->key_secret, errp);
+        if (!password) {
+            return -1;
+        }
+    }
+
+    luks = g_new0(QCryptoBlockLUKS, 1);
+    block->opaque = luks;
+
+    /* Read the entire LUKS header, minus the key material from
+     * the underling device */
+    rv = readfunc(block, 0,
+                  (uint8_t *)&luks->header,
+                  sizeof(luks->header),
+                  errp,
+                  opaque);
+    if (rv < 0) {
+        ret = rv;
+        goto fail;
+    }
+
+    /* The header is always stored in big-endian format, so
+     * convert everything to native */
+    be16_to_cpus(&luks->header.version);
+    be32_to_cpus(&luks->header.payload_offset);
+    be32_to_cpus(&luks->header.key_bytes);
+    be32_to_cpus(&luks->header.master_key_iterations);
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        be32_to_cpus(&luks->header.key_slots[i].active);
+        be32_to_cpus(&luks->header.key_slots[i].iterations);
+        be32_to_cpus(&luks->header.key_slots[i].key_offset);
+        be32_to_cpus(&luks->header.key_slots[i].stripes);
+    }
+
+    if (memcmp(luks->header.magic, qcrypto_block_luks_magic,
+               QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) {
+        error_setg(errp, "Volume is not in LUKS format");
+        ret = -EINVAL;
+        goto fail;
+    }
+    if (luks->header.version != QCRYPTO_BLOCK_LUKS_VERSION) {
+        error_setg(errp, "LUKS version %" PRIu32 " is not supported",
+                   luks->header.version);
+        ret = -ENOTSUP;
+        goto fail;
+    }
+
+    /*
+     * The cipher_mode header contains a string that we have
+     * to further parse of the format
+     *
+     *    <cipher-mode>-<iv-generator>[:<iv-hash>]
+     *
+     * eg  cbc-essiv:sha256, cbc-plain64
+     */
+    ivgen_name = strchr(luks->header.cipher_mode, '-');
+    if (!ivgen_name) {
+        ret = -EINVAL;
+        error_setg(errp, "Unexpected cipher mode string format %s",
+                   luks->header.cipher_mode);
+        goto fail;
+    }
+    *ivgen_name = '\0';
+    ivgen_name++;
+
+    ivhash_name = strchr(ivgen_name, ':');
+    if (!ivhash_name) {
+        ivhash = 0;
+    } else {
+        *ivhash_name = '\0';
+        ivhash_name++;
+
+        ivhash = qcrypto_block_luks_hash_name_lookup(ivhash_name,
+                                                     &local_err);
+        if (local_err) {
+            ret = -ENOTSUP;
+            error_propagate(errp, local_err);
+            goto fail;
+        }
+    }
+
+    ciphermode = qcrypto_block_luks_cipher_mode_lookup(luks->header.cipher_mode,
+                                                       &local_err);
+    if (local_err) {
+        ret = -ENOTSUP;
+        error_propagate(errp, local_err);
+        goto fail;
+    }
+
+    cipheralg = qcrypto_block_luks_cipher_name_lookup(luks->header.cipher_name,
+                                                      luks->header.key_bytes,
+                                                      &local_err);
+    if (local_err) {
+        ret = -ENOTSUP;
+        error_propagate(errp, local_err);
+        goto fail;
+    }
+
+    hash = qcrypto_block_luks_hash_name_lookup(luks->header.hash_spec,
+                                               &local_err);
+    if (local_err) {
+        ret = -ENOTSUP;
+        error_propagate(errp, local_err);
+        goto fail;
+    }
+
+    ivalg = qcrypto_block_luks_ivgen_name_lookup(ivgen_name,
+                                                 &local_err);
+    if (local_err) {
+        ret = -ENOTSUP;
+        error_propagate(errp, local_err);
+        goto fail;
+    }
+
+
+    if (!(flags & QCRYPTO_BLOCK_OPEN_NO_IO)) {
+        /* Try to find which key slot our password is valid for
+         * and unlock the master key from that slot.
+         */
+        if (qcrypto_block_luks_find_key(block,
+                                        password,
+                                        cipheralg, ciphermode,
+                                        hash,
+                                        ivalg,
+                                        ivhash,
+                                        &masterkey, &masterkeylen,
+                                        readfunc, opaque,
+                                        errp) < 0) {
+            ret = -EACCES;
+            goto fail;
+        }
+
+        /* We have a valid master key now, so can setup the
+         * block device payload decryption objects
+         */
+        block->kdfhash = hash;
+        block->niv = qcrypto_cipher_get_iv_len(cipheralg,
+                                               ciphermode);
+        block->ivgen = qcrypto_ivgen_new(ivalg,
+                                         cipheralg,
+                                         ivhash,
+                                         masterkey, masterkeylen,
+                                         errp);
+        if (!block->ivgen) {
+            ret = -ENOTSUP;
+            goto fail;
+        }
+
+        block->cipher = qcrypto_cipher_new(cipheralg,
+                                           ciphermode,
+                                           masterkey, masterkeylen,
+                                           errp);
+        if (!block->cipher) {
+            ret = -ENOTSUP;
+            goto fail;
+        }
+    }
+
+    block->payload_offset = luks->header.payload_offset;
+
+    g_free(masterkey);
+    g_free(password);
+
+    return 0;
+
+ fail:
+    g_free(masterkey);
+    qcrypto_cipher_free(block->cipher);
+    qcrypto_ivgen_free(block->ivgen);
+    g_free(luks);
+    g_free(password);
+    return ret;
+}
+
+
+static int
+qcrypto_block_luks_create(QCryptoBlock *block,
+                          QCryptoBlockCreateOptions *options,
+                          QCryptoBlockInitFunc initfunc,
+                          QCryptoBlockWriteFunc writefunc,
+                          void *opaque,
+                          Error **errp)
+{
+    QCryptoBlockLUKS *luks;
+    QCryptoBlockCreateOptionsLUKS luks_opts;
+    Error *local_err = NULL;
+    uint8_t *masterkey = NULL;
+    uint8_t *slotkey = NULL;
+    uint8_t *splitkey = NULL;
+    size_t splitkeylen = NULL;
+    size_t i;
+    QCryptoCipher *cipher = NULL;
+    QCryptoIVGen *ivgen = NULL;
+    char *password;
+    const char *cipher_alg;
+    const char *cipher_mode;
+    const char *ivgen_alg;
+    const char *ivgen_hash_alg = NULL;
+    const char *hash_alg;
+    char *cipher_mode_spec = NULL;
+    uuid_t uuid;
+
+    memcpy(&luks_opts, options->u.luks, sizeof(luks_opts));
+    if (!luks_opts.has_cipher_alg) {
+        luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
+    }
+    if (!luks_opts.has_cipher_mode) {
+        /* XXX switch to XTS */
+        luks_opts.cipher_mode = QCRYPTO_CIPHER_MODE_CBC;
+    }
+    if (!luks_opts.has_ivgen_alg) {
+        luks_opts.ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV;
+        if (!luks_opts.has_ivgen_hash_alg) {
+            luks_opts.ivgen_hash_alg = QCRYPTO_HASH_ALG_SHA256;
+            luks_opts.has_ivgen_hash_alg = true;
+        }
+    }
+    if (!luks_opts.has_hash_alg) {
+        luks_opts.hash_alg = QCRYPTO_HASH_ALG_SHA256;
+    }
+
+    if (!options->u.luks->key_secret) {
+        error_setg(errp, "Parameter 'key-secret' is required for cipher");
+        return -1;
+    }
+    password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
+    if (!password) {
+        return -1;
+    }
+
+    luks = g_new0(QCryptoBlockLUKS, 1);
+    block->opaque = luks;
+
+    memcpy(luks->header.magic, qcrypto_block_luks_magic,
+           QCRYPTO_BLOCK_LUKS_MAGIC_LEN);
+
+    luks->header.version = QCRYPTO_BLOCK_LUKS_VERSION;
+    uuid_generate(uuid);
+    uuid_unparse(uuid, (char *)luks->header.uuid);
+
+    switch (luks_opts.cipher_alg) {
+    case QCRYPTO_CIPHER_ALG_AES_128:
+    case QCRYPTO_CIPHER_ALG_AES_192:
+    case QCRYPTO_CIPHER_ALG_AES_256:
+        cipher_alg = "aes";
+        break;
+    default:
+        error_setg(errp, "Cipher algorithm is not supported");
+        goto error;
+    }
+
+    cipher_mode = QCryptoCipherMode_lookup[luks_opts.cipher_mode];
+    ivgen_alg = QCryptoIVGenAlgorithm_lookup[luks_opts.ivgen_alg];
+    if (luks_opts.has_ivgen_hash_alg) {
+        ivgen_hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.ivgen_hash_alg];
+        cipher_mode_spec = g_strdup_printf("%s-%s:%s", cipher_mode, ivgen_alg,
+                                           ivgen_hash_alg);
+    } else {
+        cipher_mode_spec = g_strdup_printf("%s-%s", cipher_mode, ivgen_alg);
+    }
+    hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.hash_alg];
+
+
+    if (strlen(cipher_alg) >= QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN) {
+        error_setg(errp, "Cipher name '%s' is too long for LUKS header",
+                   cipher_alg);
+        goto error;
+    }
+    if (strlen(cipher_mode_spec) >= QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN) {
+        error_setg(errp, "Cipher mode '%s' is too long for LUKS header",
+                   cipher_mode_spec);
+        goto error;
+    }
+    if (strlen(hash_alg) >= QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN) {
+        error_setg(errp, "Hash name '%s' is too long for LUKS header",
+                   hash_alg);
+        goto error;
+    }
+
+    memcpy(luks->header.cipher_name, cipher_alg,
+           strlen(cipher_alg) + 1);
+    memcpy(luks->header.cipher_mode, cipher_mode_spec,
+           strlen(cipher_mode_spec) + 1);
+    memcpy(luks->header.hash_spec, hash_alg,
+           strlen(hash_alg) + 1);
+
+    luks->header.key_bytes = qcrypto_cipher_get_key_len(luks_opts.cipher_alg);
+
+    /* Generate the salt used for hashing the master key
+     * with PBKDF later
+     */
+    if (qcrypto_random_bytes(luks->header.master_key_salt,
+                             QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                             errp) < 0) {
+        goto error;
+    }
+
+    /* Generate random master key */
+    masterkey = g_new0(uint8_t, luks->header.key_bytes);
+    if (qcrypto_random_bytes(masterkey,
+                             luks->header.key_bytes, errp) < 0) {
+        goto error;
+    }
+
+
+    /* Setup the block device payload encryption objects */
+    block->cipher = qcrypto_cipher_new(luks_opts.cipher_alg,
+                                       luks_opts.cipher_mode,
+                                       masterkey, luks->header.key_bytes,
+                                       errp);
+    if (!block->cipher) {
+        goto error;
+    }
+
+    block->kdfhash = luks_opts.hash_alg;
+    block->niv = qcrypto_cipher_get_iv_len(luks_opts.cipher_alg,
+                                           luks_opts.cipher_mode);
+    block->ivgen = qcrypto_ivgen_new(luks_opts.ivgen_alg,
+                                     luks_opts.cipher_alg,
+                                     luks_opts.ivgen_hash_alg,
+                                     masterkey, luks->header.key_bytes,
+                                     errp);
+
+    if (!block->ivgen) {
+        goto error;
+    }
+
+
+    /* Determine how many iterations we need to hash the master
+     * key with in order to have 1 second of compute time used
+     */
+    luks->header.master_key_iterations =
+        qcrypto_pbkdf2_count_iters(luks_opts.hash_alg,
+                                   masterkey, luks->header.key_bytes,
+                                   luks->header.master_key_salt,
+                                   QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                                   &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto error;
+    }
+
+    /* Why /= 8 ?  That matches cryptsetup, but there's no
+     * explanation why they chose /= 8... */
+    luks->header.master_key_iterations /= 8;
+    luks->header.master_key_iterations = MAX(
+        luks->header.master_key_iterations,
+        QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS);
+
+
+    /* Hash the master key, saving the result in the LUKS
+     * header. This hash is used when opening the encrypted
+     * device to verify that the user password unlocked a
+     * valid master key
+     */
+    if (qcrypto_pbkdf2(luks_opts.hash_alg,
+                       masterkey, luks->header.key_bytes,
+                       luks->header.master_key_salt,
+                       QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                       luks->header.master_key_iterations,
+                       luks->header.master_key_digest,
+                       QCRYPTO_BLOCK_LUKS_DIGEST_LEN,
+                       errp) < 0) {
+        goto error;
+    }
+
+
+    /* Although LUKS has multiple key slots, we're just going
+     * to use the first key slot */
+
+    splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES;
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        luks->header.key_slots[i].active = i == 0 ?
+            QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED :
+            QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
+        luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES;
+        luks->header.key_slots[i].key_offset =
+            (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
+            (ROUND_UP(((splitkeylen + 511) / 512),
+                      (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) * i);
+    }
+
+    if (qcrypto_random_bytes(luks->header.key_slots[0].salt,
+                             QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                             errp) < 0) {
+        goto error;
+    }
+
+    /* Again we determine how many iterations are required to
+     * hash the user password while consuming 1 second of compute
+     * time */
+    luks->header.key_slots[0].iterations =
+        qcrypto_pbkdf2_count_iters(luks_opts.hash_alg,
+                                   (uint8_t *)password, strlen(password),
+                                   luks->header.key_slots[0].salt,
+                                   QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                                   &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto error;
+    }
+    /* Why /= 2 ?  That matches cryptsetup, but there's no
+     * explanation why they chose /= 2... */
+    luks->header.key_slots[0].iterations /= 2;
+    luks->header.key_slots[0].iterations = MAX(
+        luks->header.key_slots[0].iterations,
+        QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS);
+
+
+    /* Generate a key that we'll use to encrypt the master
+     * key, from the user's password
+     */
+    slotkey = g_new0(uint8_t, luks->header.key_bytes);
+    if (qcrypto_pbkdf2(luks_opts.hash_alg,
+                       (uint8_t *)password, strlen(password),
+                       luks->header.key_slots[0].salt,
+                       QCRYPTO_BLOCK_LUKS_SALT_LEN,
+                       luks->header.key_slots[0].iterations,
+                       slotkey, luks->header.key_bytes,
+                       errp) < 0) {
+        goto error;
+    }
+
+
+    /* Setup the encryption objects needed to encrypt the
+     * master key material
+     */
+    cipher = qcrypto_cipher_new(luks_opts.cipher_alg,
+                                luks_opts.cipher_mode,
+                                slotkey, luks->header.key_bytes,
+                                errp);
+    if (!cipher) {
+        goto error;
+    }
+
+    ivgen = qcrypto_ivgen_new(luks_opts.ivgen_alg,
+                              luks_opts.cipher_alg,
+                              luks_opts.ivgen_hash_alg,
+                              slotkey, luks->header.key_bytes,
+                              errp);
+    if (!ivgen) {
+        goto error;
+    }
+
+    /* Before storing the master key, we need to vastly
+     * increase its size, as protection against forensic
+     * disk data recovery */
+    splitkey = g_new0(uint8_t, splitkeylen);
+
+    if (qcrypto_afsplit_encode(luks_opts.hash_alg,
+                               luks->header.key_bytes,
+                               luks->header.key_slots[0].stripes,
+                               masterkey,
+                               splitkey,
+                               errp) < 0) {
+        goto error;
+    }
+
+    /* Now we encrypt the split master key with the key generated
+     * from the user's password, before storing it */
+    if (qcrypto_block_encrypt_helper(cipher, block->niv, ivgen,
+                                     0,
+                                     splitkey,
+                                     splitkeylen,
+                                     errp) < 0) {
+        goto error;
+    }
+
+
+
+    /* The total size of the LUKS headers is the partition header + key
+     * slot headers, rounded up to the nearest sector, combined with
+     * the size of each master key material region, also rounded up
+     * to the nearest sector */
+    luks->header.payload_offset =
+        (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
+        (ROUND_UP(((splitkeylen + 511) / 512),
+                  (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) *
+         QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+
+    block->payload_offset = luks->header.payload_offset;
+
+    /* Reserve header space to match payload offset */
+    initfunc(block, block->payload_offset * 512, &local_err, opaque);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto error;
+    }
+
+    /* Everything on disk uses Big Endian, so flip header fields
+     * before writing them */
+    cpu_to_be16s(&luks->header.version);
+    cpu_to_be32s(&luks->header.payload_offset);
+    cpu_to_be32s(&luks->header.key_bytes);
+    cpu_to_be32s(&luks->header.master_key_iterations);
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        cpu_to_be32s(&luks->header.key_slots[i].active);
+        cpu_to_be32s(&luks->header.key_slots[i].iterations);
+        cpu_to_be32s(&luks->header.key_slots[i].key_offset);
+        cpu_to_be32s(&luks->header.key_slots[i].stripes);
+    }
+
+
+    /* Write out the partition header and key slot headers */
+    writefunc(block, 0,
+              (const uint8_t *)&luks->header,
+              sizeof(luks->header),
+              &local_err,
+              opaque);
+
+    /* Delay checking local_err until we've byte-swapped */
+
+    /* Byte swap the header back to native, in case we need
+     * to read it again later */
+    be16_to_cpus(&luks->header.version);
+    be32_to_cpus(&luks->header.payload_offset);
+    be32_to_cpus(&luks->header.key_bytes);
+    be32_to_cpus(&luks->header.master_key_iterations);
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        be32_to_cpus(&luks->header.key_slots[i].active);
+        be32_to_cpus(&luks->header.key_slots[i].iterations);
+        be32_to_cpus(&luks->header.key_slots[i].key_offset);
+        be32_to_cpus(&luks->header.key_slots[i].stripes);
+    }
+
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto error;
+    }
+
+    /* Write out the master key material, starting at the
+     * sector immediately following the partition header. */
+    if (writefunc(block,
+                  luks->header.key_slots[0].key_offset * 512,
+                  splitkey, splitkeylen,
+                  errp,
+                  opaque) != splitkeylen) {
+        goto error;
+    }
+
+    memset(masterkey, 0, luks->header.key_bytes);
+    g_free(masterkey);
+    memset(slotkey, 0, luks->header.key_bytes);
+    g_free(slotkey);
+    g_free(splitkey);
+    g_free(password);
+    g_free(cipher_mode_spec);
+
+    qcrypto_ivgen_free(ivgen);
+    qcrypto_cipher_free(cipher);
+
+    return 0;
+
+ error:
+    if (masterkey) {
+        memset(masterkey, 0, luks->header.key_bytes);
+    }
+    g_free(masterkey);
+    if (slotkey) {
+        memset(slotkey, 0, luks->header.key_bytes);
+    }
+    g_free(slotkey);
+    g_free(splitkey);
+    g_free(password);
+    g_free(cipher_mode_spec);
+
+    qcrypto_ivgen_free(ivgen);
+    qcrypto_cipher_free(cipher);
+
+    g_free(luks);
+    return -1;
+}
+
+
+static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
+{
+    g_free(block->opaque);
+}
+
+
+static int
+qcrypto_block_luks_decrypt(QCryptoBlock *block,
+                           uint64_t startsector,
+                           uint8_t *buf,
+                           size_t len,
+                           Error **errp)
+{
+    return qcrypto_block_decrypt_helper(block->cipher,
+                                        block->niv, block->ivgen,
+                                        startsector, buf, len, errp);
+}
+
+
+static int
+qcrypto_block_luks_encrypt(QCryptoBlock *block,
+                           uint64_t startsector,
+                           uint8_t *buf,
+                           size_t len,
+                           Error **errp)
+{
+    return qcrypto_block_encrypt_helper(block->cipher,
+                                        block->niv, block->ivgen,
+                                        startsector, buf, len, errp);
+}
+
+
+const QCryptoBlockDriver qcrypto_block_driver_luks = {
+    .open = qcrypto_block_luks_open,
+    .create = qcrypto_block_luks_create,
+    .cleanup = qcrypto_block_luks_cleanup,
+    .decrypt = qcrypto_block_luks_decrypt,
+    .encrypt = qcrypto_block_luks_encrypt,
+    .has_format = qcrypto_block_luks_has_format,
+};
diff --git a/crypto/block-luks.h b/crypto/block-luks.h
new file mode 100644
index 0000000..0934138
--- /dev/null
+++ b/crypto/block-luks.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block device encryption LUKS format
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_BLOCK_LUKS_H__
+#define QCRYPTO_BLOCK_LUKS_H__
+
+#include "crypto/blockpriv.h"
+
+extern const QCryptoBlockDriver qcrypto_block_driver_luks;
+
+#endif /* QCRYPTO_BLOCK_LUKS_H__ */
diff --git a/crypto/block.c b/crypto/block.c
index 757e28a..860ecce 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -20,9 +20,11 @@
 
 #include "crypto/blockpriv.h"
 #include "crypto/block-qcow.h"
+#include "crypto/block-luks.h"
 
 static const QCryptoBlockDriver *qcrypto_block_drivers[] = {
     [Q_CRYPTO_BLOCK_FORMAT_QCOW] = &qcrypto_block_driver_qcow,
+    [Q_CRYPTO_BLOCK_FORMAT_LUKS] = &qcrypto_block_driver_luks,
 };
 
 
diff --git a/qapi/crypto.json b/qapi/crypto.json
index b4b0a95..5e9014d 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -102,12 +102,13 @@
 #
 # @qcow: QCow/QCow2 built-in AES-CBC encryption. Use only
 #        for liberating data from old images.
+# @luks: LUKS encryption format. Recommended for new images
 #
 # Since: 2.6
 ##
 { 'enum': 'QCryptoBlockFormat',
 #  'prefix': 'QCRYPTO_BLOCK_FORMAT',
-  'data': ['qcow']}
+  'data': ['qcow', 'luks']}
 
 ##
 # QCryptoBlockOptionsBase:
@@ -136,6 +137,40 @@
   'data': { '*key-secret': 'str' }}
 
 ##
+# QCryptoBlockOptionsLUKS:
+#
+# The options that apply to LUKS encryption format
+#
+# @key-secret: #optional the ID of a QCryptoSecret object providing the
+#              decryption key
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsLUKS',
+  'data': { '*key-secret': 'str' }}
+
+
+##
+# QCryptoBlockCreateOptionsLUKS:
+#
+# The options that apply to LUKS encryption format initialization
+#
+# @cipher-alg: #optional the cipher algorithm for data encryption
+# @cipher-mode: #optional the cipher mode for data encryption
+# @ivgen-alg: #optional the initialization vector generator
+# @ivgen-hash-alg: #optional the initialization vector generator hash
+# @hash-alg: #optional the master key hash algorithm
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockCreateOptionsLUKS',
+  'base': 'QCryptoBlockOptionsLUKS',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm',
+            '*cipher-mode': 'QCryptoCipherMode',
+            '*ivgen-alg': 'QCryptoIVGenAlgorithm',
+            '*ivgen-hash-alg': 'QCryptoHashAlgorithm',
+            '*hash-alg': 'QCryptoHashAlgorithm'}}
+
+
+##
 # QCryptoBlockOpenOptions:
 #
 # The options that are available for all encryption formats
@@ -146,7 +181,8 @@
 { 'union': 'QCryptoBlockOpenOptions',
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
-  'data': { 'qcow': 'QCryptoBlockOptionsQCow' } }
+  'data': { 'qcow': 'QCryptoBlockOptionsQCow',
+            'luks': 'QCryptoBlockOptionsLUKS' } }
 
 
 ##
@@ -160,4 +196,5 @@
 { 'union': 'QCryptoBlockCreateOptions',
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
-  'data': { 'qcow': 'QCryptoBlockOptionsQCow' } }
+  'data': { 'qcow': 'QCryptoBlockOptionsQCow',
+            'luks': 'QCryptoBlockCreateOptionsLUKS' } }
diff --git a/tests/test-crypto-block.c b/tests/test-crypto-block.c
index 54216e9..5fe5d05 100644
--- a/tests/test-crypto-block.c
+++ b/tests/test-crypto-block.c
@@ -40,6 +40,69 @@ static QCryptoBlockOpenOptions qcow_open_opts = {
     .u.qcow = &qcow_opts,
 };
 
+
+static QCryptoBlockOptionsLUKS luks_open_opts_int = {
+    .has_key_secret = true,
+    .key_secret = (char *)"sec0",
+};
+
+static QCryptoBlockOpenOptions luks_open_opts = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+    .u.luks = &luks_open_opts_int,
+};
+
+
+/* Creation with all default values */
+static QCryptoBlockCreateOptionsLUKS luks_create_opts_default_int = {
+    .has_key_secret = true,
+    .key_secret = (char *)"sec0",
+};
+
+static QCryptoBlockCreateOptions luks_create_opts_default = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+    .u.luks = &luks_create_opts_default_int,
+};
+
+
+static QCryptoBlockCreateOptionsLUKS luks_create_opts_aes256_cbc_plain64_int = {
+    .has_key_secret = true,
+    .key_secret = (char *)"sec0",
+    .has_cipher_alg = true,
+    .cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
+    .has_cipher_mode = true,
+    .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+    .has_ivgen_alg = true,
+    .ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
+};
+
+static QCryptoBlockCreateOptions luks_create_opts_aes256_cbc_plain64 = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+    .u.luks = &luks_create_opts_aes256_cbc_plain64_int,
+};
+
+
+
+static QCryptoBlockCreateOptionsLUKS luks_create_opts_aes256_cbc_essiv_int = {
+    .has_key_secret = true,
+    .key_secret = (char *)"sec0",
+    .has_cipher_alg = true,
+    .cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
+    .has_cipher_mode = true,
+    .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+    .has_ivgen_alg = true,
+    .ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV,
+    .has_ivgen_hash_alg = true,
+    .ivgen_hash_alg = QCRYPTO_HASH_ALG_SHA256,
+    .has_hash_alg = true,
+    .hash_alg = QCRYPTO_HASH_ALG_SHA1,
+};
+
+static QCryptoBlockCreateOptions luks_create_opts_aes256_cbc_essiv = {
+    .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+    .u.luks = &luks_create_opts_aes256_cbc_essiv_int,
+};
+
+
 static struct QCryptoBlockTestData {
     const char *path;
     QCryptoBlockCreateOptions *create_opts;
@@ -66,6 +129,47 @@ static struct QCryptoBlockTestData {
 
         .ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
     },
+    {
+        .path = "/crypto/block/luks/default",
+        .create_opts = &luks_create_opts_default,
+        .open_opts = &luks_open_opts,
+
+        .expect_header = true,
+
+        .cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
+        .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+        .hash_alg = QCRYPTO_HASH_ALG_SHA256,
+
+        .ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV,
+        .ivgen_hash = QCRYPTO_HASH_ALG_SHA256,
+    },
+    {
+        .path = "/crypto/block/luks/aes-256-cbc-plain64",
+        .create_opts = &luks_create_opts_aes256_cbc_plain64,
+        .open_opts = &luks_open_opts,
+
+        .expect_header = true,
+
+        .cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
+        .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+        .hash_alg = QCRYPTO_HASH_ALG_SHA256,
+
+        .ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
+    },
+    {
+        .path = "/crypto/block/luks/aes-256-cbc-eesiv",
+        .create_opts = &luks_create_opts_aes256_cbc_essiv,
+        .open_opts = &luks_open_opts,
+
+        .expect_header = true,
+
+        .cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
+        .cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
+        .hash_alg = QCRYPTO_HASH_ALG_SHA1,
+
+        .ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV,
+        .ivgen_hash = QCRYPTO_HASH_ALG_SHA256,
+    },
 };
 
 
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (6 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-05 19:08   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required Daniel P. Berrange
                   ` (8 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

When opening an image it is useful to know whether the caller
intends to perform I/O on the image or not. In the case of
encrypted images this will allow the block driver to avoid
having to prompt for decryption keys when we merely want to
query header metadata about the image. eg qemu-img info

This flag is enforced at the top level only, since even if
we don't want todo I/O on the 'qcow2' file payload, the
underlying 'file' driver will still need todo I/O to read
the qcow2 header, for example.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block.c               |  5 +++--
 block/io.c            |  2 ++
 include/block/block.h |  1 +
 qemu-img.c            | 33 +++++++++++++++++----------------
 4 files changed, 23 insertions(+), 18 deletions(-)

diff --git a/block.c b/block.c
index 54c37f9..0806d4f 100644
--- a/block.c
+++ b/block.c
@@ -719,7 +719,8 @@ static void bdrv_inherited_options(int *child_flags, QDict *child_options,
     flags |= BDRV_O_UNMAP;
 
     /* Clear flags that only apply to the top layer */
-    flags &= ~(BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING | BDRV_O_COPY_ON_READ);
+    flags &= ~(BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING | BDRV_O_COPY_ON_READ |
+               BDRV_O_NO_IO);
 
     *child_flags = flags;
 }
@@ -739,7 +740,7 @@ static void bdrv_inherited_fmt_options(int *child_flags, QDict *child_options,
     child_file.inherit_options(child_flags, child_options,
                                parent_flags, parent_options);
 
-    *child_flags &= ~BDRV_O_PROTOCOL;
+    *child_flags &= ~(BDRV_O_PROTOCOL | BDRV_O_NO_IO);
 }
 
 const BdrvChildRole child_format = {
diff --git a/block/io.c b/block/io.c
index 63e3678..ebd26cd 100644
--- a/block/io.c
+++ b/block/io.c
@@ -859,6 +859,7 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs,
     assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
     assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
     assert(!qiov || bytes == qiov->size);
+    assert((bs->open_flags & BDRV_O_NO_IO) == 0);
 
     /* Handle Copy on Read and associated serialisation */
     if (flags & BDRV_REQ_COPY_ON_READ) {
@@ -1145,6 +1146,7 @@ static int coroutine_fn bdrv_aligned_pwritev(BlockDriverState *bs,
     assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
     assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
     assert(!qiov || bytes == qiov->size);
+    assert((bs->open_flags & BDRV_O_NO_IO) == 0);
 
     waited = wait_serialising_requests(req);
     assert(!waited || !req->serialising);
diff --git a/include/block/block.h b/include/block/block.h
index c96923d..73ffbd5 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -91,6 +91,7 @@ typedef struct HDGeometry {
 #define BDRV_O_PROTOCOL    0x8000  /* if no block driver is explicitly given:
                                       select an appropriate protocol driver,
                                       ignoring the format layer */
+#define BDRV_O_NO_IO       0x10000 /* don't initialize for I/O */
 
 #define BDRV_O_CACHE_MASK  (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH)
 
diff --git a/qemu-img.c b/qemu-img.c
index 4052aba..e79f29b 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -267,7 +267,7 @@ static BlockBackend *img_open_opts(const char *id,
 
 static BlockBackend *img_open_file(const char *id, const char *filename,
                                    const char *fmt, int flags,
-                                   bool require_io, bool quiet)
+                                   bool quiet)
 {
     BlockBackend *blk;
     BlockDriverState *bs;
@@ -287,7 +287,7 @@ static BlockBackend *img_open_file(const char *id, const char *filename,
     }
 
     bs = blk_bs(blk);
-    if (bdrv_is_encrypted(bs) && require_io) {
+    if (bdrv_is_encrypted(bs) && !(flags & BDRV_O_NO_IO)) {
         qprintf(quiet, "Disk image '%s' is encrypted.\n", filename);
         if (qemu_read_password(password, sizeof(password)) < 0) {
             error_report("No password given");
@@ -680,7 +680,7 @@ static int img_check(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, flags);
     } else {
-        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+        blk = img_open_file("image", filename, fmt, flags, quiet);
     }
     if (!blk) {
         return 1;
@@ -885,7 +885,7 @@ static int img_commit(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, flags);
     } else {
-        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+        blk = img_open_file("image", filename, fmt, flags, quiet);
     }
     if (!blk) {
         return 1;
@@ -1247,13 +1247,13 @@ static int img_compare(int argc, char **argv)
             goto out3;
         }
     } else {
-        blk1 = img_open_file("image_1", filename1, fmt1, flags, true, quiet);
+        blk1 = img_open_file("image_1", filename1, fmt1, flags, quiet);
         if (!blk1) {
             ret = 2;
             goto out3;
         }
 
-        blk2 = img_open_file("image_2", filename2, fmt2, flags, true, quiet);
+        blk2 = img_open_file("image_2", filename2, fmt2, flags, quiet);
         if (!blk2) {
             ret = 2;
             goto out2;
@@ -1933,7 +1933,7 @@ static int img_convert(int argc, char **argv)
             blk[bs_i] = img_open_opts(id, opts, src_flags);
         } else {
             blk[bs_i] = img_open_file(id, argv[optind + bs_i], fmt, src_flags,
-                                      true, quiet);
+                                      quiet);
         }
         g_free(id);
         if (!blk[bs_i]) {
@@ -2080,7 +2080,7 @@ static int img_convert(int argc, char **argv)
      * the bdrv_create() call which takes different params
      */
     out_blk = img_open_file("target", out_filename,
-                            out_fmt, flags, true, quiet);
+                            out_fmt, flags, quiet);
     if (!out_blk) {
         ret = -1;
         goto out;
@@ -2284,12 +2284,13 @@ static ImageInfoList *collect_image_info_list(bool image_opts,
                 goto err;
             }
             blk = img_open_opts("image", opts,
-                                BDRV_O_FLAGS | BDRV_O_NO_BACKING);
+                                BDRV_O_FLAGS | BDRV_O_NO_BACKING |
+                                BDRV_O_NO_IO);
             opts = NULL;
         } else {
             blk = img_open_file("image", filename, fmt,
-                                BDRV_O_FLAGS | BDRV_O_NO_BACKING,
-                                false, false);
+                                BDRV_O_FLAGS | BDRV_O_NO_BACKING |
+                                BDRV_O_NO_IO, false);
         }
         if (!blk) {
             goto err;
@@ -2606,7 +2607,7 @@ static int img_map(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, BDRV_O_FLAGS);
     } else {
-        blk = img_open_file("image", filename, fmt, BDRV_O_FLAGS, true, false);
+        blk = img_open_file("image", filename, fmt, BDRV_O_FLAGS, false);
     }
     if (!blk) {
         return 1;
@@ -2763,7 +2764,7 @@ static int img_snapshot(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, bdrv_oflags);
     } else {
-        blk = img_open_file("image", filename, NULL, bdrv_oflags, true, quiet);
+        blk = img_open_file("image", filename, NULL, bdrv_oflags, quiet);
     }
     if (!blk) {
         return 1;
@@ -2948,7 +2949,7 @@ static int img_rebase(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, flags);
     } else {
-        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+        blk = img_open_file("image", filename, fmt, flags, quiet);
     }
     if (!blk) {
         ret = -1;
@@ -3302,7 +3303,7 @@ static int img_resize(int argc, char **argv)
         blk = img_open_opts("image", opts, BDRV_O_FLAGS | BDRV_O_RDWR);
     } else {
         blk = img_open_file("image", filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR,
-                            true, quiet);
+                            quiet);
     }
     if (!blk) {
         ret = -1;
@@ -3473,7 +3474,7 @@ static int img_amend(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, flags);
     } else {
-        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+        blk = img_open_file("image", filename, fmt, flags, quiet);
     }
     if (!blk) {
         ret = -1;
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (7 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-05 19:52   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver Daniel P. Berrange
                   ` (7 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

The qemu-img/qemu-io tools prompt for disk encryption passwords
regardless of whether any are actually required. Adding a check
on bdrv_key_required() avoids this prompt for disk formats which
have been converted to the QCryptoSecret APIs.

This is just a temporary hack to ensure the block I/O tests
continue to work after each patch, since the last patch will
completely delete all the password prompting code.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-img.c | 3 ++-
 qemu-io.c  | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/qemu-img.c b/qemu-img.c
index e79f29b..faf4dbd 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -287,7 +287,8 @@ static BlockBackend *img_open_file(const char *id, const char *filename,
     }
 
     bs = blk_bs(blk);
-    if (bdrv_is_encrypted(bs) && !(flags & BDRV_O_NO_IO)) {
+    if (bdrv_is_encrypted(bs) && bdrv_key_required(bs) &&
+        !(flags & BDRV_O_NO_IO)) {
         qprintf(quiet, "Disk image '%s' is encrypted.\n", filename);
         if (qemu_read_password(password, sizeof(password)) < 0) {
             error_report("No password given");
diff --git a/qemu-io.c b/qemu-io.c
index 1c20e9b..98b5cc2 100644
--- a/qemu-io.c
+++ b/qemu-io.c
@@ -72,7 +72,7 @@ static int openfile(char *name, int flags, QDict *opts)
     }
 
     bs = blk_bs(qemuio_blk);
-    if (bdrv_is_encrypted(bs)) {
+    if (bdrv_is_encrypted(bs) && bdrv_key_required(bs)) {
         char password[256];
         printf("Disk image '%s' is encrypted.\n", name);
         if (qemu_read_password(password, sizeof(password)) < 0) {
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (8 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  9:12   ` Fam Zheng
  2016-02-05 22:20   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
                   ` (6 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Add a block driver that is capable of supporting any full disk
encryption format. This utilizes the previously added block
encryption code, and at this time supports the LUKS format.

The driver code is capable of supporting any format supported
by the QCryptoBlock module, so it registers one block driver
for each format.

At this time, the "luks" driver is registered. New LUKS
compatible volume can be formatted using qemu-img

$ qemu-img create --object secret,data=123456,id=sec0 \
      -f luks -o key-secret=sec0,cipher-alg=aes-256,\
                 cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \
      demo.luks 10G

And query its size

$ qemu-img info --object secret,data=123456,id=sec0  --source demo.luks,driver=luks,key-secret=sec0
image: json:{"key-secret": "sec0", "driver": "luks", "file": {"driver": "file", "filename": "demo.luks"}}
file format: luks
virtual size: 10.0G (10737416192 bytes)
disk size: 132K

All volumes created by this new 'luks' driver should be
capable of being opened by the kernel dm-crypt driver.
With this initial impl, not all volumes created with
dm-crypt can be opened by the QEMU 'luks' driver. This
is due to lack of support for certain algorithms, in
particular the 'xts' cipher mode. These limitations will
be addressed in a later series of patches, with the
intent that QEMU should be able to open anything that
dm-crypt LUKS supports.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/Makefile.objs  |   2 +
 block/crypto.c       | 541 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/block-core.json |  18 +-
 3 files changed, 560 insertions(+), 1 deletion(-)
 create mode 100644 block/crypto.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index 58ef2ef..12eae77 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -23,6 +23,8 @@ block-obj-$(CONFIG_LIBSSH2) += ssh.o
 block-obj-y += accounting.o
 block-obj-y += write-threshold.o
 
+block-obj-y += crypto.o
+
 common-obj-y += stream.o
 common-obj-y += commit.o
 common-obj-y += backup.o
diff --git a/block/crypto.c b/block/crypto.c
new file mode 100644
index 0000000..2ba78bd
--- /dev/null
+++ b/block/crypto.c
@@ -0,0 +1,541 @@
+/*
+ * QEMU block full disk encryption
+ *
+ * Copyright (c) 2015-2016 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config-host.h"
+
+#include "block/block_int.h"
+#include "crypto/block.h"
+#include "qapi/opts-visitor.h"
+#include "qapi-visit.h"
+
+#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
+#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
+#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
+#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
+#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
+#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
+
+typedef struct BlockCrypto BlockCrypto;
+
+struct BlockCrypto {
+    QCryptoBlock *block;
+    CoMutex lock;
+};
+
+
+static int block_crypto_probe_generic(QCryptoBlockFormat format,
+                                      const uint8_t *buf,
+                                      int buf_size,
+                                      const char *filename)
+{
+    if (qcrypto_block_has_format(format,
+                                 buf, buf_size)) {
+        return 100;
+    } else {
+        return 0;
+    }
+}
+
+
+static ssize_t block_crypto_read_func(QCryptoBlock *block,
+                                      size_t offset,
+                                      uint8_t *buf,
+                                      size_t buflen,
+                                      Error **errp,
+                                      void *opaque)
+{
+    BlockDriverState *bs = opaque;
+    ssize_t ret;
+
+    ret = bdrv_pread(bs->file->bs, offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not read encryption header");
+        return ret;
+    }
+    return ret;
+}
+
+
+static ssize_t block_crypto_write_func(QCryptoBlock *block,
+                                       size_t offset,
+                                       const uint8_t *buf,
+                                       size_t buflen,
+                                       Error **errp,
+                                       void *opaque)
+{
+    BlockDriverState *bs = opaque;
+    ssize_t ret;
+
+    ret = bdrv_pwrite(bs, offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not write encryption header");
+        return ret;
+    }
+    return ret;
+}
+
+
+static ssize_t block_crypto_init_func(QCryptoBlock *block,
+                                      size_t headerlen,
+                                      Error **errp,
+                                      void *opaque)
+{
+    /* We don't need to do anything special to reserve space */
+    return 0;
+}
+
+
+static QemuOptsList block_crypto_runtime_opts_luks = {
+    .name = "crypto",
+    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_runtime_opts_luks.head),
+    .desc = {
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        { /* end of list */ }
+    },
+};
+
+
+static QemuOptsList block_crypto_create_opts_luks = {
+    .name = "crypto",
+    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
+    .desc = {
+        {
+            .name = BLOCK_OPT_SIZE,
+            .type = QEMU_OPT_SIZE,
+            .help = "Virtual disk size"
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm",
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher mode",
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator algorithm",
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator hash algorithm",
+        },
+        {
+            .name = BLOCK_CRYPTO_OPT_LUKS_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption hash algorithm",
+        },
+        { /* end of list */ }
+    },
+};
+
+
+static QCryptoBlockOpenOptions *
+block_crypto_open_opts_init(QCryptoBlockFormat format,
+                            QemuOpts *opts,
+                            Error **errp)
+{
+    OptsVisitor *ov;
+    QCryptoBlockOpenOptions *ret;
+    Error *local_err = NULL;
+
+    ret = g_new0(QCryptoBlockOpenOptions, 1);
+    ret->format = format;
+
+    ov = opts_visitor_new(opts);
+
+    switch (format) {
+    case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+        ret->u.luks = g_new0(QCryptoBlockOptionsLUKS, 1);
+        visit_type_QCryptoBlockOptionsLUKS(opts_get_visitor(ov),
+                                           &ret->u.luks, "luks", &local_err);
+        break;
+
+    default:
+        error_setg(&local_err, "Unsupported block format %d", format);
+        break;
+    }
+
+    if (local_err) {
+        error_propagate(errp, local_err);
+        opts_visitor_cleanup(ov);
+        qapi_free_QCryptoBlockOpenOptions(ret);
+        return NULL;
+    }
+
+    opts_visitor_cleanup(ov);
+    return ret;
+}
+
+
+static QCryptoBlockCreateOptions *
+block_crypto_create_opts_init(QCryptoBlockFormat format,
+                              QemuOpts *opts,
+                              Error **errp)
+{
+    OptsVisitor *ov;
+    QCryptoBlockCreateOptions *ret;
+    Error *local_err = NULL;
+
+    ret = g_new0(QCryptoBlockCreateOptions, 1);
+    ret->format = format;
+
+    ov = opts_visitor_new(opts);
+
+    switch (format) {
+    case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+        ret->u.luks = g_new0(QCryptoBlockCreateOptionsLUKS, 1);
+        visit_type_QCryptoBlockCreateOptionsLUKS(
+            opts_get_visitor(ov),
+            &ret->u.luks, "luks", &local_err);
+        break;
+
+    default:
+        error_setg(&local_err, "Unsupported block format %d", format);
+        break;
+    }
+
+    if (local_err) {
+        error_propagate(errp, local_err);
+        opts_visitor_cleanup(ov);
+        qapi_free_QCryptoBlockCreateOptions(ret);
+        return NULL;
+    }
+
+    opts_visitor_cleanup(ov);
+    return ret;
+}
+
+
+static int block_crypto_open_generic(QCryptoBlockFormat format,
+                                     QemuOptsList *opts_spec,
+                                     BlockDriverState *bs,
+                                     QDict *options,
+                                     int flags,
+                                     Error **errp)
+{
+    BlockCrypto *crypto = bs->opaque;
+    QemuOpts *opts = NULL;
+    Error *local_err = NULL;
+    int ret = -EINVAL;
+    QCryptoBlockOpenOptions *open_opts = NULL;
+    unsigned int cflags = 0;
+
+    opts = qemu_opts_create(opts_spec, NULL, 0, &error_abort);
+    qemu_opts_absorb_qdict(opts, options, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto cleanup;
+    }
+
+    open_opts = block_crypto_open_opts_init(format, opts, errp);
+    if (!open_opts) {
+        goto cleanup;
+    }
+
+    if (flags & BDRV_O_NO_IO) {
+        cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
+    }
+    crypto->block = qcrypto_block_open(open_opts,
+                                       block_crypto_read_func,
+                                       bs,
+                                       cflags,
+                                       errp);
+
+    if (!crypto->block) {
+        ret = -EIO;
+        goto cleanup;
+    }
+
+    bs->encrypted = 1;
+    bs->valid_key = 1;
+
+    qemu_co_mutex_init(&crypto->lock);
+
+    ret = 0;
+ cleanup:
+    qapi_free_QCryptoBlockOpenOptions(open_opts);
+    return ret;
+}
+
+
+static int block_crypto_create_generic(QCryptoBlockFormat format,
+                                       const char *filename,
+                                       QemuOpts *opts,
+                                       Error **errp)
+{
+    int ret = -EINVAL;
+    QCryptoBlockCreateOptions *create_opts = NULL;
+    BlockDriverState *bs = NULL;
+    QCryptoBlock *crypto = NULL;
+    uint64_t size = 0;
+
+    size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+                    BDRV_SECTOR_SIZE);
+
+    create_opts = block_crypto_create_opts_init(format, opts, errp);
+    if (!create_opts) {
+        return -1;
+    }
+
+    /* XXX Should we treat size as being total physical size
+     * of the image (ie payload + encryption header), or just
+     * the logical size of the image (ie payload). If the latter
+     * then we need to extend 'size' to include the header
+     * size */
+    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
+    ret = bdrv_create_file(filename, opts, errp);
+    if (ret < 0) {
+        goto cleanup;
+    }
+
+    ret = bdrv_open(&bs, filename, NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL,
+                    errp);
+    if (ret < 0) {
+        goto cleanup;
+    }
+
+    crypto = qcrypto_block_create(create_opts,
+                                  block_crypto_init_func,
+                                  block_crypto_write_func,
+                                  bs,
+                                  errp);
+
+    if (!crypto) {
+        ret = -EIO;
+        goto cleanup;
+    }
+
+    ret = 0;
+ cleanup:
+    qcrypto_block_free(crypto);
+    bdrv_unref(bs);
+    qapi_free_QCryptoBlockCreateOptions(create_opts);
+    return ret;
+}
+
+static void block_crypto_close(BlockDriverState *bs)
+{
+    BlockCrypto *crypto = bs->opaque;
+    qcrypto_block_free(crypto->block);
+}
+
+
+#define BLOCK_CRYPTO_MAX_SECTORS 32
+
+static coroutine_fn int
+block_crypto_co_readv(BlockDriverState *bs, int64_t sector_num,
+                      int remaining_sectors, QEMUIOVector *qiov)
+{
+    BlockCrypto *crypto = bs->opaque;
+    int cur_nr_sectors; /* number of sectors in current iteration */
+    uint64_t bytes_done = 0;
+    uint8_t *cipher_data = NULL;
+    QEMUIOVector hd_qiov;
+    int ret = 0;
+    size_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
+
+    qemu_iovec_init(&hd_qiov, qiov->niov);
+
+    qemu_co_mutex_lock(&crypto->lock);
+
+    while (remaining_sectors) {
+        cur_nr_sectors = remaining_sectors;
+
+        if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
+            cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
+        }
+        cipher_data =
+            qemu_try_blockalign(bs->file->bs, cur_nr_sectors * 512);
+
+        qemu_iovec_reset(&hd_qiov);
+        qemu_iovec_add(&hd_qiov, cipher_data, cur_nr_sectors * 512);
+
+        qemu_co_mutex_unlock(&crypto->lock);
+        ret = bdrv_co_readv(bs->file->bs,
+                            payload_offset + sector_num,
+                            cur_nr_sectors, &hd_qiov);
+        qemu_co_mutex_lock(&crypto->lock);
+        if (ret < 0) {
+            goto cleanup;
+        }
+
+        if (qcrypto_block_decrypt(crypto->block,
+                                  sector_num,
+                                  cipher_data, cur_nr_sectors * 512,
+                                  NULL) < 0) {
+            ret = -1;
+            goto cleanup;
+        }
+
+        qemu_iovec_from_buf(qiov, bytes_done,
+                            cipher_data, cur_nr_sectors * 512);
+
+        remaining_sectors -= cur_nr_sectors;
+        sector_num += cur_nr_sectors;
+        bytes_done += cur_nr_sectors * 512;
+    }
+
+ cleanup:
+    qemu_co_mutex_unlock(&crypto->lock);
+
+    qemu_iovec_destroy(&hd_qiov);
+    qemu_vfree(cipher_data);
+
+    return ret;
+}
+
+
+static coroutine_fn int
+block_crypto_co_writev(BlockDriverState *bs, int64_t sector_num,
+                       int remaining_sectors, QEMUIOVector *qiov)
+{
+    BlockCrypto *crypto = bs->opaque;
+    int cur_nr_sectors; /* number of sectors in current iteration */
+    uint64_t bytes_done = 0;
+    uint8_t *cipher_data = NULL;
+    QEMUIOVector hd_qiov;
+    int ret = 0;
+    size_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
+
+    qemu_iovec_init(&hd_qiov, qiov->niov);
+
+    qemu_co_mutex_lock(&crypto->lock);
+
+    while (remaining_sectors) {
+        cur_nr_sectors = remaining_sectors;
+
+        if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
+            cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
+        }
+        cipher_data =
+            qemu_try_blockalign(bs->file->bs, cur_nr_sectors * 512);
+
+        qemu_iovec_to_buf(qiov, bytes_done,
+                          cipher_data, cur_nr_sectors * 512);
+
+        if (qcrypto_block_encrypt(crypto->block,
+                                  sector_num,
+                                  cipher_data, cur_nr_sectors * 512,
+                                  NULL) < 0) {
+            ret = -1;
+            goto cleanup;
+        }
+
+        qemu_iovec_reset(&hd_qiov);
+        qemu_iovec_add(&hd_qiov, cipher_data, cur_nr_sectors * 512);
+
+        qemu_co_mutex_unlock(&crypto->lock);
+        ret = bdrv_co_writev(bs->file->bs,
+                             payload_offset + sector_num,
+                             cur_nr_sectors, &hd_qiov);
+        qemu_co_mutex_lock(&crypto->lock);
+        if (ret < 0) {
+            goto cleanup;
+        }
+
+        remaining_sectors -= cur_nr_sectors;
+        sector_num += cur_nr_sectors;
+        bytes_done += cur_nr_sectors * 512;
+    }
+
+ cleanup:
+    qemu_co_mutex_unlock(&crypto->lock);
+
+    qemu_iovec_destroy(&hd_qiov);
+    qemu_vfree(cipher_data);
+
+    return ret;
+}
+
+
+static int64_t block_crypto_getlength(BlockDriverState *bs)
+{
+    BlockCrypto *crypto = bs->opaque;
+    int64_t len = bdrv_getlength(bs->file->bs);
+
+    ssize_t offset = qcrypto_block_get_payload_offset(crypto->block);
+
+    len -= (offset * 512);
+
+    return len;
+}
+
+#define BLOCK_CRYPTO_DRIVER(name, format)                               \
+    static int block_crypto_probe_ ## name(const uint8_t *buf,          \
+                                           int buf_size,                \
+                                           const char *filename) {      \
+        return block_crypto_probe_generic(format,                       \
+                                          buf, buf_size, filename);     \
+    }                                                                   \
+                                                                        \
+    static int block_crypto_open_ ## name(BlockDriverState *bs,         \
+                                          QDict *options,               \
+                                          int flags,                    \
+                                          Error **errp)                 \
+    {                                                                   \
+        return block_crypto_open_generic(format,                        \
+                                         &block_crypto_runtime_opts_ ## name, \
+                                         bs, options, flags, errp);     \
+    }                                                                   \
+                                                                        \
+    static int block_crypto_create_ ## name(const char *filename,       \
+                                            QemuOpts *opts,             \
+                                            Error **errp)               \
+    {                                                                   \
+        return block_crypto_create_generic(format,                      \
+                                           filename, opts, errp);       \
+    }                                                                   \
+                                                                        \
+    BlockDriver bdrv_crypto_ ## name = {                                \
+        .format_name        = #name,                                    \
+        .instance_size      = sizeof(BlockCrypto),                      \
+        .bdrv_probe         = block_crypto_probe_ ## name,              \
+        .bdrv_open          = block_crypto_open_ ## name,               \
+        .bdrv_close         = block_crypto_close,                       \
+        .bdrv_create        = block_crypto_create_ ## name,             \
+        .create_opts        = &block_crypto_create_opts_ ## name,       \
+                                                                        \
+        .bdrv_co_readv      = block_crypto_co_readv,                    \
+        .bdrv_co_writev     = block_crypto_co_writev,                   \
+        .bdrv_getlength     = block_crypto_getlength,                   \
+    }
+
+BLOCK_CRYPTO_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);
+
+static void block_crypto_init(void)
+{
+    bdrv_register(&bdrv_crypto_luks);
+}
+
+block_init(block_crypto_init);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 0a915ed..a194658 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1546,7 +1546,7 @@
 { 'enum': 'BlockdevDriver',
   'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
             'dmg', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
-            'http', 'https', 'null-aio', 'null-co', 'parallels',
+            'http', 'https', 'luks', 'null-aio', 'null-co', 'parallels',
             'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'tftp', 'vdi', 'vhdx',
             'vmdk', 'vpc', 'vvfat' ] }
 
@@ -1664,6 +1664,21 @@
   'data': { 'file': 'BlockdevRef' } }
 
 ##
+# @BlockdevOptionsLUKS
+#
+# Driver specific block device options for LUKS.
+#
+# @key-secret: #optional the ID of a QCryptoSecret object providing
+#              the decryption key (since 2.6)
+#
+# Since: 2.6
+##
+{ 'struct': 'BlockdevOptionsLUKS',
+  'base': 'BlockdevOptionsGenericFormat',
+  'data': { '*key-secret': 'str' } }
+
+
+##
 # @BlockdevOptionsGenericCOWFormat
 #
 # Driver specific block device options for image format that have no option
@@ -2000,6 +2015,7 @@
       'http':       'BlockdevOptionsFile',
       'https':      'BlockdevOptionsFile',
 # TODO iscsi: Wait for structured options
+      'luks':       'BlockdevOptionsLUKS',
 # TODO nbd: Should take InetSocketAddress for 'host'?
 # TODO nfs: Wait for structured options
       'null-aio':   'BlockdevOptionsNull',
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (9 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  9:13   ` Fam Zheng
  2016-02-05 23:22   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
                   ` (5 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Instead of requiring separate input/output buffers for
encrypting data, change qcow2_encrypt_sectors() to assume
use of a single buffer, encrypting in place. The current
callers all used the same buffer for input/output already.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow2-cluster.c | 17 +++++------------
 block/qcow2.c         |  5 ++---
 block/qcow2.h         |  3 +--
 3 files changed, 8 insertions(+), 17 deletions(-)

diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index 34112c3..f5bc4f2 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -341,12 +341,8 @@ static int count_contiguous_clusters_by_type(int nb_clusters,
     return i;
 }
 
-/* The crypt function is compatible with the linux cryptoloop
-   algorithm for < 4 GB images. NOTE: out_buf == in_buf is
-   supported */
 int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
-                          uint8_t *out_buf, const uint8_t *in_buf,
-                          int nb_sectors, bool enc,
+                          uint8_t *buf, int nb_sectors, bool enc,
                           Error **errp)
 {
     union {
@@ -366,14 +362,12 @@ int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
         }
         if (enc) {
             ret = qcrypto_cipher_encrypt(s->cipher,
-                                         in_buf,
-                                         out_buf,
+                                         buf, buf,
                                          512,
                                          errp);
         } else {
             ret = qcrypto_cipher_decrypt(s->cipher,
-                                         in_buf,
-                                         out_buf,
+                                         buf, buf,
                                          512,
                                          errp);
         }
@@ -381,8 +375,7 @@ int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
             return -1;
         }
         sector_num++;
-        in_buf += 512;
-        out_buf += 512;
+        buf += 512;
     }
     return 0;
 }
@@ -430,7 +423,7 @@ static int coroutine_fn copy_sectors(BlockDriverState *bs,
         Error *err = NULL;
         assert(s->cipher);
         if (qcow2_encrypt_sectors(s, start_sect + n_start,
-                                  iov.iov_base, iov.iov_base, n,
+                                  iov.iov_base, n,
                                   true, &err) < 0) {
             ret = -EIO;
             error_free(err);
diff --git a/block/qcow2.c b/block/qcow2.c
index d992e7f..2fae692 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1504,7 +1504,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
                 assert(s->cipher);
                 Error *err = NULL;
                 if (qcow2_encrypt_sectors(s, sector_num,  cluster_data,
-                                          cluster_data, cur_nr_sectors, false,
+                                          cur_nr_sectors, false,
                                           &err) < 0) {
                     error_free(err);
                     ret = -EIO;
@@ -1604,8 +1604,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
             qemu_iovec_to_buf(&hd_qiov, 0, cluster_data, hd_qiov.size);
 
             if (qcow2_encrypt_sectors(s, sector_num, cluster_data,
-                                      cluster_data, cur_nr_sectors,
-                                      true, &err) < 0) {
+                                      cur_nr_sectors, true, &err) < 0) {
                 error_free(err);
                 ret = -EIO;
                 goto fail;
diff --git a/block/qcow2.h b/block/qcow2.h
index a063a3c..ae04285 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -540,8 +540,7 @@ int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);
 void qcow2_l2_cache_reset(BlockDriverState *bs);
 int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
 int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
-                          uint8_t *out_buf, const uint8_t *in_buf,
-                          int nb_sectors, bool enc, Error **errp);
+                          uint8_t *buf, int nb_sectors, bool enc, Error **errp);
 
 int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
     int *num, uint64_t *cluster_offset);
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (10 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21  9:54   ` Fam Zheng
  2016-02-08 18:12   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
                   ` (4 subsequent siblings)
  16 siblings, 2 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

This converts the qcow2 driver to make use of the QCryptoBlock
APIs for encrypting image content. As well as continued support
for the legacy QCow2 encryption format, the appealing benefit
is that it enables support for the LUKS format inside qcow2.

With the LUKS format it is neccessary to store the LUKS
partition header and key material in the QCow2 file. This
data can be many MB in size, so cannot go into the QCow2
header region directly. Thus the spec is defines a FDE
(Full Disk Encryption) header extension that specifies
the offset of a set of clusters to hold the FDE headers,
as well as the length of that region. The LUKS header is
thus stored in these extra allocated clusters before the
main image payload.

With this change it is now required to use the QCryptoSecret
object for providing passwords, instead of the current block
password APIs / interactive prompting.

  $QEMU \
    -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
    -drive file=/home/berrange/encrypted.qcow2,key-secret=sec0

The new LUKS format is set as the new default format when
creating encrypted images. ie

  # qemu-img create --object secret,data=123456,id=sec0 \
       -f qcow2 -o encryption,key-secret=sec0 \
       test.qcow2 10G

Results in creation of an image using the LUKS format.

For compatibility the old qcow2 AES format can still be used
via the 'encryption-format' parameter which accepts the
values 'luks' or 'qcowaes'.

  # qemu-img create --object secret,data=123456,id=sec0 \
       -f qcow2 -o encryption,key-secret=sec0,encryption-format=qcowaes \
       test.qcow2 10G

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow2-cluster.c      |  46 +----
 block/qcow2-refcount.c     |  10 +
 block/qcow2.c              | 502 ++++++++++++++++++++++++++++++++++++++-------
 block/qcow2.h              |  21 +-
 crypto/block-luks.c        |   1 -
 docs/specs/qcow2.txt       |  74 +++++++
 qapi/block-core.json       |   6 +-
 tests/qemu-iotests/049     |   2 +-
 tests/qemu-iotests/049.out |   7 +-
 tests/qemu-iotests/082.out | 189 +++++++++++++++++
 tests/qemu-iotests/087     |  28 ++-
 tests/qemu-iotests/087.out |  12 +-
 tests/qemu-iotests/134     |  18 +-
 tests/qemu-iotests/134.out |  21 +-
 tests/qemu-iotests/common  |   1 +
 15 files changed, 775 insertions(+), 163 deletions(-)

diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index f5bc4f2..0512256 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -341,45 +341,6 @@ static int count_contiguous_clusters_by_type(int nb_clusters,
     return i;
 }
 
-int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
-                          uint8_t *buf, int nb_sectors, bool enc,
-                          Error **errp)
-{
-    union {
-        uint64_t ll[2];
-        uint8_t b[16];
-    } ivec;
-    int i;
-    int ret;
-
-    for(i = 0; i < nb_sectors; i++) {
-        ivec.ll[0] = cpu_to_le64(sector_num);
-        ivec.ll[1] = 0;
-        if (qcrypto_cipher_setiv(s->cipher,
-                                 ivec.b, G_N_ELEMENTS(ivec.b),
-                                 errp) < 0) {
-            return -1;
-        }
-        if (enc) {
-            ret = qcrypto_cipher_encrypt(s->cipher,
-                                         buf, buf,
-                                         512,
-                                         errp);
-        } else {
-            ret = qcrypto_cipher_decrypt(s->cipher,
-                                         buf, buf,
-                                         512,
-                                         errp);
-        }
-        if (ret < 0) {
-            return -1;
-        }
-        sector_num++;
-        buf += 512;
-    }
-    return 0;
-}
-
 static int coroutine_fn copy_sectors(BlockDriverState *bs,
                                      uint64_t start_sect,
                                      uint64_t cluster_offset,
@@ -421,10 +382,9 @@ static int coroutine_fn copy_sectors(BlockDriverState *bs,
 
     if (bs->encrypted) {
         Error *err = NULL;
-        assert(s->cipher);
-        if (qcow2_encrypt_sectors(s, start_sect + n_start,
-                                  iov.iov_base, n,
-                                  true, &err) < 0) {
+        assert(s->crypto);
+        if (qcrypto_block_encrypt(s->crypto, start_sect + n_start,
+                                  iov.iov_base, n, &err) < 0) {
             ret = -EIO;
             error_free(err);
             goto out;
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index af493f8..d34d59b 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -1847,6 +1847,16 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
         return ret;
     }
 
+    /* encryption */
+    if (s->crypto_header.length) {
+        ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
+                            s->crypto_header.offset,
+                            s->crypto_header.length);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
     return check_refblocks(bs, res, fix, rebuild, refcount_table, nb_clusters);
 }
 
diff --git a/block/qcow2.c b/block/qcow2.c
index 2fae692..5249ca2 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -34,6 +34,8 @@
 #include "qapi-event.h"
 #include "trace.h"
 #include "qemu/option_int.h"
+#include "qapi/opts-visitor.h"
+#include "qapi-visit.h"
 
 /*
   Differences with QCOW:
@@ -60,6 +62,7 @@ typedef struct {
 #define  QCOW2_EXT_MAGIC_END 0
 #define  QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
 #define  QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
+#define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x4c554b53
 
 static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
 {
@@ -74,6 +77,75 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
 }
 
 
+static ssize_t qcow2_crypto_hdr_read_func(QCryptoBlock *block, size_t offset,
+                                          uint8_t *buf, size_t buflen,
+                                          Error **errp, void *opaque)
+{
+    BlockDriverState *bs = opaque;
+    BDRVQcow2State *s = bs->opaque;
+    ssize_t ret;
+
+    if ((offset + buflen) > s->crypto_header.length) {
+        error_setg(errp, "Request for data outside of extension header");
+        return -1;
+    }
+
+    ret = bdrv_pread(bs->file->bs,
+                     s->crypto_header.offset + offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not read encryption header");
+        return -1;
+    }
+    return ret;
+}
+
+
+static ssize_t qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
+                                          Error **errp, void *opaque)
+{
+    BlockDriverState *bs = opaque;
+    BDRVQcow2State *s = bs->opaque;
+    int64_t ret;
+
+    s->crypto_header.length = headerlen + (headerlen % s->cluster_size);
+
+    ret = qcow2_alloc_clusters(bs, s->crypto_header.length);
+    if (ret < 0) {
+        s->crypto_header.length = 0;
+        error_setg_errno(errp, -ret,
+                         "Cannot allocate cluster for LUKS header size %zu",
+                         headerlen);
+        return -1;
+    }
+
+    s->crypto_header.offset = ret;
+    return ret;
+}
+
+
+static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
+                                           const uint8_t *buf, size_t buflen,
+                                           Error **errp, void *opaque)
+{
+    BlockDriverState *bs = opaque;
+    BDRVQcow2State *s = bs->opaque;
+    ssize_t ret;
+
+    if ((offset + buflen) > s->crypto_header.length) {
+        error_setg(errp, "Request for data outside of extension header");
+        return -1;
+    }
+
+    ret = bdrv_pwrite(bs->file->bs,
+                      s->crypto_header.offset + offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not read encryption header");
+        return -1;
+    }
+    return ret;
+}
+
+
 /* 
  * read qcow2 extension and fill bs
  * start reading from start_offset
@@ -83,6 +155,7 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
  */
 static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
                                  uint64_t end_offset, void **p_feature_table,
+                                 int flags,
                                  Error **errp)
 {
     BDRVQcow2State *s = bs->opaque;
@@ -159,6 +232,39 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
             }
             break;
 
+        case QCOW2_EXT_MAGIC_CRYPTO_HEADER: {
+            unsigned int cflags = 0;
+            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+                error_setg(errp, "CRYPTO header extension only "
+                           "expected with LUKS encryption method");
+                return -EINVAL;
+            }
+            if (ext.len != sizeof(Qcow2CryptoHeaderExtension)) {
+                error_setg(errp, "CRYPTO header extension size %u, "
+                           "but expected size %zu", ext.len,
+                           sizeof(Qcow2CryptoHeaderExtension));
+                return -EINVAL;
+            }
+
+            ret = bdrv_pread(bs->file->bs, offset, &s->crypto_header, ext.len);
+            if (ret < 0) {
+                error_setg_errno(errp, -ret,
+                                 "Unable to read CRYPTO header extension");
+                return ret;
+            }
+            be64_to_cpu(s->crypto_header.offset);
+            be64_to_cpu(s->crypto_header.length);
+
+            if (flags & BDRV_O_NO_IO) {
+                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
+            }
+            s->crypto = qcrypto_block_open(s->crypto_opts,
+                                           qcow2_crypto_hdr_read_func,
+                                           bs, cflags, errp);
+            if (!s->crypto) {
+                return -EINVAL;
+            }
+        }   break;
         default:
             /* unknown magic - save it in case we need to rewrite the header */
             {
@@ -472,6 +578,11 @@ static QemuOptsList qcow2_runtime_opts = {
             .type = QEMU_OPT_NUMBER,
             .help = "Clean unused cache entries after this time (in seconds)",
         },
+        {
+            .name = QCOW2_OPT_KEY_SECRET,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
         { /* end of list */ }
     },
 };
@@ -589,6 +700,111 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
     }
 }
 
+
+static QCryptoBlockOpenOptions *
+qcow2_crypto_open_opts_init(QCryptoBlockFormat format, QemuOpts *opts,
+                            Error **errp)
+{
+    OptsVisitor *ov;
+    QCryptoBlockOpenOptions *ret;
+    Error *local_err = NULL;
+
+    ret = g_new0(QCryptoBlockOpenOptions, 1);
+    ret->format = format;
+
+    ov = opts_visitor_new(opts);
+
+    switch (format) {
+    case Q_CRYPTO_BLOCK_FORMAT_QCOW:
+        ret->u.qcow = g_new0(QCryptoBlockOptionsQCow, 1);
+        visit_type_QCryptoBlockOptionsQCow(opts_get_visitor(ov),
+                                           &ret->u.qcow,
+                                           "qcow", &local_err);
+        break;
+
+    case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+        ret->u.luks = g_new0(QCryptoBlockOptionsLUKS, 1);
+        visit_type_QCryptoBlockOptionsLUKS(opts_get_visitor(ov),
+                                           &ret->u.luks,
+                                           "luks", &local_err);
+        break;
+
+    default:
+        error_setg(&local_err, "Unsupported block format %d", format);
+        break;
+    }
+
+    if (local_err) {
+        error_propagate(errp, local_err);
+        opts_visitor_cleanup(ov);
+        qapi_free_QCryptoBlockOpenOptions(ret);
+        return NULL;
+    }
+
+    opts_visitor_cleanup(ov);
+    return ret;
+}
+
+
+static QCryptoBlockCreateOptions *
+qcow2_crypto_create_opts_init(QCryptoBlockFormat format, QemuOpts *opts,
+                              Error **errp)
+{
+    OptsVisitor *ov;
+    QCryptoBlockCreateOptions *ret;
+    Error *local_err = NULL, *end_err = NULL;
+    Visitor *v;
+
+    ret = g_new0(QCryptoBlockCreateOptions, 1);
+    ret->format = format;
+
+    ov = opts_visitor_new(opts);
+    v = opts_get_visitor(ov);
+    visit_start_struct(v, NULL, NULL, NULL, 0, &local_err);
+    if (local_err) {
+        goto cleanup;
+    }
+
+    switch (format) {
+    case Q_CRYPTO_BLOCK_FORMAT_QCOW:
+        ret->u.qcow = g_new0(QCryptoBlockOptionsQCow, 1);
+        visit_type_QCryptoBlockOptionsQCow(v, &ret->u.qcow,
+                                           "qcow", &local_err);
+        break;
+
+    case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+        ret->u.luks = g_new0(QCryptoBlockCreateOptionsLUKS, 1);
+        visit_type_QCryptoBlockCreateOptionsLUKS(v, &ret->u.luks,
+                                                 "luks", &local_err);
+        break;
+
+    default:
+        error_setg(&local_err, "Unsupported block format %d", format);
+        break;
+    }
+
+    visit_end_struct(v, &end_err);
+    if (end_err) {
+        if (!local_err) {
+            error_propagate(&local_err, end_err);
+        } else {
+            error_free(end_err);
+        }
+    }
+
+ cleanup:
+    if (local_err) {
+        error_propagate(errp, local_err);
+        qapi_free_QCryptoBlockCreateOptions(ret);
+        ret = NULL;
+        return NULL;
+    }
+
+    opts_visitor_cleanup(ov);
+    return ret;
+}
+
+
 typedef struct Qcow2ReopenState {
     Qcow2Cache *l2_table_cache;
     Qcow2Cache *refcount_block_cache;
@@ -596,6 +812,7 @@ typedef struct Qcow2ReopenState {
     int overlap_check;
     bool discard_passthrough[QCOW2_DISCARD_MAX];
     uint64_t cache_clean_interval;
+    QCryptoBlockOpenOptions *crypto_opts; /* Disk encryption runtime options */
 } Qcow2ReopenState;
 
 static int qcow2_update_options_prepare(BlockDriverState *bs,
@@ -754,6 +971,28 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     r->discard_passthrough[QCOW2_DISCARD_OTHER] =
         qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
 
+    switch (s->crypt_method_header) {
+    case QCOW_CRYPT_NONE:
+        break;
+
+    case QCOW_CRYPT_AES:
+        r->crypto_opts = qcow2_crypto_open_opts_init(
+            Q_CRYPTO_BLOCK_FORMAT_QCOW, opts, errp);
+        break;
+
+    case QCOW_CRYPT_LUKS:
+        r->crypto_opts = qcow2_crypto_open_opts_init(
+            Q_CRYPTO_BLOCK_FORMAT_LUKS, opts, errp);
+        break;
+
+    default:
+        g_assert_not_reached();
+    }
+    if (s->crypt_method_header && !r->crypto_opts) {
+        ret = -EINVAL;
+        goto fail;
+    }
+
     ret = 0;
 fail:
     qemu_opts_del(opts);
@@ -788,6 +1027,9 @@ static void qcow2_update_options_commit(BlockDriverState *bs,
         s->cache_clean_interval = r->cache_clean_interval;
         cache_clean_timer_init(bs, bdrv_get_aio_context(bs));
     }
+
+    qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
+    s->crypto_opts = r->crypto_opts;
 }
 
 static void qcow2_update_options_abort(BlockDriverState *bs,
@@ -799,6 +1041,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
     if (r->refcount_block_cache) {
         qcow2_cache_destroy(bs, r->refcount_block_cache);
     }
+    qapi_free_QCryptoBlockOpenOptions(r->crypto_opts);
 }
 
 static int qcow2_update_options(BlockDriverState *bs, QDict *options,
@@ -932,7 +1175,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     if (s->incompatible_features & ~QCOW2_INCOMPAT_MASK) {
         void *feature_table = NULL;
         qcow2_read_extensions(bs, header.header_length, ext_end,
-                              &feature_table, NULL);
+                              &feature_table, flags, NULL);
         report_unsupported_feature(bs, errp, feature_table,
                                    s->incompatible_features &
                                    ~QCOW2_INCOMPAT_MASK);
@@ -964,20 +1207,10 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     s->refcount_max = UINT64_C(1) << (s->refcount_bits - 1);
     s->refcount_max += s->refcount_max - 1;
 
-    if (header.crypt_method > QCOW_CRYPT_AES) {
-        error_setg(errp, "Unsupported encryption method: %" PRIu32,
-                   header.crypt_method);
-        ret = -EINVAL;
-        goto fail;
-    }
-    if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES_128)) {
-        error_setg(errp, "AES cipher not available");
-        ret = -EINVAL;
-        goto fail;
-    }
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
         bs->encrypted = 1;
+        bs->valid_key = 1;
     }
 
     s->l2_bits = s->cluster_bits - 3; /* L2 is always one cluster */
@@ -1104,12 +1337,37 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
 
     /* read qcow2 extensions */
     if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL,
-        &local_err)) {
+                              flags, &local_err)) {
         error_propagate(errp, local_err);
         ret = -EINVAL;
         goto fail;
     }
 
+    /* qcow2_read_extension may have setup the crypto context
+     * if the crypt method needs a header region, some methods
+     * don't need header extensions, so must check here
+     */
+    if (s->crypt_method_header && !s->crypto) {
+        if (s->crypt_method_header == QCOW_CRYPT_AES) {
+            unsigned int cflags = 0;
+            if (flags & BDRV_O_NO_IO) {
+                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
+            }
+            s->crypto = qcrypto_block_open(s->crypto_opts, NULL, NULL,
+                                           cflags, errp);
+            if (!s->crypto) {
+                error_setg(errp, "Could not setup encryption layer");
+                ret = -EINVAL;
+                goto fail;
+            }
+        } else if (!(flags & BDRV_O_NO_IO)) {
+            error_setg(errp, "Missing CRYPTO header for crypt method %d",
+                       s->crypt_method_header);
+            ret = -EINVAL;
+            goto fail;
+        }
+    }
+
     /* read the backing file name */
     if (header.backing_file_offset != 0) {
         len = header.backing_file_size;
@@ -1199,41 +1457,6 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
     bs->bl.write_zeroes_alignment = s->cluster_sectors;
 }
 
-static int qcow2_set_key(BlockDriverState *bs, const char *key)
-{
-    BDRVQcow2State *s = bs->opaque;
-    uint8_t keybuf[16];
-    int len, i;
-    Error *err = NULL;
-
-    memset(keybuf, 0, 16);
-    len = strlen(key);
-    if (len > 16)
-        len = 16;
-    /* XXX: we could compress the chars to 7 bits to increase
-       entropy */
-    for(i = 0;i < len;i++) {
-        keybuf[i] = key[i];
-    }
-    assert(bs->encrypted);
-
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = qcrypto_cipher_new(
-        QCRYPTO_CIPHER_ALG_AES_128,
-        QCRYPTO_CIPHER_MODE_CBC,
-        keybuf, G_N_ELEMENTS(keybuf),
-        &err);
-
-    if (!s->cipher) {
-        /* XXX would be nice if errors in this method could
-         * be properly propagate to the caller. Would need
-         * the bdrv_set_key() API signature to be fixed. */
-        error_free(err);
-        return -1;
-    }
-    return 0;
-}
-
 static int qcow2_reopen_prepare(BDRVReopenState *state,
                                 BlockReopenQueue *queue, Error **errp)
 {
@@ -1345,7 +1568,7 @@ static int64_t coroutine_fn qcow2_co_get_block_status(BlockDriverState *bs,
     }
 
     if (cluster_offset != 0 && ret != QCOW2_CLUSTER_COMPRESSED &&
-        !s->cipher) {
+        !s->crypto) {
         index_in_cluster = sector_num & (s->cluster_sectors - 1);
         cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
         status |= BDRV_BLOCK_OFFSET_VALID | cluster_offset;
@@ -1395,7 +1618,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
 
         /* prepare next request */
         cur_nr_sectors = remaining_sectors;
-        if (s->cipher) {
+        if (s->crypto) {
             cur_nr_sectors = MIN(cur_nr_sectors,
                 QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors);
         }
@@ -1467,7 +1690,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
             }
 
             if (bs->encrypted) {
-                assert(s->cipher);
+                assert(s->crypto);
 
                 /*
                  * For encrypted images, read everything into a temporary
@@ -1501,11 +1724,10 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
                 goto fail;
             }
             if (bs->encrypted) {
-                assert(s->cipher);
+                assert(s->crypto);
                 Error *err = NULL;
-                if (qcow2_encrypt_sectors(s, sector_num,  cluster_data,
-                                          cur_nr_sectors, false,
-                                          &err) < 0) {
+                if (qcrypto_block_decrypt(s->crypto, sector_num, cluster_data,
+                                          cur_nr_sectors, &err) < 0) {
                     error_free(err);
                     ret = -EIO;
                     goto fail;
@@ -1588,7 +1810,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
 
         if (bs->encrypted) {
             Error *err = NULL;
-            assert(s->cipher);
+            assert(s->crypto);
             if (!cluster_data) {
                 cluster_data = qemu_try_blockalign(bs->file->bs,
                                                    QCOW_MAX_CRYPT_CLUSTERS
@@ -1603,8 +1825,8 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
                    QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size);
             qemu_iovec_to_buf(&hd_qiov, 0, cluster_data, hd_qiov.size);
 
-            if (qcow2_encrypt_sectors(s, sector_num, cluster_data,
-                                      cur_nr_sectors, true, &err) < 0) {
+            if (qcrypto_block_encrypt(s->crypto, sector_num, cluster_data,
+                                      cur_nr_sectors, &err) < 0) {
                 error_free(err);
                 ret = -EIO;
                 goto fail;
@@ -1715,8 +1937,8 @@ static void qcow2_close(BlockDriverState *bs)
     qcow2_cache_destroy(bs, s->l2_table_cache);
     qcow2_cache_destroy(bs, s->refcount_block_cache);
 
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = NULL;
+    qcrypto_block_free(s->crypto);
+    s->crypto = NULL;
 
     g_free(s->unknown_header_fields);
     cleanup_unknown_header_ext(bs);
@@ -1734,7 +1956,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
 {
     BDRVQcow2State *s = bs->opaque;
     int flags = s->flags;
-    QCryptoCipher *cipher = NULL;
+    QCryptoBlock *crypto = NULL;
     QDict *options;
     Error *local_err = NULL;
     int ret;
@@ -1744,8 +1966,8 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
      * that means we don't have to worry about reopening them here.
      */
 
-    cipher = s->cipher;
-    s->cipher = NULL;
+    crypto = s->crypto;
+    s->crypto = NULL;
 
     qcow2_close(bs);
 
@@ -1769,7 +1991,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
         return;
     }
 
-    s->cipher = cipher;
+    s->crypto = crypto;
 }
 
 static size_t header_ext_add(char *buf, uint32_t magic, const void *s,
@@ -1892,6 +2114,22 @@ int qcow2_update_header(BlockDriverState *bs)
         buflen -= ret;
     }
 
+    /* Full disk encryption header pointer extension */
+    if (s->crypto_header.offset != 0) {
+        cpu_to_be64(s->crypto_header.offset);
+        cpu_to_be64(s->crypto_header.length);
+        ret = header_ext_add(buf, QCOW2_EXT_MAGIC_CRYPTO_HEADER,
+                             &s->crypto_header, sizeof(s->crypto_header),
+                             buflen);
+        be64_to_cpu(s->crypto_header.offset);
+        be64_to_cpu(s->crypto_header.length);
+        if (ret < 0) {
+            goto fail;
+        }
+        buf += ret;
+        buflen -= ret;
+    }
+
     /* Feature table */
     Qcow2Feature features[] = {
         {
@@ -1984,6 +2222,77 @@ static int qcow2_change_backing_file(BlockDriverState *bs,
     return qcow2_update_header(bs);
 }
 
+
+static int qcow2_change_encryption(BlockDriverState *bs, QemuOpts *opts,
+                                   Error **errp)
+{
+    BDRVQcow2State *s = bs->opaque;
+    const char *cryptostr;
+    QCryptoBlockCreateOptions *cryptoopts = NULL;
+    QCryptoBlock *crypto = NULL;
+    size_t i;
+    int ret = -EINVAL;
+
+    /* Default to LUKS if crypto-format is not set */
+    cryptostr = qemu_opt_get_del(opts, QCOW2_OPT_CRYPTO_FORMAT);
+    if (cryptostr) {
+        for (i = 0; i < Q_CRYPTO_BLOCK_FORMAT__MAX; i++) {
+            if (g_str_equal(QCryptoBlockFormat_lookup[i], cryptostr)) {
+                cryptoopts = qcow2_crypto_create_opts_init(i, opts, errp);
+                if (!cryptoopts) {
+                    ret = -EINVAL;
+                    goto out;
+                }
+                break;
+            }
+        }
+        if (!cryptoopts) {
+            error_setg(errp, "Unknown crypto format %s", cryptostr);
+            ret = -EINVAL;
+            goto out;
+        }
+    } else {
+        cryptoopts = qcow2_crypto_create_opts_init(
+            Q_CRYPTO_BLOCK_FORMAT_LUKS, opts, errp);
+        if (!cryptoopts) {
+            ret = -EINVAL;
+            goto out;
+        }
+    }
+    switch (cryptoopts->format) {
+    case Q_CRYPTO_BLOCK_FORMAT_QCOW:
+        s->crypt_method_header = QCOW_CRYPT_AES;
+        break;
+    case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+        s->crypt_method_header = QCOW_CRYPT_LUKS;
+        break;
+    default:
+        error_setg(errp, "Unsupported crypto format %s", cryptostr);
+        ret = -EINVAL;
+        goto out;
+    }
+
+    crypto = qcrypto_block_create(cryptoopts,
+                                  qcow2_crypto_hdr_init_func,
+                                  qcow2_crypto_hdr_write_func,
+                                  bs, errp);
+    if (!crypto) {
+        ret = -EINVAL;
+        goto out;
+    }
+
+    ret = qcow2_update_header(bs);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not write encryption header");
+        goto out;
+    }
+
+ out:
+    qapi_free_QCryptoBlockCreateOptions(cryptoopts);
+    return ret;
+}
+
+
 static int preallocate(BlockDriverState *bs)
 {
     uint64_t nb_sectors;
@@ -2177,11 +2486,8 @@ static int qcow2_create2(const char *filename, int64_t total_size,
         .header_length              = cpu_to_be32(sizeof(*header)),
     };
 
-    if (flags & BLOCK_FLAG_ENCRYPT) {
-        header->crypt_method = cpu_to_be32(QCOW_CRYPT_AES);
-    } else {
-        header->crypt_method = cpu_to_be32(QCOW_CRYPT_NONE);
-    }
+    /* We'll update this to correct value later */
+    header->crypt_method = cpu_to_be32(QCOW_CRYPT_NONE);
 
     if (flags & BLOCK_FLAG_LAZY_REFCOUNTS) {
         header->compatible_features |=
@@ -2252,6 +2558,14 @@ static int qcow2_create2(const char *filename, int64_t total_size,
         }
     }
 
+    /* Want encryption? There you go. */
+    if (flags & BLOCK_FLAG_ENCRYPT) {
+        ret = qcow2_change_encryption(bs, opts, errp);
+        if (ret < 0) {
+            goto out;
+        }
+    }
+
     /* And if we're supposed to preallocate metadata, do that now */
     if (prealloc != PREALLOC_MODE_OFF) {
         BDRVQcow2State *s = bs->opaque;
@@ -2267,11 +2581,17 @@ static int qcow2_create2(const char *filename, int64_t total_size,
     bdrv_unref(bs);
     bs = NULL;
 
-    /* Reopen the image without BDRV_O_NO_FLUSH to flush it before returning */
+    /* Reopen the image without BDRV_O_NO_FLUSH to flush it before returning
+     * Using BDRV_O_NO_IO, since encryption is now setup we don't want to
+     * have to setup decryption context. We're not doing any I/O on the top
+     * level BlockDriverState, only lower layers, where BDRV_O_NO_IO does
+     * not have effect.
+     */
     options = qdict_new();
     qdict_put(options, "driver", qstring_from_str("qcow2"));
     ret = bdrv_open(&bs, filename, NULL, options,
-                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING,
+                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING |
+                    BDRV_O_NO_IO, /* Don't want to activate encryption */
                     &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
@@ -3046,9 +3366,9 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
             backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
         } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT)) {
             encrypt = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT,
-                                        !!s->cipher);
+                                        !!s->crypto);
 
-            if (encrypt != !!s->cipher) {
+            if (encrypt != !!s->crypto) {
                 error_report("Changing the encryption flag is not supported");
                 return -ENOTSUP;
             }
@@ -3284,6 +3604,41 @@ static QemuOptsList qcow2_create_opts = {
             .help = "Width of a reference count entry in bits",
             .def_value_str = "16"
         },
+        {
+            .name = QCOW2_OPT_CRYPTO_FORMAT,
+            .type = QEMU_OPT_STRING,
+            .help = "Encryption format, 'luks' (default), 'qcow' (deprecated)",
+        },
+        {
+            .name = QCOW2_OPT_KEY_SECRET,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        {
+            .name = QCOW2_OPT_CIPHER_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm",
+        },
+        {
+            .name = QCOW2_OPT_CIPHER_MODE,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher mode",
+        },
+        {
+            .name = QCOW2_OPT_IVGEN_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator algorithm",
+        },
+        {
+            .name = QCOW2_OPT_IVGEN_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator hash algorithm",
+        },
+        {
+            .name = QCOW2_OPT_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption hash algorithm",
+        },
         { /* end of list */ }
     }
 };
@@ -3301,7 +3656,6 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_create        = qcow2_create,
     .bdrv_has_zero_init = bdrv_has_zero_init_1,
     .bdrv_co_get_block_status = qcow2_co_get_block_status,
-    .bdrv_set_key       = qcow2_set_key,
 
     .bdrv_co_readv          = qcow2_co_readv,
     .bdrv_co_writev         = qcow2_co_writev,
diff --git a/block/qcow2.h b/block/qcow2.h
index ae04285..e1f0b5b 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -25,7 +25,7 @@
 #ifndef BLOCK_QCOW2_H
 #define BLOCK_QCOW2_H
 
-#include "crypto/cipher.h"
+#include "crypto/block.h"
 #include "qemu/coroutine.h"
 
 //#define DEBUG_ALLOC
@@ -36,6 +36,7 @@
 
 #define QCOW_CRYPT_NONE 0
 #define QCOW_CRYPT_AES  1
+#define QCOW_CRYPT_LUKS 2
 
 #define QCOW_MAX_CRYPT_CLUSTERS 32
 #define QCOW_MAX_SNAPSHOTS 65536
@@ -98,6 +99,15 @@
 #define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
 #define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
 
+#define QCOW2_OPT_CRYPTO_FORMAT "encryption-format"
+#define QCOW2_OPT_KEY_SECRET "key-secret"
+#define QCOW2_OPT_CIPHER_ALG "cipher-alg"
+#define QCOW2_OPT_CIPHER_MODE "cipher-mode"
+#define QCOW2_OPT_IVGEN_ALG "ivgen-alg"
+#define QCOW2_OPT_IVGEN_HASH_ALG "ivgen-hash-alg"
+#define QCOW2_OPT_HASH_ALG "hash-alg"
+
+
 typedef struct QCowHeader {
     uint32_t magic;
     uint32_t version;
@@ -163,6 +173,11 @@ typedef struct QCowSnapshot {
 struct Qcow2Cache;
 typedef struct Qcow2Cache Qcow2Cache;
 
+typedef struct Qcow2CryptoHeaderExtension {
+    uint64_t offset;
+    uint64_t length;
+} QEMU_PACKED Qcow2CryptoHeaderExtension;
+
 typedef struct Qcow2UnknownHeaderExtension {
     uint32_t magic;
     uint32_t len;
@@ -256,7 +271,9 @@ typedef struct BDRVQcow2State {
 
     CoMutex lock;
 
-    QCryptoCipher *cipher; /* current cipher, NULL if no key yet */
+    Qcow2CryptoHeaderExtension crypto_header; /* QCow2 header extension */
+    QCryptoBlockOpenOptions *crypto_opts; /* Disk encryption runtime options */
+    QCryptoBlock *crypto; /* Disk encryption format driver */
     uint32_t crypt_method_header;
     uint64_t snapshots_offset;
     int snapshots_size;
diff --git a/crypto/block-luks.c b/crypto/block-luks.c
index 47630ee..2f4b983 100644
--- a/crypto/block-luks.c
+++ b/crypto/block-luks.c
@@ -990,7 +990,6 @@ qcrypto_block_luks_create(QCryptoBlock *block,
         cpu_to_be32s(&luks->header.key_slots[i].stripes);
     }
 
-
     /* Write out the partition header and key slot headers */
     writefunc(block, 0,
               (const uint8_t *)&luks->header,
diff --git a/docs/specs/qcow2.txt b/docs/specs/qcow2.txt
index f236d8c..4d141a6 100644
--- a/docs/specs/qcow2.txt
+++ b/docs/specs/qcow2.txt
@@ -45,6 +45,7 @@ The first cluster of a qcow2 image contains the file header:
          32 - 35:   crypt_method
                     0 for no encryption
                     1 for AES encryption
+                    2 for LUKS encryption
 
          36 - 39:   l1_size
                     Number of entries in the active L1 table
@@ -123,6 +124,7 @@ be stored. Each extension has a structure like the following:
                         0x00000000 - End of the header extension area
                         0xE2792ACA - Backing file format name
                         0x6803f857 - Feature name table
+                        0x4c554b53 - Full disk encryption header pointer
                         other      - Unknown header extension, can be safely
                                      ignored
 
@@ -166,6 +168,78 @@ the header extension data. Each entry look like this:
                     terminated if it has full length)
 
 
+== Full disk encryption header pointer ==
+
+The full disk encryption header must be present if, and only if, the
+'crypt_method' header requires metadata. Currently this is only true
+of the 'LUKS' crypt method. The header extension must be absent for
+other methods.
+
+This header provides the offset at which the crypt method can store
+its additional data, as well as the length of such data.
+
+    Byte  0 -  7:   Offset into the image file at which the encryption
+                    header starts. Must be aligned to a cluster boundary.
+    Byte  8 - 16:   Length of the encryption header. Must be a multiple
+                    of the cluster size.
+
+While the header extension allocates the length as a multiple of the
+cluster size, the encryption header may not consume the full permitted
+allocation.
+
+For the LUKS crypt method, the encryption header works as follows.
+
+The first 592 bytes of the header clusters will contain the LUKS
+partition header. This is then followed by the key material data areas.
+The size of the key material data areas is determined by the number of
+stripes in the key slot and key size. Refer to the LUKS format
+specification ('docs/on-disk-format.pdf' in the cryptsetup source
+package) for details of the LUKS partition header format.
+
+In the LUKS partition header, the "payload-offset" field does not refer
+to the offset of the QCow2 payload. Instead it simply refers to the
+total required length of the LUKS header plus key material regions.
+
+In the LUKS key slots header, the "key-material-offset" is relative
+to the start of the LUKS header clusters in the qcow2 container,
+not the start of the qcow2 file.
+
+Logically the layout looks like
+
+  +-----------------------------+
+  | QCow2 header                |
+  +-----------------------------+
+  | QCow2 header extension X    |
+  +-----------------------------+
+  | QCow2 header extension FDE  |
+  +-----------------------------+
+  | QCow2 header extension ...  |
+  +-----------------------------+
+  | QCow2 header extension Z    |
+  +-----------------------------+
+  | ....other QCow2 tables....  |
+  .                             .
+  .                             .
+  +-----------------------------+
+  | +-------------------------+ |
+  | | LUKS partition header   | |
+  | +-------------------------+ |
+  | | LUKS key material 1     | |
+  | +-------------------------+ |
+  | | LUKS key material 2     | |
+  | +-------------------------+ |
+  | | LUKS key material ...   | |
+  | +-------------------------+ |
+  | | LUKS key material 8     | |
+  | +-------------------------+ |
+  +-----------------------------+
+  | QCow2 cluster payload       |
+  .                             .
+  .                             .
+  .                             .
+  |                             |
+  +-----------------------------+
+
 == Host cluster management ==
 
 qcow2 manages the allocation of host clusters by maintaining a reference count
diff --git a/qapi/block-core.json b/qapi/block-core.json
index a194658..b704dd7 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1789,6 +1789,8 @@
 # @cache-clean-interval:  #optional clean unused entries in the L2 and refcount
 #                         caches. The interval is in seconds. The default value
 #                         is 0 and it disables this feature (since 2.5)
+# @key-secret:            #optional the ID of a QCryptoSecret object providing
+#                         the decryption key (since 2.6)
 #
 # Since: 1.7
 ##
@@ -1802,8 +1804,8 @@
             '*cache-size': 'int',
             '*l2-cache-size': 'int',
             '*refcount-cache-size': 'int',
-            '*cache-clean-interval': 'int' } }
-
+            '*cache-clean-interval': 'int',
+            '*key-secret': 'str' } }
 
 ##
 # @BlockdevOptionsArchipelago
diff --git a/tests/qemu-iotests/049 b/tests/qemu-iotests/049
index 93aa0ea..765b950 100755
--- a/tests/qemu-iotests/049
+++ b/tests/qemu-iotests/049
@@ -107,7 +107,7 @@ test_qemu_img create -f $IMGFMT -o preallocation=1234 "$TEST_IMG" 64M
 echo "== Check encryption option =="
 echo
 test_qemu_img create -f $IMGFMT -o encryption=off "$TEST_IMG" 64M
-test_qemu_img create -f $IMGFMT -o encryption=on "$TEST_IMG" 64M
+test_qemu_img create -f $IMGFMT --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 "$TEST_IMG" 64M
 
 echo "== Check lazy_refcounts option (only with v3) =="
 echo
diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out
index a2b6703..c9f0bc5 100644
--- a/tests/qemu-iotests/049.out
+++ b/tests/qemu-iotests/049.out
@@ -186,14 +186,11 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_si
 qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M
 Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
 
-qemu-img create -f qcow2 -o encryption=on TEST_DIR/t.qcow2 64M
+qemu-img create -f qcow2 --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 TEST_DIR/t.qcow2 64M
 qemu-img: Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16 key-secret=sec0
 
 == Check lazy_refcounts option (only with v3) ==
 
diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
index a952330..b0572d4 100644
--- a/tests/qemu-iotests/082.out
+++ b/tests/qemu-iotests/082.out
@@ -53,6 +53,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o ? TEST_DIR/t.qcow2 128M
@@ -66,6 +73,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o cluster_size=4k,help TEST_DIR/t.qcow2 128M
@@ -79,6 +93,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o cluster_size=4k,? TEST_DIR/t.qcow2 128M
@@ -92,6 +113,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o help,cluster_size=4k TEST_DIR/t.qcow2 128M
@@ -105,6 +133,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o ?,cluster_size=4k TEST_DIR/t.qcow2 128M
@@ -118,6 +153,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o cluster_size=4k -o help TEST_DIR/t.qcow2 128M
@@ -131,6 +173,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o cluster_size=4k -o ? TEST_DIR/t.qcow2 128M
@@ -144,6 +193,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: create -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2 128M
@@ -172,6 +228,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 
 Testing: create -o help
 Supported options:
@@ -234,6 +297,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o ? TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -247,6 +317,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o cluster_size=4k,help TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -260,6 +337,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o cluster_size=4k,? TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -273,6 +357,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o help,cluster_size=4k TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -286,6 +377,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o ?,cluster_size=4k TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -299,6 +397,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o cluster_size=4k -o help TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -312,6 +417,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o cluster_size=4k -o ? TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -325,6 +437,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: convert -O qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
@@ -353,6 +472,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 
 Testing: convert -o help
 Supported options:
@@ -412,6 +538,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o ? TEST_DIR/t.qcow2
@@ -425,6 +558,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o cluster_size=4k,help TEST_DIR/t.qcow2
@@ -438,6 +578,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o cluster_size=4k,? TEST_DIR/t.qcow2
@@ -451,6 +598,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o help,cluster_size=4k TEST_DIR/t.qcow2
@@ -464,6 +618,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o ?,cluster_size=4k TEST_DIR/t.qcow2
@@ -477,6 +638,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o cluster_size=4k -o help TEST_DIR/t.qcow2
@@ -490,6 +658,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o cluster_size=4k -o ? TEST_DIR/t.qcow2
@@ -503,6 +678,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 nocow            Turn off copy-on-write (valid only on btrfs)
 
 Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2
@@ -533,6 +715,13 @@ cluster_size     qcow2 cluster size
 preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
 lazy_refcounts   Postpone refcount updates
 refcount_bits    Width of a reference count entry in bits
+encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
+key-secret       ID of the secret that provides the encryption key
+cipher-alg       Name of encryption cipher algorithm
+cipher-mode      Name of encryption cipher mode
+ivgen-alg        Name of IV generator algorithm
+ivgen-hash-alg   Name of IV generator hash algorithm
+hash-alg         Name of encryption hash algorithm
 
 Testing: convert -o help
 Supported options:
diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
index af44299..3386668 100755
--- a/tests/qemu-iotests/087
+++ b/tests/qemu-iotests/087
@@ -184,9 +184,18 @@ echo
 echo === Encrypted image ===
 echo
 
-_make_test_img -o encryption=on $size
+_make_test_img --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 $size
 run_qemu -S <<EOF
 { "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+  "arguments": {
+      "qom-type": "secret",
+      "id": "sec0",
+      "props": {
+          "data": "123456"
+      }
+  }
+}
 { "execute": "blockdev-add",
   "arguments": {
       "options": {
@@ -195,7 +204,8 @@ run_qemu -S <<EOF
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
-        }
+        },
+        "key-secret": "sec0"
       }
     }
   }
@@ -204,6 +214,15 @@ EOF
 
 run_qemu <<EOF
 { "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+  "arguments": {
+      "qom-type": "secret",
+      "id": "sec0",
+      "props": {
+          "data": "123456"
+      }
+  }
+}
 { "execute": "blockdev-add",
   "arguments": {
       "options": {
@@ -212,7 +231,8 @@ run_qemu <<EOF
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
-        }
+        },
+        "key-secret": "sec0"
       }
     }
   }
@@ -223,7 +243,7 @@ echo
 echo === Missing driver ===
 echo
 
-_make_test_img -o encryption=on $size
+_make_test_img --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 $size
 run_qemu -S <<EOF
 { "execute": "qmp_capabilities" }
 { "execute": "blockdev-add",
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 7d62cd5..5fc4823 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -41,13 +41,11 @@ QMP_VERSION
 qemu-img: Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 Testing: -S
 QMP_VERSION
 {"return": {}}
+{"return": {}}
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
@@ -58,6 +56,7 @@ You can use 'qemu-img convert' to convert your image to an unencrypted one.
 Testing:
 QMP_VERSION
 {"return": {}}
+{"return": {}}
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
@@ -71,10 +70,7 @@ You can use 'qemu-img convert' to convert your image to an unencrypted one.
 qemu-img: Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 Testing: -S
 QMP_VERSION
 {"return": {}}
diff --git a/tests/qemu-iotests/134 b/tests/qemu-iotests/134
index 1c3820b..e0d634e 100755
--- a/tests/qemu-iotests/134
+++ b/tests/qemu-iotests/134
@@ -44,23 +44,31 @@ _supported_os Linux
 
 
 size=128M
-IMGOPTS="encryption=on" _make_test_img $size
+
+SECRET="secret,id=sec0,data=astrochicken"
+SECRETALT="secret,id=sec0,data=platypus"
+
+_make_test_img --object $SECRET -o "encryption=on,key-secret=sec0" $size
+
+IMGSPEC="driver=$IMGFMT,file=$TEST_IMG,key-secret=sec0"
+
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
 
 echo
 echo "== reading whole image =="
-echo "astrochicken" | $QEMU_IO -c "read 0 $size" "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET --image-opts -c "read 0 $size" $IMGSPEC | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== rewriting whole image =="
-echo "astrochicken" | $QEMU_IO -c "write -P 0xa 0 $size" "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET --image-opts -c "write -P 0xa 0 $size" $IMGSPEC | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== verify pattern =="
-echo "astrochicken" | $QEMU_IO -c "read -P 0xa 0 $size" "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET --image-opts -c "read -P 0xa 0 $size" $IMGSPEC | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== verify pattern failure with wrong password =="
-echo "platypus" | $QEMU_IO -c "read -P 0xa 0 $size" "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRETALT --image-opts -c "read -P 0xa 0 $size" $IMGSPEC | _filter_qemu_io | _filter_testdir
 
 
 # success, all done
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index a16acb8..e9bf302 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -2,17 +2,12 @@ QA output created by 134
 qemu-img: Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 
 == reading whole image ==
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Disk image 'TEST_DIR/t.qcow2' is encrypted.
-password:
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
@@ -20,8 +15,6 @@ read 134217728/134217728 bytes at offset 0
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Disk image 'TEST_DIR/t.qcow2' is encrypted.
-password:
 wrote 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
@@ -29,18 +22,10 @@ wrote 134217728/134217728 bytes at offset 0
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Disk image 'TEST_DIR/t.qcow2' is encrypted.
-password:
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 == verify pattern failure with wrong password ==
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
-Disk image 'TEST_DIR/t.qcow2' is encrypted.
-password:
-Pattern verification failed at offset 0, 134217728 bytes
-read 134217728/134217728 bytes at offset 0
-128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+can't open device /home/berrange/src/virt/qemu/tests/qemu-iotests/scratch/t.qcow2: Invalid password, cannot unlock any keyslot
+no file open, try 'help open'
 *** done
diff --git a/tests/qemu-iotests/common b/tests/qemu-iotests/common
index ff84f4b..f65d04c 100644
--- a/tests/qemu-iotests/common
+++ b/tests/qemu-iotests/common
@@ -398,6 +398,7 @@ BEGIN        { for (t='$start'; t<='$end'; t++) printf "%03d\n",t }' \
 done
 
 # Set qemu-io cache mode with $CACHEMODE we have
+QEMU_IO_OPTIONS_NO_FMT="$QEMU_IO_OPTIONS --cache $CACHEMODE"
 QEMU_IO_OPTIONS="$QEMU_IO_OPTIONS -f $IMGFMT --cache $CACHEMODE"
 
 # Set default options for qemu-img create -o if they were not specified
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (11 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-08 20:30   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
                   ` (3 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Instead of requiring separate input/output buffers for
encrypting data, change encrypt_sectors() to assume
use of a single buffer, encrypting in place. One current
caller all uses the same buffer for input/output already
and the other two callers are easily converted todo so.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow.c | 31 ++++++++++---------------------
 1 file changed, 10 insertions(+), 21 deletions(-)

diff --git a/block/qcow.c b/block/qcow.c
index 635085e..5a4f310 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -293,12 +293,9 @@ static int qcow_set_key(BlockDriverState *bs, const char *key)
     return 0;
 }
 
-/* The crypt function is compatible with the linux cryptoloop
-   algorithm for < 4 GB images. NOTE: out_buf == in_buf is
-   supported */
 static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
-                           uint8_t *out_buf, const uint8_t *in_buf,
-                           int nb_sectors, bool enc, Error **errp)
+                           uint8_t *buf, int nb_sectors, bool enc,
+                           Error **errp)
 {
     union {
         uint64_t ll[2];
@@ -317,14 +314,12 @@ static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
         }
         if (enc) {
             ret = qcrypto_cipher_encrypt(s->cipher,
-                                         in_buf,
-                                         out_buf,
+                                         buf, buf,
                                          512,
                                          errp);
         } else {
             ret = qcrypto_cipher_decrypt(s->cipher,
-                                         in_buf,
-                                         out_buf,
+                                         buf, buf,
                                          512,
                                          errp);
         }
@@ -332,8 +327,7 @@ static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
             return -1;
         }
         sector_num++;
-        in_buf += 512;
-        out_buf += 512;
+        buf += 512;
     }
     return 0;
 }
@@ -453,13 +447,12 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
                     uint64_t start_sect;
                     assert(s->cipher);
                     start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
-                    memset(s->cluster_data + 512, 0x00, 512);
+                    memset(s->cluster_data, 0x00, 512);
                     for(i = 0; i < s->cluster_sectors; i++) {
                         if (i < n_start || i >= n_end) {
                             Error *err = NULL;
                             if (encrypt_sectors(s, start_sect + i,
-                                                s->cluster_data,
-                                                s->cluster_data + 512, 1,
+                                                s->cluster_data, 1,
                                                 true, &err) < 0) {
                                 error_free(err);
                                 errno = EIO;
@@ -637,7 +630,7 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
             }
             if (bs->encrypted) {
                 assert(s->cipher);
-                if (encrypt_sectors(s, sector_num, buf, buf,
+                if (encrypt_sectors(s, sector_num, buf,
                                     n, false, &err) < 0) {
                     goto fail;
                 }
@@ -672,7 +665,6 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
     BDRVQcowState *s = bs->opaque;
     int index_in_cluster;
     uint64_t cluster_offset;
-    const uint8_t *src_buf;
     int ret = 0, n;
     uint8_t *cluster_data = NULL;
     struct iovec hd_iov;
@@ -715,18 +707,15 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
             if (!cluster_data) {
                 cluster_data = g_malloc0(s->cluster_size);
             }
-            if (encrypt_sectors(s, sector_num, cluster_data, buf,
+            if (encrypt_sectors(s, sector_num, buf,
                                 n, true, &err) < 0) {
                 error_free(err);
                 ret = -EIO;
                 break;
             }
-            src_buf = cluster_data;
-        } else {
-            src_buf = buf;
         }
 
-        hd_iov.iov_base = (void *)src_buf;
+        hd_iov.iov_base = (void *)buf;
         hd_iov.iov_len = n * 512;
         qemu_iovec_init_external(&hd_qiov, &hd_iov, 1);
         qemu_co_mutex_unlock(&s->lock);
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (12 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-08 20:57   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting Daniel P. Berrange
                   ` (2 subsequent siblings)
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

This converts the qcow2 driver to make use of the QCryptoBlock
APIs for encrypting image content. This is only wired up to
permit use of the legacy QCow encryption format. Users who wish
to have the strong LUKS format should switch to qcow2 instead.

With this change it is now required to use the QCryptoSecret
object for providing passwords, instead of the current block
password APIs / interactive prompting.

  $QEMU \
    -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
    -drive file=/home/berrange/encrypted.qcow,key-secret=sec0

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow.c         | 173 +++++++++++++++++++++++----------------------------
 qapi/block-core.json |  17 ++++-
 2 files changed, 93 insertions(+), 97 deletions(-)

diff --git a/block/qcow.c b/block/qcow.c
index 5a4f310..3b7c2b9 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -26,7 +26,9 @@
 #include "qemu/module.h"
 #include <zlib.h>
 #include "qapi/qmp/qerror.h"
-#include "crypto/cipher.h"
+#include "crypto/block.h"
+#include "qapi/opts-visitor.h"
+#include "qapi-visit.h"
 #include "migration/migration.h"
 
 /**************************************************************/
@@ -40,6 +42,8 @@
 
 #define QCOW_OFLAG_COMPRESSED (1LL << 63)
 
+#define QCOW_OPT_KEY_SECRET "key-secret"
+
 typedef struct QCowHeader {
     uint32_t magic;
     uint32_t version;
@@ -72,7 +76,7 @@ typedef struct BDRVQcowState {
     uint8_t *cluster_cache;
     uint8_t *cluster_data;
     uint64_t cluster_cache_offset;
-    QCryptoCipher *cipher; /* NULL if no key yet */
+    QCryptoBlock *crypto; /* Disk encryption format driver */
     uint32_t crypt_method_header;
     CoMutex lock;
     Error *migration_blocker;
@@ -92,6 +96,19 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)
         return 0;
 }
 
+static QemuOptsList qcow_runtime_opts = {
+    .name = "qcow",
+    .head = QTAILQ_HEAD_INITIALIZER(qcow_runtime_opts.head),
+    .desc = {
+        {
+            .name = QCOW_OPT_KEY_SECRET,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        { /* end of list */ }
+    },
+};
+
 static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
                      Error **errp)
 {
@@ -99,6 +116,11 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     unsigned int len, i, shift;
     int ret;
     QCowHeader header;
+    QemuOpts *opts = NULL;
+    Error *local_err = NULL;
+    QCryptoBlockOpenOptions *crypto_opts = NULL;
+    unsigned int cflags = 0;
+    OptsVisitor *ov;
 
     ret = bdrv_pread(bs->file->bs, 0, &header, sizeof(header));
     if (ret < 0) {
@@ -147,19 +169,50 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
-    if (header.crypt_method > QCOW_CRYPT_AES) {
-        error_setg(errp, "invalid encryption method in qcow header");
-        ret = -EINVAL;
-        goto fail;
-    }
-    if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES_128)) {
-        error_setg(errp, "AES cipher not available");
+    opts = qemu_opts_create(&qcow_runtime_opts, NULL, 0, &error_abort);
+    qemu_opts_absorb_qdict(opts, options, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
         ret = -EINVAL;
         goto fail;
     }
+
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
+        if (s->crypt_method_header == QCOW_CRYPT_AES) {
+            ov = opts_visitor_new(opts);
+
+            crypto_opts = g_new0(QCryptoBlockOpenOptions, 1);
+            crypto_opts->format = Q_CRYPTO_BLOCK_FORMAT_QCOW;
+            crypto_opts->u.qcow = g_new0(QCryptoBlockOptionsQCow, 1);
+            visit_type_QCryptoBlockOptionsQCow(opts_get_visitor(ov),
+                                               &crypto_opts->u.qcow,
+                                               "qcow", &local_err);
+            if (local_err) {
+                error_propagate(errp, local_err);
+                opts_visitor_cleanup(ov);
+                ret = -EINVAL;
+                goto fail;
+            }
+            opts_visitor_cleanup(ov);
+
+            if (flags & BDRV_O_NO_IO) {
+                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
+            }
+            s->crypto = qcrypto_block_open(crypto_opts, NULL, NULL,
+                                           cflags, errp);
+            if (!s->crypto) {
+                error_setg(errp, "Could not setup encryption layer");
+                ret = -EINVAL;
+                goto fail;
+            }
+        } else {
+            error_setg(errp, "invalid encryption method in qcow header");
+            ret = -EINVAL;
+            goto fail;
+        }
         bs->encrypted = 1;
+        bs->valid_key = 1;
     }
     s->cluster_bits = header.cluster_bits;
     s->cluster_size = 1 << s->cluster_bits;
@@ -238,6 +291,7 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
                bdrv_get_device_or_node_name(bs));
     migrate_add_blocker(s->migration_blocker);
 
+    qapi_free_QCryptoBlockOpenOptions(crypto_opts);
     qemu_co_mutex_init(&s->lock);
     return 0;
 
@@ -246,6 +300,7 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     qemu_vfree(s->l2_cache);
     g_free(s->cluster_cache);
     g_free(s->cluster_data);
+    qapi_free_QCryptoBlockOpenOptions(crypto_opts);
     return ret;
 }
 
@@ -258,79 +313,6 @@ static int qcow_reopen_prepare(BDRVReopenState *state,
     return 0;
 }
 
-static int qcow_set_key(BlockDriverState *bs, const char *key)
-{
-    BDRVQcowState *s = bs->opaque;
-    uint8_t keybuf[16];
-    int len, i;
-    Error *err;
-
-    memset(keybuf, 0, 16);
-    len = strlen(key);
-    if (len > 16)
-        len = 16;
-    /* XXX: we could compress the chars to 7 bits to increase
-       entropy */
-    for(i = 0;i < len;i++) {
-        keybuf[i] = key[i];
-    }
-    assert(bs->encrypted);
-
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = qcrypto_cipher_new(
-        QCRYPTO_CIPHER_ALG_AES_128,
-        QCRYPTO_CIPHER_MODE_CBC,
-        keybuf, G_N_ELEMENTS(keybuf),
-        &err);
-
-    if (!s->cipher) {
-        /* XXX would be nice if errors in this method could
-         * be properly propagate to the caller. Would need
-         * the bdrv_set_key() API signature to be fixed. */
-        error_free(err);
-        return -1;
-    }
-    return 0;
-}
-
-static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
-                           uint8_t *buf, int nb_sectors, bool enc,
-                           Error **errp)
-{
-    union {
-        uint64_t ll[2];
-        uint8_t b[16];
-    } ivec;
-    int i;
-    int ret;
-
-    for(i = 0; i < nb_sectors; i++) {
-        ivec.ll[0] = cpu_to_le64(sector_num);
-        ivec.ll[1] = 0;
-        if (qcrypto_cipher_setiv(s->cipher,
-                                 ivec.b, G_N_ELEMENTS(ivec.b),
-                                 errp) < 0) {
-            return -1;
-        }
-        if (enc) {
-            ret = qcrypto_cipher_encrypt(s->cipher,
-                                         buf, buf,
-                                         512,
-                                         errp);
-        } else {
-            ret = qcrypto_cipher_decrypt(s->cipher,
-                                         buf, buf,
-                                         512,
-                                         errp);
-        }
-        if (ret < 0) {
-            return -1;
-        }
-        sector_num++;
-        buf += 512;
-    }
-    return 0;
-}
 
 /* 'allocate' is:
  *
@@ -445,15 +427,15 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
                 if (bs->encrypted &&
                     (n_end - n_start) < s->cluster_sectors) {
                     uint64_t start_sect;
-                    assert(s->cipher);
+                    assert(s->crypto);
                     start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
                     memset(s->cluster_data, 0x00, 512);
                     for(i = 0; i < s->cluster_sectors; i++) {
                         if (i < n_start || i >= n_end) {
                             Error *err = NULL;
-                            if (encrypt_sectors(s, start_sect + i,
-                                                s->cluster_data, 1,
-                                                true, &err) < 0) {
+                            if (qcrypto_block_encrypt(s->crypto, start_sect + i,
+                                                      s->cluster_data, 1,
+                                                      &err) < 0) {
                                 error_free(err);
                                 errno = EIO;
                                 return -1;
@@ -498,7 +480,7 @@ static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
     if (!cluster_offset) {
         return 0;
     }
-    if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->cipher) {
+    if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->crypto) {
         return BDRV_BLOCK_DATA;
     }
     cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
@@ -629,9 +611,9 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
                 break;
             }
             if (bs->encrypted) {
-                assert(s->cipher);
-                if (encrypt_sectors(s, sector_num, buf,
-                                    n, false, &err) < 0) {
+                assert(s->crypto);
+                if (qcrypto_block_decrypt(s->crypto, sector_num, buf,
+                                          n, &err) < 0) {
                     goto fail;
                 }
             }
@@ -703,12 +685,12 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
         }
         if (bs->encrypted) {
             Error *err = NULL;
-            assert(s->cipher);
+            assert(s->crypto);
             if (!cluster_data) {
                 cluster_data = g_malloc0(s->cluster_size);
             }
-            if (encrypt_sectors(s, sector_num, buf,
-                                n, true, &err) < 0) {
+            if (qcrypto_block_encrypt(s->crypto, sector_num, buf,
+                                      n, &err) < 0) {
                 error_free(err);
                 ret = -EIO;
                 break;
@@ -746,8 +728,8 @@ static void qcow_close(BlockDriverState *bs)
 {
     BDRVQcowState *s = bs->opaque;
 
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = NULL;
+    qcrypto_block_free(s->crypto);
+    s->crypto = NULL;
     g_free(s->l1_table);
     qemu_vfree(s->l2_cache);
     g_free(s->cluster_cache);
@@ -1012,7 +994,6 @@ static BlockDriver bdrv_qcow = {
     .bdrv_co_writev         = qcow_co_writev,
     .bdrv_co_get_block_status   = qcow_co_get_block_status,
 
-    .bdrv_set_key           = qcow_set_key,
     .bdrv_make_empty        = qcow_make_empty,
     .bdrv_write_compressed  = qcow_write_compressed,
     .bdrv_get_info          = qcow_get_info,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index b704dd7..e0d423f 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1756,6 +1756,21 @@
             'mode':  'Qcow2OverlapCheckMode' } }
 
 ##
+# @BlockdevOptionsQcow
+#
+# Driver specific block device options for qcow.
+#
+# @key-secret:   #optional ID of the "secret" object providing the
+#                AES decryption key.
+#
+# Since: 2.6
+##
+{ 'struct': 'BlockdevOptionsQcow',
+  'base': 'BlockdevOptionsGenericCOWFormat',
+  'data': { '*key-secret': 'str' } }
+
+
+##
 # @BlockdevOptionsQcow2
 #
 # Driver specific block device options for qcow2.
@@ -2024,7 +2039,7 @@
       'null-co':    'BlockdevOptionsNull',
       'parallels':  'BlockdevOptionsGenericFormat',
       'qcow2':      'BlockdevOptionsQcow2',
-      'qcow':       'BlockdevOptionsGenericCOWFormat',
+      'qcow':       'BlockdevOptionsQcow',
       'qed':        'BlockdevOptionsGenericCOWFormat',
       'quorum':     'BlockdevOptionsQuorum',
       'raw':        'BlockdevOptionsGenericFormat',
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (13 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-01-21 13:02   ` Fam Zheng
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs Daniel P. Berrange
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption Daniel P. Berrange
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Now that qcow & qcow2 are wired up to get encryption keys
via the QCryptoSecret object, nothing is relying on the
interactive prompting for passwords. All the code related
to password prompting can thus be ripped out.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 hmp.c                        | 31 --------------------
 hw/usb/dev-storage.c         | 34 ----------------------
 include/monitor/monitor.h    |  7 -----
 include/qemu/osdep.h         |  2 --
 monitor.c                    | 68 --------------------------------------------
 qemu-img.c                   | 15 ----------
 qemu-io.c                    | 21 --------------
 qmp.c                        |  9 ------
 tests/qemu-iotests/087       |  2 ++
 tests/qemu-iotests/087.out   |  4 +--
 tests/qemu-iotests/common.rc |  4 +--
 util/oslib-posix.c           | 66 ------------------------------------------
 util/oslib-win32.c           | 24 ----------------
 13 files changed, 6 insertions(+), 281 deletions(-)

diff --git a/hmp.c b/hmp.c
index 95930b0..bfd42d0 100644
--- a/hmp.c
+++ b/hmp.c
@@ -966,37 +966,12 @@ void hmp_ringbuf_read(Monitor *mon, const QDict *qdict)
     g_free(data);
 }
 
-static void hmp_cont_cb(void *opaque, int err)
-{
-    if (!err) {
-        qmp_cont(NULL);
-    }
-}
-
-static bool key_is_missing(const BlockInfo *bdev)
-{
-    return (bdev->inserted && bdev->inserted->encryption_key_missing);
-}
-
 void hmp_cont(Monitor *mon, const QDict *qdict)
 {
-    BlockInfoList *bdev_list, *bdev;
     Error *err = NULL;
 
-    bdev_list = qmp_query_block(NULL);
-    for (bdev = bdev_list; bdev; bdev = bdev->next) {
-        if (key_is_missing(bdev->value)) {
-            monitor_read_block_device_key(mon, bdev->value->device,
-                                          hmp_cont_cb, NULL);
-            goto out;
-        }
-    }
-
     qmp_cont(&err);
     hmp_handle_error(mon, &err);
-
-out:
-    qapi_free_BlockInfoList(bdev_list);
 }
 
 void hmp_system_wakeup(Monitor *mon, const QDict *qdict)
@@ -1377,12 +1352,6 @@ void hmp_change(Monitor *mon, const QDict *qdict)
 
         qmp_blockdev_change_medium(device, target, !!arg, arg,
                                    !!read_only, read_only_mode, &err);
-        if (err &&
-            error_get_class(err) == ERROR_CLASS_DEVICE_ENCRYPTED) {
-            error_free(err);
-            monitor_read_block_device_key(mon, device, NULL, NULL);
-            return;
-        }
     }
 
     hmp_handle_error(mon, &err);
diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c
index 597d8fd..2122f4f 100644
--- a/hw/usb/dev-storage.c
+++ b/hw/usb/dev-storage.c
@@ -553,21 +553,6 @@ static void usb_msd_handle_data(USBDevice *dev, USBPacket *p)
     }
 }
 
-static void usb_msd_password_cb(void *opaque, int err)
-{
-    MSDState *s = opaque;
-    Error *local_err = NULL;
-
-    if (!err) {
-        usb_device_attach(&s->dev, &local_err);
-    }
-
-    if (local_err) {
-        error_report_err(local_err);
-        qdev_unplug(&s->dev.qdev, NULL);
-    }
-}
-
 static void *usb_msd_load_request(QEMUFile *f, SCSIRequest *req)
 {
     MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent);
@@ -613,25 +598,6 @@ static void usb_msd_realize_storage(USBDevice *dev, Error **errp)
         return;
     }
 
-    if (blk_bs(blk)) {
-        bdrv_add_key(blk_bs(blk), NULL, &err);
-        if (err) {
-            if (monitor_cur_is_qmp()) {
-                error_propagate(errp, err);
-                return;
-            }
-            error_free(err);
-            err = NULL;
-            if (cur_mon) {
-                monitor_read_bdrv_key_start(cur_mon, blk_bs(blk),
-                                            usb_msd_password_cb, s);
-                s->dev.auto_attach = 0;
-            } else {
-                autostart = 0;
-            }
-        }
-    }
-
     blkconf_serial(&s->conf, &dev->serial);
     blkconf_blocksizes(&s->conf);
 
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index aa0f373..cd38020 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -21,13 +21,6 @@ void monitor_init(CharDriverState *chr, int flags);
 int monitor_suspend(Monitor *mon);
 void monitor_resume(Monitor *mon);
 
-int monitor_read_bdrv_key_start(Monitor *mon, BlockDriverState *bs,
-                                BlockCompletionFunc *completion_cb,
-                                void *opaque);
-int monitor_read_block_device_key(Monitor *mon, const char *device,
-                                  BlockCompletionFunc *completion_cb,
-                                  void *opaque);
-
 int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp);
 int monitor_fd_param(Monitor *mon, const char *fdname, Error **errp);
 
diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
index 59a7f8d..3420ac6 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
@@ -301,8 +301,6 @@ void qemu_set_tty_echo(int fd, bool echo);
 
 void os_mem_prealloc(int fd, char *area, size_t sz);
 
-int qemu_read_password(char *buf, int buf_size);
-
 /**
  * qemu_fork:
  *
diff --git a/monitor.c b/monitor.c
index c53a453..198b85f 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4138,74 +4138,6 @@ void monitor_init(CharDriverState *chr, int flags)
     qemu_mutex_unlock(&monitor_lock);
 }
 
-static void bdrv_password_cb(void *opaque, const char *password,
-                             void *readline_opaque)
-{
-    Monitor *mon = opaque;
-    BlockDriverState *bs = readline_opaque;
-    int ret = 0;
-    Error *local_err = NULL;
-
-    bdrv_add_key(bs, password, &local_err);
-    if (local_err) {
-        error_report_err(local_err);
-        ret = -EPERM;
-    }
-    if (mon->password_completion_cb)
-        mon->password_completion_cb(mon->password_opaque, ret);
-
-    monitor_read_command(mon, 1);
-}
-
-int monitor_read_bdrv_key_start(Monitor *mon, BlockDriverState *bs,
-                                BlockCompletionFunc *completion_cb,
-                                void *opaque)
-{
-    int err;
-
-    monitor_printf(mon, "%s (%s) is encrypted.\n", bdrv_get_device_name(bs),
-                   bdrv_get_encrypted_filename(bs));
-
-    mon->password_completion_cb = completion_cb;
-    mon->password_opaque = opaque;
-
-    err = monitor_read_password(mon, bdrv_password_cb, bs);
-
-    if (err && completion_cb)
-        completion_cb(opaque, err);
-
-    return err;
-}
-
-int monitor_read_block_device_key(Monitor *mon, const char *device,
-                                  BlockCompletionFunc *completion_cb,
-                                  void *opaque)
-{
-    Error *err = NULL;
-    BlockBackend *blk;
-
-    blk = blk_by_name(device);
-    if (!blk) {
-        monitor_printf(mon, "Device not found %s\n", device);
-        return -1;
-    }
-    if (!blk_bs(blk)) {
-        monitor_printf(mon, "Device '%s' has no medium\n", device);
-        return -1;
-    }
-
-    bdrv_add_key(blk_bs(blk), NULL, &err);
-    if (err) {
-        error_free(err);
-        return monitor_read_bdrv_key_start(mon, blk_bs(blk), completion_cb, opaque);
-    }
-
-    if (completion_cb) {
-        completion_cb(opaque, 0);
-    }
-    return 0;
-}
-
 QemuOptsList qemu_mon_opts = {
     .name = "mon",
     .implied_opt_name = "chardev",
diff --git a/qemu-img.c b/qemu-img.c
index faf4dbd..e99c000 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -270,8 +270,6 @@ static BlockBackend *img_open_file(const char *id, const char *filename,
                                    bool quiet)
 {
     BlockBackend *blk;
-    BlockDriverState *bs;
-    char password[256];
     Error *local_err = NULL;
     QDict *options = NULL;
 
@@ -286,19 +284,6 @@ static BlockBackend *img_open_file(const char *id, const char *filename,
         goto fail;
     }
 
-    bs = blk_bs(blk);
-    if (bdrv_is_encrypted(bs) && bdrv_key_required(bs) &&
-        !(flags & BDRV_O_NO_IO)) {
-        qprintf(quiet, "Disk image '%s' is encrypted.\n", filename);
-        if (qemu_read_password(password, sizeof(password)) < 0) {
-            error_report("No password given");
-            goto fail;
-        }
-        if (bdrv_set_key(bs, password) < 0) {
-            error_report("invalid password");
-            goto fail;
-        }
-    }
     return blk;
 fail:
     blk_unref(blk);
diff --git a/qemu-io.c b/qemu-io.c
index 98b5cc2..6ae59f0 100644
--- a/qemu-io.c
+++ b/qemu-io.c
@@ -56,7 +56,6 @@ static const cmdinfo_t close_cmd = {
 static int openfile(char *name, int flags, QDict *opts)
 {
     Error *local_err = NULL;
-    BlockDriverState *bs;
 
     if (qemuio_blk) {
         error_report("file open already, try 'help close'");
@@ -71,27 +70,7 @@ static int openfile(char *name, int flags, QDict *opts)
         return 1;
     }
 
-    bs = blk_bs(qemuio_blk);
-    if (bdrv_is_encrypted(bs) && bdrv_key_required(bs)) {
-        char password[256];
-        printf("Disk image '%s' is encrypted.\n", name);
-        if (qemu_read_password(password, sizeof(password)) < 0) {
-            error_report("No password given");
-            goto error;
-        }
-        if (bdrv_set_key(bs, password) < 0) {
-            error_report("invalid password");
-            goto error;
-        }
-    }
-
-
     return 0;
-
- error:
-    blk_unref(qemuio_blk);
-    qemuio_blk = NULL;
-    return 1;
 }
 
 static void open_help(void)
diff --git a/qmp.c b/qmp.c
index c5d9be7..effc5b8 100644
--- a/qmp.c
+++ b/qmp.c
@@ -170,9 +170,7 @@ SpiceInfo *qmp_query_spice(Error **errp)
 
 void qmp_cont(Error **errp)
 {
-    Error *local_err = NULL;
     BlockBackend *blk;
-    BlockDriverState *bs;
 
     if (runstate_needs_reset()) {
         error_setg(errp, "Resetting the Virtual Machine is required");
@@ -184,13 +182,6 @@ void qmp_cont(Error **errp)
     for (blk = blk_next(NULL); blk; blk = blk_next(blk)) {
         blk_iostatus_reset(blk);
     }
-    for (bs = bdrv_next(NULL); bs; bs = bdrv_next(bs)) {
-        bdrv_add_key(bs, NULL, &local_err);
-        if (local_err) {
-            error_propagate(errp, local_err);
-            return;
-        }
-    }
 
     if (runstate_check(RUN_STATE_INMIGRATE)) {
         autostart = 1;
diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
index 3386668..065d9af 100755
--- a/tests/qemu-iotests/087
+++ b/tests/qemu-iotests/087
@@ -201,6 +201,7 @@ run_qemu -S <<EOF
       "options": {
         "driver": "$IMGFMT",
         "id": "disk",
+        "key-secret": "sec0",
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
@@ -228,6 +229,7 @@ run_qemu <<EOF
       "options": {
         "driver": "$IMGFMT",
         "id": "disk",
+        "key-secret": "sec0",
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 5fc4823..6582dda 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -49,7 +49,7 @@ QMP_VERSION
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-{"error": {"class": "GenericError", "desc": "blockdev-add doesn't support encrypted devices"}}
+{"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
 
@@ -60,7 +60,7 @@ QMP_VERSION
 Encrypted images are deprecated
 Support for them will be removed in a future release.
 You can use 'qemu-img convert' to convert your image to an unencrypted one.
-{"error": {"class": "GenericError", "desc": "Guest must be stopped for opening of encrypted image"}}
+{"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
 
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index d9913f8..da78459 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -135,9 +135,9 @@ _make_test_img()
     # XXX(hch): have global image options?
     (
      if [ $use_backing = 1 ]; then
-        $QEMU_IMG create -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
+        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
      else
-        $QEMU_IMG create -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
+        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
      fi
     ) | _filter_img_create
 
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
index d25f671..e2c02f6 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
@@ -384,72 +384,6 @@ void os_mem_prealloc(int fd, char *area, size_t memory)
 }
 
 
-static struct termios oldtty;
-
-static void term_exit(void)
-{
-    tcsetattr(0, TCSANOW, &oldtty);
-}
-
-static void term_init(void)
-{
-    struct termios tty;
-
-    tcgetattr(0, &tty);
-    oldtty = tty;
-
-    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
-                          |INLCR|IGNCR|ICRNL|IXON);
-    tty.c_oflag |= OPOST;
-    tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
-    tty.c_cflag &= ~(CSIZE|PARENB);
-    tty.c_cflag |= CS8;
-    tty.c_cc[VMIN] = 1;
-    tty.c_cc[VTIME] = 0;
-
-    tcsetattr(0, TCSANOW, &tty);
-
-    atexit(term_exit);
-}
-
-int qemu_read_password(char *buf, int buf_size)
-{
-    uint8_t ch;
-    int i, ret;
-
-    printf("password: ");
-    fflush(stdout);
-    term_init();
-    i = 0;
-    for (;;) {
-        ret = read(0, &ch, 1);
-        if (ret == -1) {
-            if (errno == EAGAIN || errno == EINTR) {
-                continue;
-            } else {
-                break;
-            }
-        } else if (ret == 0) {
-            ret = -1;
-            break;
-        } else {
-            if (ch == '\r' ||
-                ch == '\n') {
-                ret = 0;
-                break;
-            }
-            if (i < (buf_size - 1)) {
-                buf[i++] = ch;
-            }
-        }
-    }
-    term_exit();
-    buf[i] = '\0';
-    printf("\n");
-    return ret;
-}
-
-
 pid_t qemu_fork(Error **errp)
 {
     sigset_t oldmask, newmask;
diff --git a/util/oslib-win32.c b/util/oslib-win32.c
index 6a47019..6ccc952 100644
--- a/util/oslib-win32.c
+++ b/util/oslib-win32.c
@@ -474,30 +474,6 @@ void os_mem_prealloc(int fd, char *area, size_t memory)
 }
 
 
-/* XXX: put correct support for win32 */
-int qemu_read_password(char *buf, int buf_size)
-{
-    int c, i;
-
-    printf("Password: ");
-    fflush(stdout);
-    i = 0;
-    for (;;) {
-        c = getchar();
-        if (c < 0) {
-            buf[i] = '\0';
-            return -1;
-        } else if (c == '\n') {
-            break;
-        } else if (i < (buf_size - 1)) {
-            buf[i++] = c;
-        }
-    }
-    buf[i] = '\0';
-    return 0;
-}
-
-
 pid_t qemu_fork(Error **errp)
 {
     errno = ENOSYS;
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (14 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-08 21:23   ` Eric Blake
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption Daniel P. Berrange
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Now that all encryption keys must be provided upfront via
the QCryptoSecret API and associated block driver properties
there is no need for any explicit encryption handling APIs
in the block layer. Encryption can be handled transparently
within the block driver. We only retain an API for querying
whether an image is encrypted or not, since that is a
potentially useful piece of metadata to report to the user.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block.c                   | 82 ++---------------------------------------------
 block/crypto.c            |  1 -
 block/qapi.c              |  2 +-
 block/qcow.c              |  1 -
 block/qcow2.c             |  1 -
 blockdev.c                | 40 ++---------------------
 include/block/block.h     |  4 ---
 include/block/block_int.h |  1 -
 8 files changed, 5 insertions(+), 127 deletions(-)

diff --git a/block.c b/block.c
index 0806d4f..5403355 100644
--- a/block.c
+++ b/block.c
@@ -1693,17 +1693,8 @@ static int bdrv_open_inherit(BlockDriverState **pbs, const char *filename,
         goto close_and_fail;
     }
 
-    if (!bdrv_key_required(bs)) {
-        if (bs->blk) {
-            blk_dev_change_media_cb(bs->blk, true);
-        }
-    } else if (!runstate_check(RUN_STATE_PRELAUNCH)
-               && !runstate_check(RUN_STATE_INMIGRATE)
-               && !runstate_check(RUN_STATE_PAUSED)) { /* HACK */
-        error_setg(errp,
-                   "Guest must be stopped for opening of encrypted image");
-        ret = -EBUSY;
-        goto close_and_fail;
+    if (bs->blk) {
+        blk_dev_change_media_cb(bs->blk, true);
     }
 
     QDECREF(options);
@@ -2190,7 +2181,6 @@ void bdrv_close(BlockDriverState *bs)
         bs->backing_format[0] = '\0';
         bs->total_sectors = 0;
         bs->encrypted = 0;
-        bs->valid_key = 0;
         bs->sg = 0;
         bs->zero_beyond_eof = false;
         QDECREF(bs->options);
@@ -2774,74 +2764,6 @@ int bdrv_is_encrypted(BlockDriverState *bs)
     return bs->encrypted;
 }
 
-int bdrv_key_required(BlockDriverState *bs)
-{
-    BdrvChild *backing = bs->backing;
-
-    if (backing && backing->bs->encrypted && !backing->bs->valid_key) {
-        return 1;
-    }
-    return (bs->encrypted && !bs->valid_key);
-}
-
-int bdrv_set_key(BlockDriverState *bs, const char *key)
-{
-    int ret;
-    if (bs->backing && bs->backing->bs->encrypted) {
-        ret = bdrv_set_key(bs->backing->bs, key);
-        if (ret < 0)
-            return ret;
-        if (!bs->encrypted)
-            return 0;
-    }
-    if (!bs->encrypted) {
-        return -EINVAL;
-    } else if (!bs->drv || !bs->drv->bdrv_set_key) {
-        return -ENOMEDIUM;
-    }
-    ret = bs->drv->bdrv_set_key(bs, key);
-    if (ret < 0) {
-        bs->valid_key = 0;
-    } else if (!bs->valid_key) {
-        bs->valid_key = 1;
-        if (bs->blk) {
-            /* call the change callback now, we skipped it on open */
-            blk_dev_change_media_cb(bs->blk, true);
-        }
-    }
-    return ret;
-}
-
-/*
- * Provide an encryption key for @bs.
- * If @key is non-null:
- *     If @bs is not encrypted, fail.
- *     Else if the key is invalid, fail.
- *     Else set @bs's key to @key, replacing the existing key, if any.
- * If @key is null:
- *     If @bs is encrypted and still lacks a key, fail.
- *     Else do nothing.
- * On failure, store an error object through @errp if non-null.
- */
-void bdrv_add_key(BlockDriverState *bs, const char *key, Error **errp)
-{
-    if (key) {
-        if (!bdrv_is_encrypted(bs)) {
-            error_setg(errp, "Node '%s' is not encrypted",
-                      bdrv_get_device_or_node_name(bs));
-        } else if (bdrv_set_key(bs, key) < 0) {
-            error_setg(errp, QERR_INVALID_PASSWORD);
-        }
-    } else {
-        if (bdrv_key_required(bs)) {
-            error_set(errp, ERROR_CLASS_DEVICE_ENCRYPTED,
-                      "'%s' (%s) is encrypted",
-                      bdrv_get_device_or_node_name(bs),
-                      bdrv_get_encrypted_filename(bs));
-        }
-    }
-}
-
 const char *bdrv_get_format_name(BlockDriverState *bs)
 {
     return bs->drv ? bs->drv->format_name : NULL;
diff --git a/block/crypto.c b/block/crypto.c
index 2ba78bd..03194e5 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -278,7 +278,6 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
     }
 
     bs->encrypted = 1;
-    bs->valid_key = 1;
 
     qemu_co_mutex_init(&crypto->lock);
 
diff --git a/block/qapi.c b/block/qapi.c
index 58d3975..639b231 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -42,7 +42,7 @@ BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs, Error **errp)
     info->ro                     = bs->read_only;
     info->drv                    = g_strdup(bs->drv->format_name);
     info->encrypted              = bs->encrypted;
-    info->encryption_key_missing = bdrv_key_required(bs);
+    info->encryption_key_missing = false;
 
     info->cache = g_new(BlockdevCacheInfo, 1);
     *info->cache = (BlockdevCacheInfo) {
diff --git a/block/qcow.c b/block/qcow.c
index 3b7c2b9..2fc7c3c 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -212,7 +212,6 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
             goto fail;
         }
         bs->encrypted = 1;
-        bs->valid_key = 1;
     }
     s->cluster_bits = header.cluster_bits;
     s->cluster_size = 1 << s->cluster_bits;
diff --git a/block/qcow2.c b/block/qcow2.c
index 5249ca2..1fbae85 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1210,7 +1210,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
         bs->encrypted = 1;
-        bs->valid_key = 1;
     }
 
     s->l2_bits = s->cluster_bits - 3; /* L2 is always one cluster */
diff --git a/blockdev.c b/blockdev.c
index 1392fff..d3c3317 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -623,10 +623,6 @@ static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
             bdrv_set_io_limits(bs, &cfg);
         }
 
-        if (bdrv_key_required(bs)) {
-            autostart = 0;
-        }
-
         block_acct_init(blk_get_stats(blk), account_invalid, account_failed);
 
         if (!parse_stats_intervals(blk_get_stats(blk), interval_list, errp)) {
@@ -2261,24 +2257,8 @@ void qmp_block_passwd(bool has_device, const char *device,
                       bool has_node_name, const char *node_name,
                       const char *password, Error **errp)
 {
-    Error *local_err = NULL;
-    BlockDriverState *bs;
-    AioContext *aio_context;
-
-    bs = bdrv_lookup_bs(has_device ? device : NULL,
-                        has_node_name ? node_name : NULL,
-                        &local_err);
-    if (local_err) {
-        error_propagate(errp, local_err);
-        return;
-    }
-
-    aio_context = bdrv_get_aio_context(bs);
-    aio_context_acquire(aio_context);
-
-    bdrv_add_key(bs, password, errp);
-
-    aio_context_release(aio_context);
+    error_setg_errno(errp, -ENOSYS,
+                     "Setting block passwords directly is no longer supported");
 }
 
 void qmp_blockdev_open_tray(const char *device, bool has_force, bool force,
@@ -2505,12 +2485,6 @@ void qmp_blockdev_change_medium(const char *device, const char *filename,
 
     blk_apply_root_state(blk, medium_bs);
 
-    bdrv_add_key(medium_bs, NULL, &err);
-    if (err) {
-        error_propagate(errp, err);
-        goto fail;
-    }
-
     qmp_blockdev_open_tray(device, false, false, &err);
     if (err) {
         error_propagate(errp, err);
@@ -3849,16 +3823,6 @@ void qmp_blockdev_add(BlockdevOptions *options, Error **errp)
         }
     }
 
-    if (bs && bdrv_key_required(bs)) {
-        if (blk) {
-            blk_unref(blk);
-        } else {
-            bdrv_unref(bs);
-        }
-        error_setg(errp, "blockdev-add doesn't support encrypted devices");
-        goto fail;
-    }
-
 fail:
     qmp_output_visitor_cleanup(ov);
 }
diff --git a/include/block/block.h b/include/block/block.h
index 73ffbd5..379a24c 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -414,10 +414,6 @@ bool bdrv_chain_contains(BlockDriverState *top, BlockDriverState *base);
 BlockDriverState *bdrv_next_node(BlockDriverState *bs);
 BlockDriverState *bdrv_next(BlockDriverState *bs);
 int bdrv_is_encrypted(BlockDriverState *bs);
-int bdrv_key_required(BlockDriverState *bs);
-int bdrv_set_key(BlockDriverState *bs, const char *key);
-void bdrv_add_key(BlockDriverState *bs, const char *key, Error **errp);
-int bdrv_query_missing_keys(void);
 void bdrv_iterate_format(void (*it)(void *opaque, const char *name),
                          void *opaque);
 const char *bdrv_get_node_name(const BlockDriverState *bs);
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 256609d..191ce83 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -374,7 +374,6 @@ struct BlockDriverState {
     int read_only; /* if true, the media is read only */
     int open_flags; /* flags used to open the file, re-used for re-open */
     int encrypted; /* if true, the media is encrypted */
-    int valid_key; /* if true, a valid encryption key has been set */
     int sg;        /* if true, the device is a /dev/sg* */
     int copy_on_read; /* if true, copy read backing sectors into image
                          note this is a reference count */
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption
  2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
                   ` (15 preceding siblings ...)
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs Daniel P. Berrange
@ 2016-01-20 17:38 ` Daniel P. Berrange
  2016-02-08 21:26   ` Eric Blake
  16 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-20 17:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

Refuse to use images with the legacy AES-CBC encryption
format in the system emulators. They are still fully
supported in the qemu-img, qemu-io & qemu-nbd tools in
order to allow data to be liberated and for compatibility
with older QEMU versions. Continued support in these tools
is not a notable burden with the new FDE framework.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block.c                    | 12 +++++-------
 block/qcow.c               |  8 ++++++++
 block/qcow2.c              |  8 ++++++++
 include/block/block.h      |  1 +
 tests/qemu-iotests/049.out |  3 ---
 tests/qemu-iotests/087.out | 12 ------------
 tests/qemu-iotests/134.out | 12 ------------
 7 files changed, 22 insertions(+), 34 deletions(-)

diff --git a/block.c b/block.c
index 5403355..b59bd81 100644
--- a/block.c
+++ b/block.c
@@ -312,6 +312,11 @@ static int bdrv_is_whitelisted(BlockDriver *drv, bool read_only)
     return 0;
 }
 
+bool bdrv_uses_whitelist(void)
+{
+    return use_bdrv_whitelist;
+}
+
 typedef struct CreateCo {
     BlockDriver *drv;
     char *filename;
@@ -1021,13 +1026,6 @@ static int bdrv_open_common(BlockDriverState *bs, BdrvChild *file,
         goto free_and_fail;
     }
 
-    if (bs->encrypted) {
-        error_report("Encrypted images are deprecated");
-        error_printf("Support for them will be removed in a future release.\n"
-                     "You can use 'qemu-img convert' to convert your image"
-                     " to an unencrypted one.\n");
-    }
-
     ret = refresh_total_sectors(bs, bs->total_sectors);
     if (ret < 0) {
         error_setg_errno(errp, -ret, "Could not refresh total sector count");
diff --git a/block/qcow.c b/block/qcow.c
index 2fc7c3c..3ae438e 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -180,6 +180,14 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
         if (s->crypt_method_header == QCOW_CRYPT_AES) {
+            if (bdrv_uses_whitelist()) {
+                error_setg(errp,
+                           "Use of AES-CBC encrypted qcow images is no longer "
+                           "supported. Please use the qcow2 LUKS format instead.");
+                ret = -ENOSYS;
+                goto fail;
+            }
+
             ov = opts_visitor_new(opts);
 
             crypto_opts = g_new0(QCryptoBlockOpenOptions, 1);
diff --git a/block/qcow2.c b/block/qcow2.c
index 1fbae85..a737c6c 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1209,6 +1209,14 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
 
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
+        if (bdrv_uses_whitelist() &&
+            s->crypt_method_header == QCOW_CRYPT_AES) {
+            error_setg(errp,
+                       "Use of AES-CBC encrypted qcow2 images is no longer "
+                       "supported. Please use the qcow2 LUKS format instead.");
+            ret = -ENOSYS;
+            goto fail;
+        }
         bs->encrypted = 1;
     }
 
diff --git a/include/block/block.h b/include/block/block.h
index 379a24c..58006d2 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -190,6 +190,7 @@ void bdrv_io_limits_update_group(BlockDriverState *bs, const char *group);
 
 void bdrv_init(void);
 void bdrv_init_with_whitelist(void);
+bool bdrv_uses_whitelist(void);
 BlockDriver *bdrv_find_protocol(const char *filename,
                                 bool allow_protocol_prefix,
                                 Error **errp);
diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out
index c9f0bc5..e0bedc0 100644
--- a/tests/qemu-iotests/049.out
+++ b/tests/qemu-iotests/049.out
@@ -187,9 +187,6 @@ qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M
 Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 TEST_DIR/t.qcow2 64M
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16 key-secret=sec0
 
 == Check lazy_refcounts option (only with v3) ==
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 6582dda..b8842d5 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -38,17 +38,11 @@ QMP_VERSION
 
 === Encrypted image ===
 
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 Testing: -S
 QMP_VERSION
 {"return": {}}
 {"return": {}}
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
@@ -57,9 +51,6 @@ Testing:
 QMP_VERSION
 {"return": {}}
 {"return": {}}
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 {"return": {}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
@@ -67,9 +58,6 @@ You can use 'qemu-img convert' to convert your image to an unencrypted one.
 
 === Missing driver ===
 
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 Testing: -S
 QMP_VERSION
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index e9bf302..d498570 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -1,27 +1,15 @@
 QA output created by 134
-qemu-img: Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on key-secret=sec0
 
 == reading whole image ==
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 == rewriting whole image ==
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 wrote 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 == verify pattern ==
-Encrypted images are deprecated
-Support for them will be removed in a future release.
-You can use 'qemu-img convert' to convert your image to an unencrypted one.
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source Daniel P. Berrange
@ 2016-01-21  6:12   ` Fam Zheng
  2016-01-21  8:59     ` Daniel P. Berrange
  2016-02-04 17:44   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  6:12 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> +int qcrypto_random_bytes(uint8_t *buf G_GNUC_UNUSED,
> +                         size_t buflen G_GNUC_UNUSED,
> +                         Error **errp)
> +{
> +    error_setg(errp, "No random byte source provided in this build");
> +    return -1;

Curious, why does a random(3) or /dev/random implementation fall short to
error?

Regardlessly,

Reviewed-by: Fam Zheng <famz@redhat.com>

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined Daniel P. Berrange
@ 2016-01-21  6:12   ` Fam Zheng
  0 siblings, 0 replies; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  6:12 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> The qcrypto_hash_digest_len method was accidentally inside
> a CONFIG_GNUTLS_HASH block, even though it doesn't depend
> on gnutls. Re-arrange it to be unconditionally defined.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Fam Zheng <famz@redhat.com>

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
@ 2016-01-21  6:59   ` Fam Zheng
  2016-01-21 10:59     ` Daniel P. Berrange
  2016-02-04 22:14   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  6:59 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> The LUKS data format includes use of PBKDF2 (Password-Based
> Key Derivation Function). The Nettle library can provide
> an implementation of this, but we don't want code directly
> depending on a specific crypto library backend. Introduce
> a include/crypto/pbkdf.h header which defines a QEMU
> API for invoking PBKDK2. The initial implementations are
> backed by nettle & gcrypt, which are commonly available
> with distros shipping GNUTLS.
> 
> The test suite data is taken from the cryptsetup codebase
> under the LGPLv2.1+ license. This merely aims to verify
> that whatever backend we provide for this function in QEMU
> will comply with the spec.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  crypto/Makefile.objs      |   6 +-
>  crypto/pbkdf-gcrypt.c     |  65 ++++++++
>  crypto/pbkdf-nettle.c     |  64 ++++++++
>  crypto/pbkdf-stub.c       |  41 +++++
>  crypto/pbkdf.c            |  68 +++++++++
>  include/crypto/pbkdf.h    | 152 +++++++++++++++++++
>  tests/.gitignore          |   1 +
>  tests/Makefile            |   2 +
>  tests/test-crypto-pbkdf.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++
>  9 files changed, 776 insertions(+), 1 deletion(-)
>  create mode 100644 crypto/pbkdf-gcrypt.c
>  create mode 100644 crypto/pbkdf-nettle.c
>  create mode 100644 crypto/pbkdf-stub.c
>  create mode 100644 crypto/pbkdf.c
>  create mode 100644 include/crypto/pbkdf.h
>  create mode 100644 tests/test-crypto-pbkdf.c
> 
> diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
> index 1802ff5..4d2cd3e 100644
> --- a/crypto/Makefile.objs
> +++ b/crypto/Makefile.objs
> @@ -10,8 +10,12 @@ crypto-obj-y += tlssession.o
>  crypto-obj-y += secret.o
>  crypto-obj-$(if $(CONFIG_GNUTLS),n,$(CONFIG_GCRYPT)) += random-gcrypt.o
>  crypto-obj-$(CONFIG_GNUTLS) += random-gnutls.o
> +crypto-obj-y += pbkdf.o
> +crypto-obj-$(CONFIG_NETTLE) += pbkdf-nettle.o
> +crypto-obj-$(if $(CONFIG_NETTLE),n,$(CONFIG_GCRYPT)) += pbkdf-gcrypt.o
>  
>  # Let the userspace emulators avoid linking gnutls/etc
>  crypto-aes-obj-y = aes.o
>  
> -stub-obj-y += random-stub.o
> \ No newline at end of file
> +stub-obj-y += random-stub.o
> +stub-obj-y += pbkdf-stub.o
> diff --git a/crypto/pbkdf-gcrypt.c b/crypto/pbkdf-gcrypt.c
> new file mode 100644
> index 0000000..94abcf8
> --- /dev/null
> +++ b/crypto/pbkdf-gcrypt.c
> @@ -0,0 +1,65 @@
> +/*
> + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "crypto/pbkdf.h"
> +#include "gcrypt.h"
> +
> +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)
> +{
> +    switch (hash) {
> +    case QCRYPTO_HASH_ALG_SHA1:
> +    case QCRYPTO_HASH_ALG_SHA256:
> +        return true;
> +    default:
> +        return false;
> +    }
> +}
> +
> +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> +                   const uint8_t *key, size_t nkey,
> +                   const uint8_t *salt, size_t nsalt,
> +                   unsigned int iterations,
> +                   uint8_t *out, size_t nout,
> +                   Error **errp)
> +{
> +    static const int hash_map[QCRYPTO_HASH_ALG__MAX] = {
> +        [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5,
> +        [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1,
> +        [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256,
> +    };
> +    int ret;
> +
> +    if (hash > G_N_ELEMENTS(hash_map)) {

Do you want ">="?

> +        error_setg(errp, "Unexpected hash algorithm %d", hash);
> +        return -1;
> +    }
> +
> +    ret = gcry_kdf_derive(key, nkey, GCRY_KDF_PBKDF2,
> +                          hash_map[hash],
> +                          salt, nsalt, iterations,
> +                          nout, out);

We go ahead with QCRYPTO_HASH_ALG_MD5 here, but we didn't accept it in
qcrypto_pbkdf2_supports, is that a mistake?

> +    if (ret != 0) {
> +        error_setg(errp, "Cannot derive password: %s",
> +                   gcry_strerror(ret));
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> diff --git a/crypto/pbkdf-nettle.c b/crypto/pbkdf-nettle.c
> new file mode 100644
> index 0000000..b0f2d8e
> --- /dev/null
> +++ b/crypto/pbkdf-nettle.c
> @@ -0,0 +1,64 @@
> +/*
> + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "crypto/pbkdf.h"
> +#include "nettle/pbkdf2.h"
> +
> +
> +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)
> +{
> +    switch (hash) {
> +    case QCRYPTO_HASH_ALG_SHA1:
> +    case QCRYPTO_HASH_ALG_SHA256:
> +        return true;
> +    default:
> +        return false;
> +    }
> +}
> +
> +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> +                   const uint8_t *key, size_t nkey,
> +                   const uint8_t *salt, size_t nsalt,
> +                   unsigned int iterations,
> +                   uint8_t *out, size_t nout,
> +                   Error **errp)
> +{
> +    switch (hash) {
> +    case QCRYPTO_HASH_ALG_SHA1:
> +        pbkdf2_hmac_sha1(nkey, key,
> +                         iterations,
> +                         nsalt, salt,
> +                         nout, out);
> +        break;
> +
> +    case QCRYPTO_HASH_ALG_SHA256:
> +        pbkdf2_hmac_sha256(nkey, key,
> +                           iterations,
> +                           nsalt, salt,
> +                           nout, out);
> +        break;
> +
> +    default:
> +        error_setg_errno(errp, ENOSYS,
> +                         "PBKDF does not support hash algorithm %d", hash);
> +        return -1;
> +    }
> +    return 0;
> +}
> diff --git a/crypto/pbkdf-stub.c b/crypto/pbkdf-stub.c
> new file mode 100644
> index 0000000..73a08c3
> --- /dev/null
> +++ b/crypto/pbkdf-stub.c
> @@ -0,0 +1,41 @@
> +/*
> + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "crypto/pbkdf.h"
> +
> +bool qcrypto_pbkdf2_supports(QCryptoHashAlgorithm hash)

Missing G_GNUC_UNUSED to be consistent with the rest of the file, doesn't
hurt though.

> +{
> +    return false;
> +}
> +
> +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash G_GNUC_UNUSED,
> +                   const uint8_t *key G_GNUC_UNUSED,
> +                   size_t nkey G_GNUC_UNUSED,
> +                   const uint8_t *salt G_GNUC_UNUSED,
> +                   size_t nsalt G_GNUC_UNUSED,
> +                   unsigned int iterations G_GNUC_UNUSED,
> +                   uint8_t *out G_GNUC_UNUSED,
> +                   size_t nout G_GNUC_UNUSED,
> +                   Error **errp)
> +{
> +    error_setg_errno(errp, ENOSYS,
> +                     "No crypto library supporting PBKDF in this build");
> +    return -1;
> +}
> diff --git a/crypto/pbkdf.c b/crypto/pbkdf.c
> new file mode 100644
> index 0000000..71f96cd
> --- /dev/null
> +++ b/crypto/pbkdf.c
> @@ -0,0 +1,68 @@
> +/*
> + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "crypto/pbkdf.h"
> +#include <sys/resource.h>
> +
> +
> +int qcrypto_pbkdf2_count_iters(QCryptoHashAlgorithm hash,
> +                               const uint8_t *key, size_t nkey,
> +                               const uint8_t *salt, size_t nsalt,
> +                               Error **errp)
> +{
> +    uint8_t out[32];
> +    int iterations = (1 << 15);

To be safe from overflow, I'd use at least 64 bits to do the math, just in case
that some machine is too good at computing this stuff. :)

> +    struct rusage start, end;
> +    unsigned long long delta;
> +
> +    while (1) {
> +        if (getrusage(RUSAGE_THREAD, &start) < 0) {
> +            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
> +            return -1;
> +        }
> +        if (qcrypto_pbkdf2(hash,
> +                           key, nkey,
> +                           salt, nsalt,
> +                           iterations,
> +                           out, sizeof(out),
> +                           errp) < 0) {
> +            return -1;
> +        }
> +        if (getrusage(RUSAGE_THREAD, &end) < 0) {
> +            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
> +            return -1;
> +        }
> +
> +        delta = (((end.ru_utime.tv_sec * 1000ll) +
> +                  (end.ru_utime.tv_usec / 1000)) -
> +                 ((start.ru_utime.tv_sec * 1000ll) +
> +                  (start.ru_utime.tv_usec / 1000)));
> +
> +        if (delta > 500) {
> +            break;
> +        } else if (delta < 100) {
> +            iterations = iterations * 10;
> +        } else {
> +            iterations = (iterations * 1000 / delta);
> +        }
> +    }
> +
> +    return iterations * 1000 / delta;
> +}
> diff --git a/include/crypto/pbkdf.h b/include/crypto/pbkdf.h
> new file mode 100644
> index 0000000..a5e8267
> --- /dev/null
> +++ b/include/crypto/pbkdf.h
> @@ -0,0 +1,152 @@
> +/*
> + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QCRYPTO_PBKDF_H__
> +#define QCRYPTO_PBKDF_H__
> +
> +#include "crypto/hash.h"
> +
> +/**
> + * This module provides an interface to the PBKDF2 algorithm
> + *
> + *   https://en.wikipedia.org/wiki/PBKDF2
> + *
> + * <example>
> + *   <title>Generating a AES encryption key from a user password</title>

s/a AES/an AES/ ?

> + *   <programlisting>

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors Daniel P. Berrange
@ 2016-01-21  7:51   ` Fam Zheng
  2016-01-21 11:00     ` Daniel P. Berrange
  2016-02-04 22:57   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  7:51 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> +static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
> +                                    const uint8_t *key, size_t nkey,
> +                                    Error **errp)
> +{
> +    uint8_t *salt;
> +    size_t nhash;
> +    QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
> +
> +    nhash = qcrypto_hash_digest_len(ivgen->hash);
> +    /* Salt must be larger of hash size or key size */
> +    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);
> +
> +    if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey,
> +                           &salt, &nhash,
> +                           errp) < 0) {
> +        g_free(essiv);
> +        return -1;
> +    }
> +
> +    essiv->cipher = qcrypto_cipher_new(ivgen->cipher,
> +                                       QCRYPTO_CIPHER_MODE_ECB,
> +                                       salt, nkey,
> +                                       errp);
> +    if (!essiv->cipher) {
> +        g_free(essiv);
> +        g_free(salt);
> +        return -1;
> +    }
> +
> +    g_free(salt);
> +    ivgen->private = essiv;
> +
> +    return 0;
> +}
> +
> +static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
> +                                         uint64_t sector,
> +                                         uint8_t *iv, size_t niv,
> +                                         Error **errp)
> +{
> +    QCryptoIVGenESSIV *essiv = ivgen->private;
> +    size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher);
> +    uint8_t *data = g_new(uint8_t, ndata);
> +
> +    sector = cpu_to_le64((uint32_t)sector);

Why casting to 32 bit?

> +    memcpy(data, (uint8_t *)&sector, ndata);
> +    if (sizeof(sector) < ndata) {
> +        memset(data + sizeof(sector), 0, ndata - sizeof(sector));
> +    }
> +
> +    if (qcrypto_cipher_encrypt(essiv->cipher,
> +                               data,
> +                               data,
> +                               ndata,
> +                               errp) < 0) {
> +        g_free(data);
> +        return -1;
> +    }
> +
> +    if (ndata > niv) {
> +        ndata = niv;
> +    }
> +    memcpy(iv, data, ndata);
> +    if (ndata < niv) {
> +        memset(iv + ndata, 0, niv - ndata);
> +    }
> +    g_free(data);
> +    return 0;
> +}
> +
> +static void qcrypto_ivgen_essiv_cleanup(QCryptoIVGen *ivgen)
> +{
> +    QCryptoIVGenESSIV *essiv = ivgen->private;
> +
> +    qcrypto_cipher_free(essiv->cipher);
> +    g_free(essiv);
> +}
> +
> +
> +struct QCryptoIVGenDriver qcrypto_ivgen_essiv = {
> +    qcrypto_ivgen_essiv_init,

It'd be more readable to explicitly write

    .init = qcrypto_ivgen_essiv_init,
    .calculate = ...,
    .cleanup = ...

but it is fine since there are only three operations now.

> +    qcrypto_ivgen_essiv_calculate,
> +    qcrypto_ivgen_essiv_cleanup,
> +};
> +

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
@ 2016-01-21  8:37   ` Fam Zheng
  2016-01-21 11:01     ` Daniel P. Berrange
  2016-02-04 23:26   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  8:37 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> diff --git a/crypto/afsplit.c b/crypto/afsplit.c
> new file mode 100644
> index 0000000..42529e7
> --- /dev/null
> +++ b/crypto/afsplit.c
> @@ -0,0 +1,162 @@
> +/*
> + * QEMU Crypto anti forensic information splitter
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * Derived from cryptsetup package lib/lusk1/af.c

s/lusk1/luks1/

> + *
> + * Copyright (C) 2004, Clemens Fruhwirth <clemens@endorphin.org>
> + * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved.
> + *
> + * This library 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 library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +

<snip>

> +
> +/**
> + * qcrypto_afsplit_encode:
> + * @hash: the hash algorithm to use for data expansion
> + * @blocklen: the size of @in in bytes
> + * @stripes: the number of times to expand @in in size
> + * @in: the master key to be expanded in size
> + * @out: preallocted buffer to hold the split key
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Split the data in @in, which is @blocklen bytes in
> + * size, to form a larger piece of data @out, which is
> + * @blocklen * @stripes bytes in size.
> + *
> + * Returns: 0 on success, -1 on error;
> + */
> +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
> +                           size_t blocklen,
> +                           uint32_t stripes,
> +                           const uint8_t *in,
> +                           uint8_t *out,
> +                           Error **errp);
> +
> +/**
> + * qcrypto_afsplit_decode:
> + * @hash: the hash algorithm to use for data compression
> + * @blocklen: the size of @out in bytes
> + * @stripes: the number of times to decrease @in in size
> + * @in: the master key to be expanded in size
> + * @out: preallocted buffer to hold the split key

I think the descriptions for @in and @out are wrong.

> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Join the data in @in, which is @blocklen * @stripes
> + * bytes in size, to form the original small piece o

piece of

> + * data @out, which is @blocklen bytes in size.
> + *
> + * Returns: 0 on success, -1 on error;
> + */
> +int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
> +                           size_t blocklen,
> +                           uint32_t stripes,
> +                           const uint8_t *in,
> +                           uint8_t *out,
> +                           Error **errp);
> +
> +#endif /* QCRYPTO_AFSPLIT_H__ */

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source
  2016-01-21  6:12   ` Fam Zheng
@ 2016-01-21  8:59     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21  8:59 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 02:12:06PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > +int qcrypto_random_bytes(uint8_t *buf G_GNUC_UNUSED,
> > +                         size_t buflen G_GNUC_UNUSED,
> > +                         Error **errp)
> > +{
> > +    error_setg(errp, "No random byte source provided in this build");
> > +    return -1;
> 
> Curious, why does a random(3) or /dev/random implementation fall short to
> error?

random(3) does not provide crytographically strong random numbers.

/dev/random would be suitable, but if we read that directly we would
deplete entropy very quickly.

The gnutls backends will use /dev/random in combination with a
cryptographically secure generator to provide high entropy
numbers, at a high data rate.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver Daniel P. Berrange
@ 2016-01-21  9:12   ` Fam Zheng
  2016-01-21 11:02     ` Daniel P. Berrange
  2016-02-05 22:20   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  9:12 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> +    /* XXX Should we treat size as being total physical size
> +     * of the image (ie payload + encryption header), or just
> +     * the logical size of the image (ie payload). If the latter
> +     * then we need to extend 'size' to include the header
> +     * size */

The latter. :)

> +    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> +#define BLOCK_CRYPTO_DRIVER(name, format)                               \
> +    static int block_crypto_probe_ ## name(const uint8_t *buf,          \
> +                                           int buf_size,                \
> +                                           const char *filename) {      \
> +        return block_crypto_probe_generic(format,                       \
> +                                          buf, buf_size, filename);     \
> +    }                                                                   \
> +                                                                        \
> +    static int block_crypto_open_ ## name(BlockDriverState *bs,         \
> +                                          QDict *options,               \
> +                                          int flags,                    \
> +                                          Error **errp)                 \
> +    {                                                                   \
> +        return block_crypto_open_generic(format,                        \
> +                                         &block_crypto_runtime_opts_ ## name, \
> +                                         bs, options, flags, errp);     \
> +    }                                                                   \
> +                                                                        \
> +    static int block_crypto_create_ ## name(const char *filename,       \
> +                                            QemuOpts *opts,             \
> +                                            Error **errp)               \
> +    {                                                                   \
> +        return block_crypto_create_generic(format,                      \
> +                                           filename, opts, errp);       \
> +    }                                                                   \
> +                                                                        \
> +    BlockDriver bdrv_crypto_ ## name = {                                \
> +        .format_name        = #name,                                    \
> +        .instance_size      = sizeof(BlockCrypto),                      \
> +        .bdrv_probe         = block_crypto_probe_ ## name,              \
> +        .bdrv_open          = block_crypto_open_ ## name,               \
> +        .bdrv_close         = block_crypto_close,                       \
> +        .bdrv_create        = block_crypto_create_ ## name,             \
> +        .create_opts        = &block_crypto_create_opts_ ## name,       \
> +                                                                        \
> +        .bdrv_co_readv      = block_crypto_co_readv,                    \
> +        .bdrv_co_writev     = block_crypto_co_writev,                   \
> +        .bdrv_getlength     = block_crypto_getlength,                   \
> +    }
> +
> +BLOCK_CRYPTO_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);

Personally I really prefer a preprocessed version, for the ease of grep.

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
@ 2016-01-21  9:13   ` Fam Zheng
  2016-02-05 23:22   ` Eric Blake
  1 sibling, 0 replies; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  9:13 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> Instead of requiring separate input/output buffers for
> encrypting data, change qcow2_encrypt_sectors() to assume
> use of a single buffer, encrypting in place. The current
> callers all used the same buffer for input/output already.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow2-cluster.c | 17 +++++------------
>  block/qcow2.c         |  5 ++---
>  block/qcow2.h         |  3 +--
>  3 files changed, 8 insertions(+), 17 deletions(-)
> 
> diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> index 34112c3..f5bc4f2 100644
> --- a/block/qcow2-cluster.c
> +++ b/block/qcow2-cluster.c
> @@ -341,12 +341,8 @@ static int count_contiguous_clusters_by_type(int nb_clusters,
>      return i;
>  }
>  
> -/* The crypt function is compatible with the linux cryptoloop
> -   algorithm for < 4 GB images. NOTE: out_buf == in_buf is
> -   supported */
>  int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
> -                          uint8_t *out_buf, const uint8_t *in_buf,
> -                          int nb_sectors, bool enc,
> +                          uint8_t *buf, int nb_sectors, bool enc,
>                            Error **errp)
>  {
>      union {
> @@ -366,14 +362,12 @@ int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
>          }
>          if (enc) {
>              ret = qcrypto_cipher_encrypt(s->cipher,
> -                                         in_buf,
> -                                         out_buf,
> +                                         buf, buf,
>                                           512,
>                                           errp);
>          } else {
>              ret = qcrypto_cipher_decrypt(s->cipher,
> -                                         in_buf,
> -                                         out_buf,
> +                                         buf, buf,
>                                           512,
>                                           errp);
>          }
> @@ -381,8 +375,7 @@ int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
>              return -1;
>          }
>          sector_num++;
> -        in_buf += 512;
> -        out_buf += 512;
> +        buf += 512;
>      }
>      return 0;
>  }
> @@ -430,7 +423,7 @@ static int coroutine_fn copy_sectors(BlockDriverState *bs,
>          Error *err = NULL;
>          assert(s->cipher);
>          if (qcow2_encrypt_sectors(s, start_sect + n_start,
> -                                  iov.iov_base, iov.iov_base, n,
> +                                  iov.iov_base, n,
>                                    true, &err) < 0) {
>              ret = -EIO;
>              error_free(err);
> diff --git a/block/qcow2.c b/block/qcow2.c
> index d992e7f..2fae692 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -1504,7 +1504,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
>                  assert(s->cipher);
>                  Error *err = NULL;
>                  if (qcow2_encrypt_sectors(s, sector_num,  cluster_data,
> -                                          cluster_data, cur_nr_sectors, false,
> +                                          cur_nr_sectors, false,
>                                            &err) < 0) {
>                      error_free(err);
>                      ret = -EIO;
> @@ -1604,8 +1604,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
>              qemu_iovec_to_buf(&hd_qiov, 0, cluster_data, hd_qiov.size);
>  
>              if (qcow2_encrypt_sectors(s, sector_num, cluster_data,
> -                                      cluster_data, cur_nr_sectors,
> -                                      true, &err) < 0) {
> +                                      cur_nr_sectors, true, &err) < 0) {
>                  error_free(err);
>                  ret = -EIO;
>                  goto fail;
> diff --git a/block/qcow2.h b/block/qcow2.h
> index a063a3c..ae04285 100644
> --- a/block/qcow2.h
> +++ b/block/qcow2.h
> @@ -540,8 +540,7 @@ int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);
>  void qcow2_l2_cache_reset(BlockDriverState *bs);
>  int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
>  int qcow2_encrypt_sectors(BDRVQcow2State *s, int64_t sector_num,
> -                          uint8_t *out_buf, const uint8_t *in_buf,
> -                          int nb_sectors, bool enc, Error **errp);
> +                          uint8_t *buf, int nb_sectors, bool enc, Error **errp);
>  
>  int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
>      int *num, uint64_t *cluster_offset);
> -- 
> 2.5.0
> 

Reviewed-by: Fam Zheng <famz@redhat.com>

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
@ 2016-01-21  9:54   ` Fam Zheng
  2016-01-21 10:50     ` Daniel P. Berrange
  2016-02-08 18:12   ` Eric Blake
  1 sibling, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21  9:54 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> This converts the qcow2 driver to make use of the QCryptoBlock
> APIs for encrypting image content. As well as continued support
> for the legacy QCow2 encryption format, the appealing benefit
> is that it enables support for the LUKS format inside qcow2.

FWIW, with today's QEMU, it's possible to stack format drivers on top of each
other.  In other words, even without this patch, we can make LUKS driver
encrypt/decrypt the qcow2 payload, while keeping them completely orthogonal.

It's someting like:

           --------------------
           |       LUKS       |
           --------------------
                    |
                    v
           --------------------
           |      qcow2       |
           --------------------
                    |
                    v
           --------------------
           |       file       |
           --------------------

The command line looks like this:

 -drive driver=luks,file.driver=qcow2,file.file.driver=file,\
file.file.filename=$qcow2_image_whose_payload_is_in_luks_format

unfortunately I don't know how to create nested images with qemu-img. I tested
the nested qcow2 by attaching the outter image to a VM and running "qemu-img
create -f qcow2 /dev/vda" in guest shell. Kevin?

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-21  9:54   ` Fam Zheng
@ 2016-01-21 10:50     ` Daniel P. Berrange
  2016-01-21 13:56       ` Fam Zheng
  0 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 10:50 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 05:54:23PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > This converts the qcow2 driver to make use of the QCryptoBlock
> > APIs for encrypting image content. As well as continued support
> > for the legacy QCow2 encryption format, the appealing benefit
> > is that it enables support for the LUKS format inside qcow2.
> 
> FWIW, with today's QEMU, it's possible to stack format drivers on top of each
> other.  In other words, even without this patch, we can make LUKS driver
> encrypt/decrypt the qcow2 payload, while keeping them completely orthogonal.

Yep, that is certainly possible, and it is what is intended for using
LUKS with RBD, iSCSI, & other network drivers.

I think there is value in having LUKS integrated directly into qcow2
though. It means that given a qcow2 file you can 100% reliably
distinguish between a file created with the intention of QEMU managing
the LUKS encryption, from a file where the guest OS happens to have
set up LUKS encryption in its virtual disk. If you don't have this,
then given a random qcow2 file, you have to probe to see if LUKS is
present or not. Given the security issues we've had in the past with
raw images being turned into qcow2 images by a malicious guest writing
a qcow2 header, I feel that having explicitly integration LUKS support
in QCow is worthwhile as a concept.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-01-21  6:59   ` Fam Zheng
@ 2016-01-21 10:59     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 10:59 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 02:59:24PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > The LUKS data format includes use of PBKDF2 (Password-Based
> > Key Derivation Function). The Nettle library can provide
> > an implementation of this, but we don't want code directly
> > depending on a specific crypto library backend. Introduce
> > a include/crypto/pbkdf.h header which defines a QEMU
> > API for invoking PBKDK2. The initial implementations are
> > backed by nettle & gcrypt, which are commonly available
> > with distros shipping GNUTLS.
> > 
> > The test suite data is taken from the cryptsetup codebase
> > under the LGPLv2.1+ license. This merely aims to verify
> > that whatever backend we provide for this function in QEMU
> > will comply with the spec.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  crypto/Makefile.objs      |   6 +-
> >  crypto/pbkdf-gcrypt.c     |  65 ++++++++
> >  crypto/pbkdf-nettle.c     |  64 ++++++++
> >  crypto/pbkdf-stub.c       |  41 +++++
> >  crypto/pbkdf.c            |  68 +++++++++
> >  include/crypto/pbkdf.h    | 152 +++++++++++++++++++
> >  tests/.gitignore          |   1 +
> >  tests/Makefile            |   2 +
> >  tests/test-crypto-pbkdf.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++
> >  9 files changed, 776 insertions(+), 1 deletion(-)
> >  create mode 100644 crypto/pbkdf-gcrypt.c
> >  create mode 100644 crypto/pbkdf-nettle.c
> >  create mode 100644 crypto/pbkdf-stub.c
> >  create mode 100644 crypto/pbkdf.c
> >  create mode 100644 include/crypto/pbkdf.h
> >  create mode 100644 tests/test-crypto-pbkdf.c
> > 
> > +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> > +                   const uint8_t *key, size_t nkey,
> > +                   const uint8_t *salt, size_t nsalt,
> > +                   unsigned int iterations,
> > +                   uint8_t *out, size_t nout,
> > +                   Error **errp)
> > +{
> > +    static const int hash_map[QCRYPTO_HASH_ALG__MAX] = {
> > +        [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5,
> > +        [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1,
> > +        [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256,
> > +    };
> > +    int ret;
> > +
> > +    if (hash > G_N_ELEMENTS(hash_map)) {
> 
> Do you want ">="?

Yes.

> 
> > +        error_setg(errp, "Unexpected hash algorithm %d", hash);
> > +        return -1;
> > +    }
> > +
> > +    ret = gcry_kdf_derive(key, nkey, GCRY_KDF_PBKDF2,
> > +                          hash_map[hash],
> > +                          salt, nsalt, iterations,
> > +                          nout, out);
> 
> We go ahead with QCRYPTO_HASH_ALG_MD5 here, but we didn't accept it in
> qcrypto_pbkdf2_supports, is that a mistake?

Yes, I should have reported MD5 in the earlier function, since
gcrypt allows that.

> > diff --git a/crypto/pbkdf.c b/crypto/pbkdf.c
> > new file mode 100644
> > index 0000000..71f96cd
> > --- /dev/null
> > +++ b/crypto/pbkdf.c
> > @@ -0,0 +1,68 @@
> > +/*
> > + * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
> > + *
> > + * Copyright (c) 2015-2016 Red Hat, Inc.
> > + *
> > + * This library is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU Lesser General Public
> > + * License as published by the Free Software Foundation; either
> > + * version 2 of the License, or (at your option) any later version.
> > + *
> > + * This library 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
> > + * Lesser General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU Lesser General Public
> > + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> > + *
> > + */
> > +
> > +#include "crypto/pbkdf.h"
> > +#include <sys/resource.h>
> > +
> > +
> > +int qcrypto_pbkdf2_count_iters(QCryptoHashAlgorithm hash,
> > +                               const uint8_t *key, size_t nkey,
> > +                               const uint8_t *salt, size_t nsalt,
> > +                               Error **errp)
> > +{
> > +    uint8_t out[32];
> > +    int iterations = (1 << 15);
> 
> To be safe from overflow, I'd use at least 64 bits to do the math, just in case
> that some machine is too good at computing this stuff. :)

Hmm, probably need to change the return type to be 64 bit too then

> 
> > +    struct rusage start, end;
> > +    unsigned long long delta;
> > +
> > +    while (1) {
> > +        if (getrusage(RUSAGE_THREAD, &start) < 0) {
> > +            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
> > +            return -1;
> > +        }
> > +        if (qcrypto_pbkdf2(hash,
> > +                           key, nkey,
> > +                           salt, nsalt,
> > +                           iterations,
> > +                           out, sizeof(out),
> > +                           errp) < 0) {
> > +            return -1;
> > +        }
> > +        if (getrusage(RUSAGE_THREAD, &end) < 0) {
> > +            error_setg_errno(errp, errno, "Unable to get thread CPU usage");
> > +            return -1;
> > +        }
> > +
> > +        delta = (((end.ru_utime.tv_sec * 1000ll) +
> > +                  (end.ru_utime.tv_usec / 1000)) -
> > +                 ((start.ru_utime.tv_sec * 1000ll) +
> > +                  (start.ru_utime.tv_usec / 1000)));
> > +
> > +        if (delta > 500) {
> > +            break;
> > +        } else if (delta < 100) {
> > +            iterations = iterations * 10;
> > +        } else {
> > +            iterations = (iterations * 1000 / delta);
> > +        }
> > +    }
> > +
> > +    return iterations * 1000 / delta;
> > +}
> > +/**
> > + * This module provides an interface to the PBKDF2 algorithm
> > + *
> > + *   https://en.wikipedia.org/wiki/PBKDF2
> > + *
> > + * <example>
> > + *   <title>Generating a AES encryption key from a user password</title>
> 
> s/a AES/an AES/ ?

Yes

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-01-21  7:51   ` Fam Zheng
@ 2016-01-21 11:00     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 11:00 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 03:51:37PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > +static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
> > +                                    const uint8_t *key, size_t nkey,
> > +                                    Error **errp)
> > +{
> > +    uint8_t *salt;
> > +    size_t nhash;
> > +    QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
> > +
> > +    nhash = qcrypto_hash_digest_len(ivgen->hash);
> > +    /* Salt must be larger of hash size or key size */
> > +    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);
> > +
> > +    if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey,
> > +                           &salt, &nhash,
> > +                           errp) < 0) {
> > +        g_free(essiv);
> > +        return -1;
> > +    }
> > +
> > +    essiv->cipher = qcrypto_cipher_new(ivgen->cipher,
> > +                                       QCRYPTO_CIPHER_MODE_ECB,
> > +                                       salt, nkey,
> > +                                       errp);
> > +    if (!essiv->cipher) {
> > +        g_free(essiv);
> > +        g_free(salt);
> > +        return -1;
> > +    }
> > +
> > +    g_free(salt);
> > +    ivgen->private = essiv;
> > +
> > +    return 0;
> > +}
> > +
> > +static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
> > +                                         uint64_t sector,
> > +                                         uint8_t *iv, size_t niv,
> > +                                         Error **errp)
> > +{
> > +    QCryptoIVGenESSIV *essiv = ivgen->private;
> > +    size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher);
> > +    uint8_t *data = g_new(uint8_t, ndata);
> > +
> > +    sector = cpu_to_le64((uint32_t)sector);
> 
> Why casting to 32 bit?

Bugtastic.

> > +    memcpy(data, (uint8_t *)&sector, ndata);
> > +    if (sizeof(sector) < ndata) {
> > +        memset(data + sizeof(sector), 0, ndata - sizeof(sector));
> > +    }
> > +
> > +    if (qcrypto_cipher_encrypt(essiv->cipher,
> > +                               data,
> > +                               data,
> > +                               ndata,
> > +                               errp) < 0) {
> > +        g_free(data);
> > +        return -1;
> > +    }
> > +
> > +    if (ndata > niv) {
> > +        ndata = niv;
> > +    }
> > +    memcpy(iv, data, ndata);
> > +    if (ndata < niv) {
> > +        memset(iv + ndata, 0, niv - ndata);
> > +    }
> > +    g_free(data);
> > +    return 0;
> > +}
> > +
> > +static void qcrypto_ivgen_essiv_cleanup(QCryptoIVGen *ivgen)
> > +{
> > +    QCryptoIVGenESSIV *essiv = ivgen->private;
> > +
> > +    qcrypto_cipher_free(essiv->cipher);
> > +    g_free(essiv);
> > +}
> > +
> > +
> > +struct QCryptoIVGenDriver qcrypto_ivgen_essiv = {
> > +    qcrypto_ivgen_essiv_init,
> 
> It'd be more readable to explicitly write
> 
>     .init = qcrypto_ivgen_essiv_init,
>     .calculate = ...,
>     .cleanup = ...
> 
> but it is fine since there are only three operations now.

Yeah, I normally do it that way anyway, so will change it.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-01-21  8:37   ` Fam Zheng
@ 2016-01-21 11:01     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 11:01 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 04:37:28PM +0800, Fam Zheng wrote:
> 
> > +
> > +/**
> > + * qcrypto_afsplit_encode:
> > + * @hash: the hash algorithm to use for data expansion
> > + * @blocklen: the size of @in in bytes
> > + * @stripes: the number of times to expand @in in size
> > + * @in: the master key to be expanded in size
> > + * @out: preallocted buffer to hold the split key
> > + * @errp: pointer to a NULL-initialized error object
> > + *
> > + * Split the data in @in, which is @blocklen bytes in
> > + * size, to form a larger piece of data @out, which is
> > + * @blocklen * @stripes bytes in size.
> > + *
> > + * Returns: 0 on success, -1 on error;
> > + */
> > +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
> > +                           size_t blocklen,
> > +                           uint32_t stripes,
> > +                           const uint8_t *in,
> > +                           uint8_t *out,
> > +                           Error **errp);
> > +
> > +/**
> > + * qcrypto_afsplit_decode:
> > + * @hash: the hash algorithm to use for data compression
> > + * @blocklen: the size of @out in bytes
> > + * @stripes: the number of times to decrease @in in size
> > + * @in: the master key to be expanded in size
> > + * @out: preallocted buffer to hold the split key
> 
> I think the descriptions for @in and @out are wrong.

Yeah, got them the wrong way around

> 
> > + * @errp: pointer to a NULL-initialized error object
> > + *
> > + * Join the data in @in, which is @blocklen * @stripes
> > + * bytes in size, to form the original small piece o
> 
> piece of
> 
> > + * data @out, which is @blocklen bytes in size.
> > + *
> > + * Returns: 0 on success, -1 on error;
> > + */
> > +int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
> > +                           size_t blocklen,
> > +                           uint32_t stripes,
> > +                           const uint8_t *in,
> > +                           uint8_t *out,
> > +                           Error **errp);
> > +
> > +#endif /* QCRYPTO_AFSPLIT_H__ */
> 
> Fam

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-21  9:12   ` Fam Zheng
@ 2016-01-21 11:02     ` Daniel P. Berrange
  2016-01-21 13:01       ` Fam Zheng
  0 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 11:02 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 05:12:08PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > +    /* XXX Should we treat size as being total physical size
> > +     * of the image (ie payload + encryption header), or just
> > +     * the logical size of the image (ie payload). If the latter
> > +     * then we need to extend 'size' to include the header
> > +     * size */
> 
> The latter. :)

Ok

> > +    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> > +#define BLOCK_CRYPTO_DRIVER(name, format)                               \
> > +    static int block_crypto_probe_ ## name(const uint8_t *buf,          \
> > +                                           int buf_size,                \
> > +                                           const char *filename) {      \
> > +        return block_crypto_probe_generic(format,                       \
> > +                                          buf, buf_size, filename);     \
> > +    }                                                                   \
> > +                                                                        \
> > +    static int block_crypto_open_ ## name(BlockDriverState *bs,         \
> > +                                          QDict *options,               \
> > +                                          int flags,                    \
> > +                                          Error **errp)                 \
> > +    {                                                                   \
> > +        return block_crypto_open_generic(format,                        \
> > +                                         &block_crypto_runtime_opts_ ## name, \
> > +                                         bs, options, flags, errp);     \
> > +    }                                                                   \
> > +                                                                        \
> > +    static int block_crypto_create_ ## name(const char *filename,       \
> > +                                            QemuOpts *opts,             \
> > +                                            Error **errp)               \
> > +    {                                                                   \
> > +        return block_crypto_create_generic(format,                      \
> > +                                           filename, opts, errp);       \
> > +    }                                                                   \
> > +                                                                        \
> > +    BlockDriver bdrv_crypto_ ## name = {                                \
> > +        .format_name        = #name,                                    \
> > +        .instance_size      = sizeof(BlockCrypto),                      \
> > +        .bdrv_probe         = block_crypto_probe_ ## name,              \
> > +        .bdrv_open          = block_crypto_open_ ## name,               \
> > +        .bdrv_close         = block_crypto_close,                       \
> > +        .bdrv_create        = block_crypto_create_ ## name,             \
> > +        .create_opts        = &block_crypto_create_opts_ ## name,       \
> > +                                                                        \
> > +        .bdrv_co_readv      = block_crypto_co_readv,                    \
> > +        .bdrv_co_writev     = block_crypto_co_writev,                   \
> > +        .bdrv_getlength     = block_crypto_getlength,                   \
> > +    }
> > +
> > +BLOCK_CRYPTO_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);
> 
> Personally I really prefer a preprocessed version, for the ease of grep.

I'm not sure I understand what you mean by a preprocessed version - could
you expand on that.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-21 11:02     ` Daniel P. Berrange
@ 2016-01-21 13:01       ` Fam Zheng
  2016-01-21 13:12         ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21 13:01 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, 01/21 11:02, Daniel P. Berrange wrote:
> On Thu, Jan 21, 2016 at 05:12:08PM +0800, Fam Zheng wrote:
> > On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > > +    /* XXX Should we treat size as being total physical size
> > > +     * of the image (ie payload + encryption header), or just
> > > +     * the logical size of the image (ie payload). If the latter
> > > +     * then we need to extend 'size' to include the header
> > > +     * size */
> > 
> > The latter. :)
> 
> Ok
> 
> > > +    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> > > +#define BLOCK_CRYPTO_DRIVER(name, format)                               \
> > > +    static int block_crypto_probe_ ## name(const uint8_t *buf,          \
> > > +                                           int buf_size,                \
> > > +                                           const char *filename) {      \
> > > +        return block_crypto_probe_generic(format,                       \
> > > +                                          buf, buf_size, filename);     \
> > > +    }                                                                   \
> > > +                                                                        \
> > > +    static int block_crypto_open_ ## name(BlockDriverState *bs,         \
> > > +                                          QDict *options,               \
> > > +                                          int flags,                    \
> > > +                                          Error **errp)                 \
> > > +    {                                                                   \
> > > +        return block_crypto_open_generic(format,                        \
> > > +                                         &block_crypto_runtime_opts_ ## name, \
> > > +                                         bs, options, flags, errp);     \
> > > +    }                                                                   \
> > > +                                                                        \
> > > +    static int block_crypto_create_ ## name(const char *filename,       \
> > > +                                            QemuOpts *opts,             \
> > > +                                            Error **errp)               \
> > > +    {                                                                   \
> > > +        return block_crypto_create_generic(format,                      \
> > > +                                           filename, opts, errp);       \
> > > +    }                                                                   \
> > > +                                                                        \
> > > +    BlockDriver bdrv_crypto_ ## name = {                                \
> > > +        .format_name        = #name,                                    \
> > > +        .instance_size      = sizeof(BlockCrypto),                      \
> > > +        .bdrv_probe         = block_crypto_probe_ ## name,              \
> > > +        .bdrv_open          = block_crypto_open_ ## name,               \
> > > +        .bdrv_close         = block_crypto_close,                       \
> > > +        .bdrv_create        = block_crypto_create_ ## name,             \
> > > +        .create_opts        = &block_crypto_create_opts_ ## name,       \
> > > +                                                                        \
> > > +        .bdrv_co_readv      = block_crypto_co_readv,                    \
> > > +        .bdrv_co_writev     = block_crypto_co_writev,                   \
> > > +        .bdrv_getlength     = block_crypto_getlength,                   \
> > > +    }
> > > +
> > > +BLOCK_CRYPTO_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);
> > 
> > Personally I really prefer a preprocessed version, for the ease of grep.
> 
> I'm not sure I understand what you mean by a preprocessed version - could
> you expand on that.

I mean don't use macro concatenation and use plain symbols like in other block
drivers.

    BlockDriver bdrv_crypto_luks = {
        .format_name        = "luks",
        .instance_size      = sizeof(BlockCrypto),
        .bdrv_probe         = block_crypto_probe_luks,
        .bdrv_open          = block_crypto_open_luks,
        ...

mostly because it's easier to grep (or for refactoring with tools).

But I can't how repeatitive this would be (I can see the "don't repeat
yourself" with your approach).  There is only one BLOCK_CRYPTO_DRIVER instance
in this series. This is probably bikeshedding.

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting Daniel P. Berrange
@ 2016-01-21 13:02   ` Fam Zheng
  2016-01-21 13:11     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21 13:02 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
> index 3386668..065d9af 100755
> --- a/tests/qemu-iotests/087
> +++ b/tests/qemu-iotests/087
> @@ -201,6 +201,7 @@ run_qemu -S <<EOF
>        "options": {
>          "driver": "$IMGFMT",
>          "id": "disk",
> +        "key-secret": "sec0",
>          "file": {
>              "driver": "file",
>              "filename": "$TEST_IMG"
> @@ -228,6 +229,7 @@ run_qemu <<EOF
>        "options": {
>          "driver": "$IMGFMT",
>          "id": "disk",
> +        "key-secret": "sec0",
>          "file": {
>              "driver": "file",
>              "filename": "$TEST_IMG"
> diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
> index 5fc4823..6582dda 100644
> --- a/tests/qemu-iotests/087.out
> +++ b/tests/qemu-iotests/087.out
> @@ -49,7 +49,7 @@ QMP_VERSION
>  Encrypted images are deprecated
>  Support for them will be removed in a future release.
>  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> -{"error": {"class": "GenericError", "desc": "blockdev-add doesn't support encrypted devices"}}
> +{"return": {}}
>  {"return": {}}
>  {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
>  
> @@ -60,7 +60,7 @@ QMP_VERSION
>  Encrypted images are deprecated
>  Support for them will be removed in a future release.
>  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> -{"error": {"class": "GenericError", "desc": "Guest must be stopped for opening of encrypted image"}}
> +{"return": {}}
>  {"return": {}}
>  {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
>  
> diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
> index d9913f8..da78459 100644
> --- a/tests/qemu-iotests/common.rc
> +++ b/tests/qemu-iotests/common.rc
> @@ -135,9 +135,9 @@ _make_test_img()
>      # XXX(hch): have global image options?
>      (
>       if [ $use_backing = 1 ]; then
> -        $QEMU_IMG create -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
> +        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
>       else
> -        $QEMU_IMG create -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
> +        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
>       fi
>      ) | _filter_img_create
>  

Does this change belong to a separate patch?

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting
  2016-01-21 13:02   ` Fam Zheng
@ 2016-01-21 13:11     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 13:11 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 09:02:14PM +0800, Fam Zheng wrote:
> On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087
> > index 3386668..065d9af 100755
> > --- a/tests/qemu-iotests/087
> > +++ b/tests/qemu-iotests/087
> > @@ -201,6 +201,7 @@ run_qemu -S <<EOF
> >        "options": {
> >          "driver": "$IMGFMT",
> >          "id": "disk",
> > +        "key-secret": "sec0",
> >          "file": {
> >              "driver": "file",
> >              "filename": "$TEST_IMG"
> > @@ -228,6 +229,7 @@ run_qemu <<EOF
> >        "options": {
> >          "driver": "$IMGFMT",
> >          "id": "disk",
> > +        "key-secret": "sec0",
> >          "file": {
> >              "driver": "file",
> >              "filename": "$TEST_IMG"
> > diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
> > index 5fc4823..6582dda 100644
> > --- a/tests/qemu-iotests/087.out
> > +++ b/tests/qemu-iotests/087.out
> > @@ -49,7 +49,7 @@ QMP_VERSION
> >  Encrypted images are deprecated
> >  Support for them will be removed in a future release.
> >  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> > -{"error": {"class": "GenericError", "desc": "blockdev-add doesn't support encrypted devices"}}
> > +{"return": {}}
> >  {"return": {}}
> >  {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
> >  
> > @@ -60,7 +60,7 @@ QMP_VERSION
> >  Encrypted images are deprecated
> >  Support for them will be removed in a future release.
> >  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> > -{"error": {"class": "GenericError", "desc": "Guest must be stopped for opening of encrypted image"}}
> > +{"return": {}}
> >  {"return": {}}
> >  {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
> >  
> > diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
> > index d9913f8..da78459 100644
> > --- a/tests/qemu-iotests/common.rc
> > +++ b/tests/qemu-iotests/common.rc
> > @@ -135,9 +135,9 @@ _make_test_img()
> >      # XXX(hch): have global image options?
> >      (
> >       if [ $use_backing = 1 ]; then
> > -        $QEMU_IMG create -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
> > +        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options -b "$backing_file" "$img_name" $image_size 2>&1
> >       else
> > -        $QEMU_IMG create -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
> > +        $QEMU_IMG create $EXTRA_IMG_ARGS -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1
> >       fi
> >      ) | _filter_img_create
> >  
> 
> Does this change belong to a separate patch?

Opps, no. This change to common.rc should be deleted - it was something
I added which turned out to not be needed

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-21 13:01       ` Fam Zheng
@ 2016-01-21 13:12         ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 13:12 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 09:01:19PM +0800, Fam Zheng wrote:
> On Thu, 01/21 11:02, Daniel P. Berrange wrote:
> > On Thu, Jan 21, 2016 at 05:12:08PM +0800, Fam Zheng wrote:
> > > On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > > > +    /* XXX Should we treat size as being total physical size
> > > > +     * of the image (ie payload + encryption header), or just
> > > > +     * the logical size of the image (ie payload). If the latter
> > > > +     * then we need to extend 'size' to include the header
> > > > +     * size */
> > > 
> > > The latter. :)
> > 
> > Ok
> > 
> > > > +    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> > > > +#define BLOCK_CRYPTO_DRIVER(name, format)                               \
> > > > +    static int block_crypto_probe_ ## name(const uint8_t *buf,          \
> > > > +                                           int buf_size,                \
> > > > +                                           const char *filename) {      \
> > > > +        return block_crypto_probe_generic(format,                       \
> > > > +                                          buf, buf_size, filename);     \
> > > > +    }                                                                   \
> > > > +                                                                        \
> > > > +    static int block_crypto_open_ ## name(BlockDriverState *bs,         \
> > > > +                                          QDict *options,               \
> > > > +                                          int flags,                    \
> > > > +                                          Error **errp)                 \
> > > > +    {                                                                   \
> > > > +        return block_crypto_open_generic(format,                        \
> > > > +                                         &block_crypto_runtime_opts_ ## name, \
> > > > +                                         bs, options, flags, errp);     \
> > > > +    }                                                                   \
> > > > +                                                                        \
> > > > +    static int block_crypto_create_ ## name(const char *filename,       \
> > > > +                                            QemuOpts *opts,             \
> > > > +                                            Error **errp)               \
> > > > +    {                                                                   \
> > > > +        return block_crypto_create_generic(format,                      \
> > > > +                                           filename, opts, errp);       \
> > > > +    }                                                                   \
> > > > +                                                                        \
> > > > +    BlockDriver bdrv_crypto_ ## name = {                                \
> > > > +        .format_name        = #name,                                    \
> > > > +        .instance_size      = sizeof(BlockCrypto),                      \
> > > > +        .bdrv_probe         = block_crypto_probe_ ## name,              \
> > > > +        .bdrv_open          = block_crypto_open_ ## name,               \
> > > > +        .bdrv_close         = block_crypto_close,                       \
> > > > +        .bdrv_create        = block_crypto_create_ ## name,             \
> > > > +        .create_opts        = &block_crypto_create_opts_ ## name,       \
> > > > +                                                                        \
> > > > +        .bdrv_co_readv      = block_crypto_co_readv,                    \
> > > > +        .bdrv_co_writev     = block_crypto_co_writev,                   \
> > > > +        .bdrv_getlength     = block_crypto_getlength,                   \
> > > > +    }
> > > > +
> > > > +BLOCK_CRYPTO_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);
> > > 
> > > Personally I really prefer a preprocessed version, for the ease of grep.
> > 
> > I'm not sure I understand what you mean by a preprocessed version - could
> > you expand on that.
> 
> I mean don't use macro concatenation and use plain symbols like in other block
> drivers.
> 
>     BlockDriver bdrv_crypto_luks = {
>         .format_name        = "luks",
>         .instance_size      = sizeof(BlockCrypto),
>         .bdrv_probe         = block_crypto_probe_luks,
>         .bdrv_open          = block_crypto_open_luks,
>         ...
> 
> mostly because it's easier to grep (or for refactoring with tools).
> 
> But I can't how repeatitive this would be (I can see the "don't repeat
> yourself" with your approach).  There is only one BLOCK_CRYPTO_DRIVER instance
> in this series. This is probably bikeshedding.

Yeah, thinking about it, my macro approach is probably overkill for the
forseeable future anyway. I was future proofing wrt possible addition
of trucrypt support, but that's soo far off its not something i really
need worry about now.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-21 10:50     ` Daniel P. Berrange
@ 2016-01-21 13:56       ` Fam Zheng
  2016-01-21 14:03         ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Fam Zheng @ 2016-01-21 13:56 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, 01/21 10:50, Daniel P. Berrange wrote:
> On Thu, Jan 21, 2016 at 05:54:23PM +0800, Fam Zheng wrote:
> > On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > > This converts the qcow2 driver to make use of the QCryptoBlock
> > > APIs for encrypting image content. As well as continued support
> > > for the legacy QCow2 encryption format, the appealing benefit
> > > is that it enables support for the LUKS format inside qcow2.
> > 
> > FWIW, with today's QEMU, it's possible to stack format drivers on top of each
> > other.  In other words, even without this patch, we can make LUKS driver
> > encrypt/decrypt the qcow2 payload, while keeping them completely orthogonal.
> 
> Yep, that is certainly possible, and it is what is intended for using
> LUKS with RBD, iSCSI, & other network drivers.
> 
> I think there is value in having LUKS integrated directly into qcow2
> though. It means that given a qcow2 file you can 100% reliably
> distinguish between a file created with the intention of QEMU managing
> the LUKS encryption, from a file where the guest OS happens to have
> set up LUKS encryption in its virtual disk. If you don't have this,
> then given a random qcow2 file, you have to probe to see if LUKS is
> present or not. Given the security issues we've had in the past with
> raw images being turned into qcow2 images by a malicious guest writing
> a qcow2 header, I feel that having explicitly integration LUKS support
> in QCow is worthwhile as a concept.

Yes, I'm not objecting to explicit (read: mandatory) encryption in qcow2 in any
way, and extending the format spec for LUKS is a good thing.

I mentioned stacked drivers because I wonder how good we can do in reusing
block/crypto.c code to achieve this (to save 500+ new code in qcow2). For
example I have a rough idea along this:

* In qcow2 spec, define type "2" of crypt_method for LUKS encryption, and state
  that if this method is used, the payload must be LUKS format as defined in
  cryptsetup's docs/on-disk-format.pdf, and driver will take care of
  encrypting/decrypting data in a way that is transparent to guest.

* In qcow2_open, if header's crypt_method is LUKS, set a special flag in the
  BlockDriverState to indicate that block layer should create an overlay crypto
  driver (which will be luks in this case), on top of this BDS. To do this we
  need some modification to bdrv_open.

* When qcow2_open is done, block layer will then instantiate a LUKS driver,
  which backed by the qcow2 BDS (as the BDS.file). This LUKS BDS will then be
  attached to guest device.

* From the guest PoV, R/W reqs are served as if it's an ordinary qcow2; from
  LUKS driver's PoV, it works properly formatted luks data, which is backed by
  whatever it doesn't care, be it an iscsi target, rbd block, local file, or
  a qcow2 image that has metadata; from the qcow2 driver's PoV, it only made
  sure that a LUKS driver covers itself, at instantiation time.  Everything
  else operates the same way as a non-encrypted qcow2.

* Of course, qcow2_create would need some similar changes to the block layer as
  to bdrv_open, but that shouldn't be too hard.

Makes sense?

Fam

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-21 13:56       ` Fam Zheng
@ 2016-01-21 14:03         ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-01-21 14:03 UTC (permalink / raw)
  To: Fam Zheng; +Cc: Kevin Wolf, qemu-devel, qemu-block

On Thu, Jan 21, 2016 at 09:56:11PM +0800, Fam Zheng wrote:
> On Thu, 01/21 10:50, Daniel P. Berrange wrote:
> > On Thu, Jan 21, 2016 at 05:54:23PM +0800, Fam Zheng wrote:
> > > On Wed, 01/20 17:38, Daniel P. Berrange wrote:
> > > > This converts the qcow2 driver to make use of the QCryptoBlock
> > > > APIs for encrypting image content. As well as continued support
> > > > for the legacy QCow2 encryption format, the appealing benefit
> > > > is that it enables support for the LUKS format inside qcow2.
> > > 
> > > FWIW, with today's QEMU, it's possible to stack format drivers on top of each
> > > other.  In other words, even without this patch, we can make LUKS driver
> > > encrypt/decrypt the qcow2 payload, while keeping them completely orthogonal.
> > 
> > Yep, that is certainly possible, and it is what is intended for using
> > LUKS with RBD, iSCSI, & other network drivers.
> > 
> > I think there is value in having LUKS integrated directly into qcow2
> > though. It means that given a qcow2 file you can 100% reliably
> > distinguish between a file created with the intention of QEMU managing
> > the LUKS encryption, from a file where the guest OS happens to have
> > set up LUKS encryption in its virtual disk. If you don't have this,
> > then given a random qcow2 file, you have to probe to see if LUKS is
> > present or not. Given the security issues we've had in the past with
> > raw images being turned into qcow2 images by a malicious guest writing
> > a qcow2 header, I feel that having explicitly integration LUKS support
> > in QCow is worthwhile as a concept.
> 
> Yes, I'm not objecting to explicit (read: mandatory) encryption in qcow2 in any
> way, and extending the format spec for LUKS is a good thing.
> 
> I mentioned stacked drivers because I wonder how good we can do in reusing
> block/crypto.c code to achieve this (to save 500+ new code in qcow2). For
> example I have a rough idea along this:
> 
> * In qcow2 spec, define type "2" of crypt_method for LUKS encryption, and state
>   that if this method is used, the payload must be LUKS format as defined in
>   cryptsetup's docs/on-disk-format.pdf, and driver will take care of
>   encrypting/decrypting data in a way that is transparent to guest.
> 
> * In qcow2_open, if header's crypt_method is LUKS, set a special flag in the
>   BlockDriverState to indicate that block layer should create an overlay crypto
>   driver (which will be luks in this case), on top of this BDS. To do this we
>   need some modification to bdrv_open.
> 
> * When qcow2_open is done, block layer will then instantiate a LUKS driver,
>   which backed by the qcow2 BDS (as the BDS.file). This LUKS BDS will then be
>   attached to guest device.
> 
> * From the guest PoV, R/W reqs are served as if it's an ordinary qcow2; from
>   LUKS driver's PoV, it works properly formatted luks data, which is backed by
>   whatever it doesn't care, be it an iscsi target, rbd block, local file, or
>   a qcow2 image that has metadata; from the qcow2 driver's PoV, it only made
>   sure that a LUKS driver covers itself, at instantiation time.  Everything
>   else operates the same way as a non-encrypted qcow2.
> 
> * Of course, qcow2_create would need some similar changes to the block layer as
>   to bdrv_open, but that shouldn't be too hard.
> 
> Makes sense?

Hmm, interesting idea. So essentially we'd be having a format driver
be able to inform the generic block code to insert an encryption
layer above it.  The downside is that it leaks details about use
of encryption out into the generic block code. The upside is that
it reduces code in the qcow2 impl.  I do wonder if that would lead
to more or less code overall, or whether the generic block layer
additions would cancel out the removals from qcow2.

BTW, we can probably reduce the amount of code in qcow2 regardless,
as about 1/2 of the addition is related to handling of the QemuOpts
<-> QCryptoBlockOpenOptions conversion, and I could share that
with the block/crypto.c driver to a greater extent than I do now.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source Daniel P. Berrange
  2016-01-21  6:12   ` Fam Zheng
@ 2016-02-04 17:44   ` Eric Blake
  1 sibling, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-04 17:44 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 1877 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> There are three backend impls provided. The preferred
> is gnutls, which is backed by nettle in modern distros.
> The gcrypt impl is provided for cases where QEMU build
> against gnutls is disabled, but crypto is still desired.
> No nettle impl is provided, since it is non-trivial to
> use the nettle APIs for random numbers. Users of nettle
> should ensure gnutls is enabled for QEMU.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> +++ b/crypto/Makefile.objs
> @@ -8,6 +8,10 @@ crypto-obj-y += tlscredsanon.o
>  crypto-obj-y += tlscredsx509.o
>  crypto-obj-y += tlssession.o
>  crypto-obj-y += secret.o
> +crypto-obj-$(if $(CONFIG_GNUTLS),n,$(CONFIG_GCRYPT)) += random-gcrypt.o
> +crypto-obj-$(CONFIG_GNUTLS) += random-gnutls.o
>  
>  # Let the userspace emulators avoid linking gnutls/etc
>  crypto-aes-obj-y = aes.o
> +
> +stub-obj-y += random-stub.o
> \ No newline at end of file

You'll want to fix that newline.

> +++ b/crypto/random-gcrypt.c

> +
> +#include <config-host.h>

Now that we are starting to include "qemu/osdep.h" first everywhere,
you'll want to pick that up on rebase.

> +++ b/include/crypto/random.h

> +
> +/**
> + * qcrypto_random_bytes:
> + * @buf: the buffer to fill
> + * @buflen: length of @buf in bytes
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Fill @buf with @buflen bytes of random data

s/of/of cryptographically strong/

> + *
> + * Returns 0 on sucess, -1 on error
> + */
> +int qcrypto_random_bytes(uint8_t *buf,
> +                         size_t buflen,
> +                         Error **errp);
> +

With those tweaks,
Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
  2016-01-21  6:59   ` Fam Zheng
@ 2016-02-04 22:14   ` Eric Blake
  2016-02-05  9:23     ` Daniel P. Berrange
  2016-02-05 10:13     ` Daniel P. Berrange
  1 sibling, 2 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-04 22:14 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 2976 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> The LUKS data format includes use of PBKDF2 (Password-Based
> Key Derivation Function). The Nettle library can provide
> an implementation of this, but we don't want code directly
> depending on a specific crypto library backend. Introduce
> a include/crypto/pbkdf.h header which defines a QEMU

'an include/...', or maybe 'a new include/...'?

> API for invoking PBKDK2. The initial implementations are
> backed by nettle & gcrypt, which are commonly available
> with distros shipping GNUTLS.
> 
> The test suite data is taken from the cryptsetup codebase
> under the LGPLv2.1+ license. This merely aims to verify
> that whatever backend we provide for this function in QEMU
> will comply with the spec.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

In addition to Fam's review,

> +++ b/crypto/pbkdf-gcrypt.c

> +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> +                   const uint8_t *key, size_t nkey,
> +                   const uint8_t *salt, size_t nsalt,
> +                   unsigned int iterations,
> +                   uint8_t *out, size_t nout,
> +                   Error **errp)
> +{
> +    static const int hash_map[QCRYPTO_HASH_ALG__MAX] = {
> +        [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5,
> +        [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1,
> +        [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256,
> +    };

If QCRYPTO_HASH_ gains future enum values, those elements of the array
will be 0-initialized.

> +    int ret;
> +
> +    if (hash > G_N_ELEMENTS(hash_map)) {
> +        error_setg(errp, "Unexpected hash algorithm %d", hash);
> +        return -1;
> +    }

This checks for beyond the bounds of the array, but not for an element
that was 0-initialized.  Is that a problem we need to worry about?

> +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> +                   const uint8_t *key, size_t nkey,
> +                   const uint8_t *salt, size_t nsalt,
> +                   unsigned int iterations,
> +                   uint8_t *out, size_t nout,
> +                   Error **errp);
> +
> +/**
> + * qcrypto_pbkdf2_count_iters:
> + * @hash: the hash algorithm to use
> + * @key: the user password / key
> + * @nkey: the length of @key in bytes
> + * @salt: a random salt
> + * @nsalt: length of @salt in bytes
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Time the PBKDF2 algorithm to determine how many
> + * iterations are required to derive an encryption
> + * key from a user password provided in @key in 1
> + * second of compute time. The result of this can
> + * be used as a the @iterations parameter of a later
> + * call to qcrypto_pbkdf2().

As machines get faster, will 2^31 still be enough, or do we want a
64-bit iterations counter?


-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors Daniel P. Berrange
  2016-01-21  7:51   ` Fam Zheng
@ 2016-02-04 22:57   ` Eric Blake
  2016-02-05 10:23     ` Daniel P. Berrange
  1 sibling, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-04 22:57 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 6304 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> There are a number of different algorithms that can be used
> to generate initialization vectors for disk encryption. This
> introduces a simple internal QCryptoBlockIV object to provide
> a consistent internal API to the different algorithms. The
> initially implemented algorithms are 'plain', 'plain64' and
> 'essiv', each matching the same named algorithm provided
> by the Linux kernel dm-crypt driver.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> +++ b/crypto/ivgen-essiv.c

> +static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
> +                                    const uint8_t *key, size_t nkey,
> +                                    Error **errp)
> +{
> +    uint8_t *salt;
> +    size_t nhash;
> +    QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
> +
> +    nhash = qcrypto_hash_digest_len(ivgen->hash);
> +    /* Salt must be larger of hash size or key size */
> +    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);

Don't we have a handy MAX() macro for that?

> +++ b/crypto/ivgen-plain.c
> +static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen,
> +                                         uint64_t sector,
> +                                         uint8_t *iv, size_t niv,
> +                                         Error **errp)
> +{
> +    size_t ivprefix;
> +    uint32_t shortsector = cpu_to_le32((uint32_t)(sector & 0xffffffff));

Why do you need both the cast and the mask to 32 bits?

> +++ b/crypto/ivgenpriv.h
> @@ -0,0 +1,49 @@

> +struct QCryptoIVGenDriver {
> +    int (*init)(QCryptoIVGen *biv,

Where'd the name 'biv' come from? But it doesn't affect correctness.

> +++ b/include/crypto/ivgen.h
> @@ -0,0 +1,203 @@

> +
> +/**
> + * This module provides a framework for generating initialization
> + * vectors for block encryption schemes using chained cipher modes
> + * CBC. The principle is that each disk sector is assigned a unique
> + * initialization vector for use for encryption of data in that
> + * sector.
> + *
> + * <example>
> + *   <title>Encrypting block data with initialiation vectors</title>
> + *   <programlisting>

> + * niv =  qcrypto_cipher_get_iv_len(QCRYPTO_CIPHER_ALG_AES_128,
> + *                                  QCRYPTO_CIPHER_MODE_CBC);
> + * iv = g_new0(uint8_t, niv);
> + *
> + *
> + * while (ndata) {
> + *     if (qcrypto_ivgen_calculate(ivgen, sector, iv, niv, errp) < 0) {
> + *         goto error;
> + *     }
> + *     if (qcrypto_cipher_setiv(cipher, iv, niv, errp) < 0) {
> + *         goto error;
> + *     }
> + *     if (qcrypto_cipher_encrypt(cipher,
> + *                                data + (sector * 512),
> + *                                data + (sector * 512),
> + *                                512, errp) < 0) {
> + *         goto error;
> + *     }
> + * }

Umm, this loop is infinite except on errors, because ndata is never
changed.  Are you missing something like 'sector++; ndata -= 512;' ?


> +
> +/**
> + * qcrypto_ivgen_new:
> + * @alg: the initialization vector generation algorithm
> + * @cipheralg: the cipher algorithm or 0
> + * @hash: the hash algorithm or 0,

Inconsistent trailing punctuation.

Why 'or 0'? These two fields are defined by QAPI enums, which start
numbering at 0.  So cipheralg == 0 would by indistinguishable from
requesting the 'aes-128' cipher.  Is the intent to allow a sentinel for
unspecified, when the algorithm can use a default of its choosing?...

> + * @key: the encryption key or NULL
> + * @nkey: the size of @key in bytes
> + *
> + * Create a new initialization vector generator that uses
> + * the algorithm @alg. Whether the remaining parameters
> + * are required or not depends on the choice of @alg
> + * requested.

...Oh, because 0 is okay if @alg won't use the parameter.

> + *
> + * - QCRYPTO_IVGEN_ALG_PLAIN
> + *
> + * The IVs are generated by the 32-bit truncated sector
> + * number. This should never be used for block devices
> + * that are larger than 2^32 sectors in size

Should the code assert/set errp if sector is too large, rather than
blindly truncating it?  I guess it is user-triggerable so assert is
probably bad, but it may still be nice to tell the user up front that
they can't use this mode based on the size of their block device, if we
can figure that out.


> +/**
> + * qcrypto_ivgen_get_cipher:
> + * @ivgen: the IV generator object
> + *
> + * Get the cipher algorithm used by this IV generator (if
> + * applicable)
> + *
> + * Returns: the cipher algorithm
> + */
> +QCryptoCipherAlgorithm qcrypto_ivgen_get_cipher(QCryptoIVGen *ivgen);

Should this return a known and obvious sentinel when the ivgen doesn't
need a cipher, rather than just playing back the user's input (which was
likely 0)?

> +
> +
> +/**
> + * qcrypto_ivgen_get_algorithm:
> + * @ivgen: the IV generator object
> + *
> + * Get the hash algorithm used by this IV generator (if
> + * applicable)
> + *
> + * Returns: the hash algorithm
> + */
> +QCryptoHashAlgorithm qcrypto_ivgen_get_hash(QCryptoIVGen *ivgen);

Copy/paste mismatch in documentation name (this is get_hash, not
get_algorithm)

> +
> +
> +/**
> + * qcrypto_ivgen_free:
> + * @ivgen: the IV generator object
> + *
> + * Release all resources associated with @ivgen

Worth mentioning that it works on NULL?

> +++ b/qapi/crypto.json
> @@ -78,3 +78,19 @@
>  { 'enum': 'QCryptoCipherMode',
>    'prefix': 'QCRYPTO_CIPHER_MODE',
>    'data': ['ecb', 'cbc']}
> +
> +
> +##
> +# QCryptoIVGenAlgorithm:
> +#
> +# The supported algorithms for generating initialization
> +# vectors for full disk encryption
> +#
> +# @plain: 64-bit sector number truncated to 32-bits

Worth a warning to avoid this for disks larger than 2^32 bytes?

> +# @plain64: 64-bit sector number
> +# @essiv: 64-bit sector number encrypted with a hash of the encryption key
> +# Since: 2.6
> +##
> +{ 'enum': 'QCryptoIVGenAlgorithm',
> +  'prefix': 'QCRYPTO_IVGEN_ALG',
> +  'data': ['plain', 'plain64', 'essiv']}


-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
  2016-01-21  8:37   ` Fam Zheng
@ 2016-02-04 23:26   ` Eric Blake
  2016-02-05 12:37     ` Daniel P. Berrange
  2016-02-05 12:39     ` Daniel P. Berrange
  1 sibling, 2 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-04 23:26 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 7037 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> The LUKS format specifies an anti-forensic split algorithm which
> is used to artificially expand the size of the key material on
> disk. This is an implementation of that algorithm.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  crypto/Makefile.objs        |   1 +
>  crypto/afsplit.c            | 162 ++++++++++++++++++++++++++++++++++++++++
>  include/crypto/afsplit.h    | 135 +++++++++++++++++++++++++++++++++
>  tests/.gitignore            |   1 +
>  tests/Makefile              |   2 +
>  tests/test-crypto-afsplit.c | 176 ++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 477 insertions(+)
>  create mode 100644 crypto/afsplit.c
>  create mode 100644 include/crypto/afsplit.h
>  create mode 100644 tests/test-crypto-afsplit.c
> 

In addition to Fam's findings,

> +++ b/crypto/afsplit.c
> @@ -0,0 +1,162 @@
> +/*
> + * QEMU Crypto anti forensic information splitter
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * Derived from cryptsetup package lib/lusk1/af.c
> + *
> + * Copyright (C) 2004, Clemens Fruhwirth <clemens@endorphin.org>
> + * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved.
> + *

> +
> +static void qcrypto_afsplit_xor(size_t blocklen,
> +                                const uint8_t *in1,
> +                                const uint8_t *in2,
> +                                uint8_t *out)
> +{
> +    size_t i;
> +    for (i = 0; i < blocklen; i++) {
> +        out[i] = in1[i] ^ in2[i];
> +    }

Could be made faster using larger data types, if we know that things are
properly aligned.  I'm not sure if the compiler can automatically
optimize this.  I'm also not sure if this is ever on a hot path where it
matters.

> +}
> +
> +
> +static int qcrypto_afsplit_hash(QCryptoHashAlgorithm hash,
> +                                size_t blocklen,
> +                                uint8_t *block,
> +                                Error **errp)
> +{
> +    size_t digestlen = qcrypto_hash_digest_len(hash);
> +
> +    size_t hashcount = blocklen / digestlen;
> +    size_t finallen = blocklen % digestlen;
> +    uint32_t i;
> +
> +    if (finallen) {
> +        hashcount++;
> +    } else {
> +        finallen = blocklen;
> +    }
> +
> +    for (i = 0; i < hashcount; i++) {
> +        uint8_t *out = NULL;
> +        size_t outlen = 0;
> +        uint32_t iv = cpu_to_be32(i);
> +        struct iovec in[] = {
> +            { .iov_base = &iv,
> +              .iov_len = sizeof(iv) },
> +            { .iov_base = block + (i * digestlen),
> +              .iov_len = (i == (hashcount - 1)) ? finallen : digestlen },
> +        };
> +
> +        if (qcrypto_hash_bytesv(hash,
> +                                in,
> +                                G_N_ELEMENTS(in),
> +                                &out, &outlen,
> +                                errp) < 0) {
> +            return -1;
> +        }
> +
> +        if (outlen != digestlen) {
> +            error_setg(errp, "Hash output %zu not %zu",
> +                       outlen, digestlen);
> +            g_free(out);

Is this error even possible, or can it be an assert?

> +            return -1;
> +        }
> +        memcpy(block + (i * digestlen), out,
> +               (i == (hashcount - 1)) ? finallen : digestlen);
> +        g_free(out);
> +    }
> +
> +    return 0;
> +}
> +
> +


> 
> +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
> +                           size_t blocklen,
> +                           uint32_t stripes,
> +                           const uint8_t *in,
> +                           uint8_t *out,
> +                           Error **errp)
> +{
> +    uint8_t *block = g_new0(uint8_t, blocklen);
> +    size_t i;

> 
> +
> +        qcrypto_afsplit_xor(blocklen,
> +                            out + (i * blocklen),
> +                            block,
> +                            block);

Should we ensure no overflow in the multiplication here (or rather, that
the input blocklen * stripes is reasonable on input)?


> +++ b/include/crypto/afsplit.h

> +
> +/**
> + * This module implements the anti-forensic splitter that is specified
> + * as part of the LUKS format:
> + *
> + *   http://clemens.endorphin.org/cryptography
> + *   http://clemens.endorphin.org/TKS1-draft.pdf
> + *
> + * The core idea is to take a short piece of data (key material)
> + * and process it to expand it to a much larger piece of data.
> + * The expansion process is reversable, to obtain the original

s/reversable/reversible/

> + * short data. The key property of the expansion is that if any
> + * byte in the larger data set is changed / missing, it should be
> + * impossible to recreate the original short data.
> + *

> +
> +/**
> + * qcrypto_afsplit_encode:
> + * @hash: the hash algorithm to use for data expansion
> + * @blocklen: the size of @in in bytes
> + * @stripes: the number of times to expand @in in size
> + * @in: the master key to be expanded in size
> + * @out: preallocted buffer to hold the split key

s/preallocted/preallocated/

> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Split the data in @in, which is @blocklen bytes in
> + * size, to form a larger piece of data @out, which is
> + * @blocklen * @stripes bytes in size.
> + *
> + * Returns: 0 on success, -1 on error;
> + */
> +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
> +                           size_t blocklen,
> +                           uint32_t stripes,
> +                           const uint8_t *in,
> +                           uint8_t *out,
> +                           Error **errp);
> +
> +/**
> + * qcrypto_afsplit_decode:
> + * @hash: the hash algorithm to use for data compression
> + * @blocklen: the size of @out in bytes
> + * @stripes: the number of times to decrease @in in size
> + * @in: the master key to be expanded in size
> + * @out: preallocted buffer to hold the split key

and again

> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Join the data in @in, which is @blocklen * @stripes
> + * bytes in size, to form the original small piece o
> + * data @out, which is @blocklen bytes in size.
> + *
> + * Returns: 0 on success, -1 on error;
> + */
> +int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
> +                           size_t blocklen,
> +                           uint32_t stripes,
> +                           const uint8_t *in,
> +                           uint8_t *out,
> +                           Error **errp);

Markus may have an opinion on whether these functions could return void
and just use the errp pointer to report errors; but I'm fine with how
you've done it.


-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework Daniel P. Berrange
@ 2016-02-05  0:23   ` Eric Blake
  2016-02-05 12:43     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-05  0:23 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 12615 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Add a generic framework for support different block encryption
> formats. Upon instantiating a QCryptoBlock object, it will read
> the encryption header and extract the encryption keys. It is
> then possible to call methods to encrypt/decrypt data buffers.
> 
> There is also a mode whereby it will create/initialize a new
> encryption header on a previously unformatted volume.
> 
> The initial framework comes with support for the legacy QCow
> AES based encryption. This enables code in the QCow driver to
> be consolidated later.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  crypto/Makefile.objs      |   2 +
>  crypto/block-qcow.c       | 167 +++++++++++++++++++++++++++++
>  crypto/block-qcow.h       |  28 +++++
>  crypto/block.c            | 263 ++++++++++++++++++++++++++++++++++++++++++++++
>  crypto/blockpriv.h        |  90 ++++++++++++++++
>  include/crypto/block.h    | 233 ++++++++++++++++++++++++++++++++++++++++
>  qapi/crypto.json          |  67 ++++++++++++
>  tests/.gitignore          |   1 +
>  tests/Makefile            |   2 +
>  tests/test-crypto-block.c | 239 +++++++++++++++++++++++++++++++++++++++++
>  10 files changed, 1092 insertions(+)
>  create mode 100644 crypto/block-qcow.c
>  create mode 100644 crypto/block-qcow.h
>  create mode 100644 crypto/block.c
>  create mode 100644 crypto/blockpriv.h
>  create mode 100644 include/crypto/block.h
>  create mode 100644 tests/test-crypto-block.c
> 

> +++ b/crypto/block-qcow.c
> @@ -0,0 +1,167 @@
> +/*
> + * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */

Maybe worth a big comment stating that this file exists for backwards
compatibility, and no one in their right mind should copy the code
and/or encrypt new files with it.

> 
> +static gboolean
> +qcrypto_block_qcow_has_format(const uint8_t *buf G_GNUC_UNUSED,
> +                              size_t buf_size G_GNUC_UNUSED)
> +{
> +    return false;
> +}

When I see gboolean, I think TRUE/FALSE.  Yes, C99 'false' happens to
promote to the correct value for whatever integer type gboolean is, but
this would read nicer if it returned 'bool'.

> +
> +static int
> +qcrypto_block_qcow_init(QCryptoBlock *block,
> +                        const char *keysecret,
> +                        Error **errp)
> +{
> +    char *password;
> +    int ret;
> +    uint8_t keybuf[16];
> +    int len, i;
> +
> +    memset(keybuf, 0, 16);
> +
> +    password = qcrypto_secret_lookup_as_utf8(keysecret, errp);
> +    if (!password) {
> +        return -1;
> +    }
> +
> +    len = strlen(password);
> +    if (len > 16) {
> +        len = 16;
> +    }
> +    for (i = 0; i < len; i++) {
> +        keybuf[i] = password[i];
> +    }

What - we really throw away anything longer than 16 bytes?

Would a memcpy() be any nicer than an open-coded loop?


> +
> +static int
> +qcrypto_block_qcow_create(QCryptoBlock *block,
> +                          QCryptoBlockCreateOptions *options,
> +                          QCryptoBlockInitFunc initfunc G_GNUC_UNUSED,
> +                          QCryptoBlockWriteFunc writefunc G_GNUC_UNUSED,
> +                          void *opaque G_GNUC_UNUSED,
> +                          Error **errp)
> +{
> +    if (!options->u.qcow->key_secret) {
> +        error_setg(errp, "Parameter 'key-secret' is required for cipher");
> +        return -1;
> +    }
> +    /* QCow2 has no special header, since everything is hardwired */
> +    return qcrypto_block_qcow_init(block, options->u.qcow->key_secret, errp);
> +}
> +
> +
> +static void
> +qcrypto_block_qcow_cleanup(QCryptoBlock *block)
> +{
> +}

Nothing to free?  qcrypto_block_qcow_init cleaned up block->cipher and
block->ivgen on error, so shouldn't this do likewise (and then the init
could call this function instead of open-coding the cleanup)?...

> diff --git a/crypto/block.c b/crypto/block.c
> new file mode 100644
> index 0000000..757e28a
> --- /dev/null
> +++ b/crypto/block.c
> @@ -0,0 +1,263 @@

> +uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block)
> +{
> +    return block->payload_offset;
> +}
> +
> +
> +void qcrypto_block_free(QCryptoBlock *block)
> +{
> +    if (!block) {
> +        return;
> +    }
> +
> +    block->driver->cleanup(block);
> +
> +    qcrypto_cipher_free(block->cipher);
> +    qcrypto_ivgen_free(block->ivgen);
> +    g_free(block);

...oh, you centralized that part of the cleanup.

> +++ b/crypto/blockpriv.h

> +
> +typedef struct QCryptoBlockDriver QCryptoBlockDriver;
> +
> +struct QCryptoBlock {
> +    QCryptoBlockFormat format;
> +
> +    const QCryptoBlockDriver *driver;
> +    void *opaque;
> +
> +    QCryptoCipher *cipher;
> +    QCryptoIVGen *ivgen;
> +    QCryptoHashAlgorithm kdfhash;
> +    size_t niv;
> +    uint64_t payload_offset; /* In 512 byte sectors */

Someday, we may want to support 4k sectors natively.  I don't envy the
person making the conversion, but we could at least make their life
easier by representing this in bytes instead of units of 512-byte sectors.

> +};
> +
> +struct QCryptoBlockDriver {
> +    int (*open)(QCryptoBlock *block,
> +                QCryptoBlockOpenOptions *options,
> +                QCryptoBlockReadFunc readfunc,
> +                void *opaque,
> +                unsigned int flags,
> +                Error **errp);

No documentation on any of these contracts?

> +
> +    gboolean (*has_format)(const uint8_t *buf,
> +                           size_t buflen);

Why gboolean instead of bool?

> +++ b/include/crypto/block.h


> +/**
> + * qcrypto_block_has_format:
> + * @format: the encryption format
> + * @buf: the data from head of the volume
> + * @len: the length of @buf in bytes
> + *
> + * Given @len bytes of data from the head of a storage volume
> + * in @buf, probe to determine if the volume has the encryption
> + * format specified in @format.
> + *
> + * Returns: true if the data in @buf matches @format
> + */
> +gboolean qcrypto_block_has_format(QCryptoBlockFormat format,

Again, this should probably be 'bool', not gboolean.

> +                                  const uint8_t *buf,
> +                                  size_t buflen);
> +
> +typedef enum {
> +    QCRYPTO_BLOCK_OPEN_NO_IO = (1 << 0),
> +} QCryptoBlockOpenFlags;
> +
> +/**
> + * qcrypto_block_open:
> + * @options: the encryption options
> + * @readfunc: callback for reading data from the volume
> + * @opaque: data to pass to @readfunc
> + * @flags: bitmask of QCryptoBlockOpenFlags values
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Create a new block encryption object for an existing
> + * storage volume encrypted with format identified by
> + * the parameters in @options.
> + *
> + * This will use @readfunc to initialize the encryption
> + * context based on the volume header(s), extracting the
> + * master key(s) as required.
> + *
> + * If @flags contains QCRYPTO_BLOCK_OPEN_NO_IO then
> + * the open process will be optimized to skip any parts
> + * that are only required to perform I/O. In particular
> + * this would usually avoid the need to decrypt any
> + * master keys. The only thing that can be done with
> + * the resulting QCryptoBlock object would be to query
> + * metadata such as the payload offset. There will be
> + * no cipher or ivgen objects available.
> + *
> + * If any part of initializing the encryption context
> + * fails an error will be returned. This could be due
> + * to the volume being in the wrong format, an cipher
> + * or IV generator algorithm that is not supoported,

s/supoported/supported/

> + * or incorrect passphrases.
> + *
> + * Returns: a block encryption format, or NULL on error
> + */
> +QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
> +                                 QCryptoBlockReadFunc readfunc,
> +                                 void *opaque,
> +                                 unsigned int flags,
> +                                 Error **errp);
> +
> +/**
> + * qcrypto_block_create:
> + * @format: the encryption format
> + * @initfunc: callback for initializing volume header
> + * @writefunc: callback for writing data to the volume header
> + * @opaque: data to pass to @initfunc & @writefunc

I'd spell it out s/&/and/

> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Create a new block encryption object for initializing
> + * a storage volume to be encrypted with format identified
> + * by the parameters in @options.
> + *
> + * This method will allocate space for a new volume header
> + * using @initfunc and then write header data using @writefunc,
> + * generating new master keys, etc as required. Any existing
> + * data present on the volume will be irrevokably destroyed.

s/irrevokably/irrevocably/

> + *
> + * If any part of initializing the encryption context
> + * fails an error will be returned. This could be due
> + * to the volume being in the wrong format, an cipher
> + * or IV generator algorithm that is not supoported,

s/supoported/supported/

> + * or incorrect passphrases.
> + *
> + * Returns: a block encryption format, or NULL on error
> + */
> +QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
> +                                   QCryptoBlockInitFunc initfunc,
> +                                   QCryptoBlockWriteFunc writefunc,
> +                                   void *opaque,
> +                                   Error **errp);
> +
> +/**
> + * @qcrypto_block_decrypt:
> + * @block: the block encryption object
> + * @startsector: the sector from which @buf was read

From the guest's point of view, right?

> + * @buf: the buffer to decrypt
> + * @len: the length of @buf in bytes
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Decrypt @len bytes of cipher text in @buf, writing
> + * plain text back into @buf
> + *
> + * Returns 0 on success, -1 on failure
> + */
> +int qcrypto_block_decrypt(QCryptoBlock *block,
> +                          uint64_t startsector,
> +                          uint8_t *buf,
> +                          size_t len,
> +                          Error **errp);
> +
> +/**
> + * @qcrypto_block_encrypt:
> + * @block: the block encryption object
> + * @startsector: the sector to which @buf will be written

Likewise, from the guest's point of view, right?

> + * @buf: the buffer to decrypt
> + * @len: the length of @buf in bytes
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Encrypt @len bytes of plain text in @buf, writing
> + * cipher text back into @buf
> + *
> + * Returns 0 on success, -1 on failure
> + */
> +int qcrypto_block_encrypt(QCryptoBlock *block,
> +                          uint64_t startsector,
> +                          uint8_t *buf,
> +                          size_t len,
> +                          Error **errp);
> +


> +/**
> + * qcrypto_block_get_payload_offset:
> + * @block: the block encryption object
> + *
> + * Get the offset to the payload indicated by the
> + * encryption header. The offset is measured in
> + * 512 byte sectors
> + *
> + * Returns: the payload offset in sectors.
> + */
> +uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block);

Please, let's use bytes here, not sectors.

> +++ b/tests/test-crypto-block.c

Nice tests. Overall looks like we are fairly close to having this ready
to commit.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-02-04 22:14   ` Eric Blake
@ 2016-02-05  9:23     ` Daniel P. Berrange
  2016-02-05 10:13     ` Daniel P. Berrange
  1 sibling, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05  9:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 03:14:10PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> > +                   const uint8_t *key, size_t nkey,
> > +                   const uint8_t *salt, size_t nsalt,
> > +                   unsigned int iterations,
> > +                   uint8_t *out, size_t nout,
> > +                   Error **errp);
> > +
> > +/**
> > + * qcrypto_pbkdf2_count_iters:
> > + * @hash: the hash algorithm to use
> > + * @key: the user password / key
> > + * @nkey: the length of @key in bytes
> > + * @salt: a random salt
> > + * @nsalt: length of @salt in bytes
> > + * @errp: pointer to a NULL-initialized error object
> > + *
> > + * Time the PBKDF2 algorithm to determine how many
> > + * iterations are required to derive an encryption
> > + * key from a user password provided in @key in 1
> > + * second of compute time. The result of this can
> > + * be used as a the @iterations parameter of a later
> > + * call to qcrypto_pbkdf2().
> 
> As machines get faster, will 2^31 still be enough, or do we want a
> 64-bit iterations counter?

The luks format only has space to store a 32-bit int for interations,
so 64-bit would require an on disk format change. On my current modern
laptop we're getting iteration counts in the 400,000 range, so it will
be a decent while before we hit 2^31, by which time LUKS maintainers
will have to do a format change.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm
  2016-02-04 22:14   ` Eric Blake
  2016-02-05  9:23     ` Daniel P. Berrange
@ 2016-02-05 10:13     ` Daniel P. Berrange
  1 sibling, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 10:13 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 03:14:10PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > The LUKS data format includes use of PBKDF2 (Password-Based
> > Key Derivation Function). The Nettle library can provide
> > an implementation of this, but we don't want code directly
> > depending on a specific crypto library backend. Introduce
> > a include/crypto/pbkdf.h header which defines a QEMU
> 
> 'an include/...', or maybe 'a new include/...'?
> 
> > API for invoking PBKDK2. The initial implementations are
> > backed by nettle & gcrypt, which are commonly available
> > with distros shipping GNUTLS.
> > 
> > The test suite data is taken from the cryptsetup codebase
> > under the LGPLv2.1+ license. This merely aims to verify
> > that whatever backend we provide for this function in QEMU
> > will comply with the spec.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> 
> In addition to Fam's review,
> 
> > +++ b/crypto/pbkdf-gcrypt.c
> 
> > +int qcrypto_pbkdf2(QCryptoHashAlgorithm hash,
> > +                   const uint8_t *key, size_t nkey,
> > +                   const uint8_t *salt, size_t nsalt,
> > +                   unsigned int iterations,
> > +                   uint8_t *out, size_t nout,
> > +                   Error **errp)
> > +{
> > +    static const int hash_map[QCRYPTO_HASH_ALG__MAX] = {
> > +        [QCRYPTO_HASH_ALG_MD5] = GCRY_MD_MD5,
> > +        [QCRYPTO_HASH_ALG_SHA1] = GCRY_MD_SHA1,
> > +        [QCRYPTO_HASH_ALG_SHA256] = GCRY_MD_SHA256,
> > +    };
> 
> If QCRYPTO_HASH_ gains future enum values, those elements of the array
> will be 0-initialized.
> 
> > +    int ret;
> > +
> > +    if (hash > G_N_ELEMENTS(hash_map)) {
> > +        error_setg(errp, "Unexpected hash algorithm %d", hash);
> > +        return -1;
> > +    }
> 
> This checks for beyond the bounds of the array, but not for an element
> that was 0-initialized.  Is that a problem we need to worry about?

I'll add  '||  hash_map[hash] == GCRY_MD_NONE' to this condition


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-02-04 22:57   ` Eric Blake
@ 2016-02-05 10:23     ` Daniel P. Berrange
  2016-02-05 13:23       ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 10:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 03:57:33PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > There are a number of different algorithms that can be used
> > to generate initialization vectors for disk encryption. This
> > introduces a simple internal QCryptoBlockIV object to provide
> > a consistent internal API to the different algorithms. The
> > initially implemented algorithms are 'plain', 'plain64' and
> > 'essiv', each matching the same named algorithm provided
> > by the Linux kernel dm-crypt driver.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> 
> > +++ b/crypto/ivgen-essiv.c
> 
> > +static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
> > +                                    const uint8_t *key, size_t nkey,
> > +                                    Error **errp)
> > +{
> > +    uint8_t *salt;
> > +    size_t nhash;
> > +    QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
> > +
> > +    nhash = qcrypto_hash_digest_len(ivgen->hash);
> > +    /* Salt must be larger of hash size or key size */
> > +    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);
> 
> Don't we have a handy MAX() macro for that?

Yes

> > +++ b/crypto/ivgen-plain.c
> > +static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen,
> > +                                         uint64_t sector,
> > +                                         uint8_t *iv, size_t niv,
> > +                                         Error **errp)
> > +{
> > +    size_t ivprefix;
> > +    uint32_t shortsector = cpu_to_le32((uint32_t)(sector & 0xffffffff));
> 
> Why do you need both the cast and the mask to 32 bits?

I'll remove the cast. I'll also add in

    if (shortsector != sector) {
        error_setg(errp, "Sector '%llu' is too large for 'plain' "
                   "initialization vector generator",
                   (long long unsigned)sector);
        return -1;
    }

> 
> > +++ b/crypto/ivgenpriv.h
> > @@ -0,0 +1,49 @@
> 
> > +struct QCryptoIVGenDriver {
> > +    int (*init)(QCryptoIVGen *biv,
> 
> Where'd the name 'biv' come from? But it doesn't affect correctness.

An older version of the code called the struct  QCryptoBlockIV.
I'll change this to 'ivgen' to match elsewhere.


> > + * while (ndata) {
> > + *     if (qcrypto_ivgen_calculate(ivgen, sector, iv, niv, errp) < 0) {
> > + *         goto error;
> > + *     }
> > + *     if (qcrypto_cipher_setiv(cipher, iv, niv, errp) < 0) {
> > + *         goto error;
> > + *     }
> > + *     if (qcrypto_cipher_encrypt(cipher,
> > + *                                data + (sector * 512),
> > + *                                data + (sector * 512),
> > + *                                512, errp) < 0) {
> > + *         goto error;
> > + *     }
> > + * }
> 
> Umm, this loop is infinite except on errors, because ndata is never
> changed.  Are you missing something like 'sector++; ndata -= 512;' ?

Yes, will add those.

> > + *
> > + * - QCRYPTO_IVGEN_ALG_PLAIN
> > + *
> > + * The IVs are generated by the 32-bit truncated sector
> > + * number. This should never be used for block devices
> > + * that are larger than 2^32 sectors in size
> 
> Should the code assert/set errp if sector is too large, rather than
> blindly truncating it?  I guess it is user-triggerable so assert is
> probably bad, but it may still be nice to tell the user up front that
> they can't use this mode based on the size of their block device, if we
> can figure that out.

I put an error check in as mentioned above.

> > +/**
> > + * qcrypto_ivgen_get_cipher:
> > + * @ivgen: the IV generator object
> > + *
> > + * Get the cipher algorithm used by this IV generator (if
> > + * applicable)
> > + *
> > + * Returns: the cipher algorithm
> > + */
> > +QCryptoCipherAlgorithm qcrypto_ivgen_get_cipher(QCryptoIVGen *ivgen);
> 
> Should this return a known and obvious sentinel when the ivgen doesn't
> need a cipher, rather than just playing back the user's input (which was
> likely 0)?

I feel it is nicer to just be consistent with the user's input.

> > +/**
> > + * qcrypto_ivgen_free:
> > + * @ivgen: the IV generator object
> > + *
> > + * Release all resources associated with @ivgen
> 
> Worth mentioning that it works on NULL?

Yep.

> > +# QCryptoIVGenAlgorithm:
> > +#
> > +# The supported algorithms for generating initialization
> > +# vectors for full disk encryption
> > +#
> > +# @plain: 64-bit sector number truncated to 32-bits
> 
> Worth a warning to avoid this for disks larger than 2^32 bytes?

Yes, will do.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-02-04 23:26   ` Eric Blake
@ 2016-02-05 12:37     ` Daniel P. Berrange
  2016-02-05 12:39     ` Daniel P. Berrange
  1 sibling, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 12:37 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 04:26:42PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > +++ b/crypto/afsplit.c
> > @@ -0,0 +1,162 @@
> > +/*
> > + * QEMU Crypto anti forensic information splitter
> > + *
> > + * Copyright (c) 2015-2016 Red Hat, Inc.
> > + *
> > + * Derived from cryptsetup package lib/lusk1/af.c
> > + *
> > + * Copyright (C) 2004, Clemens Fruhwirth <clemens@endorphin.org>
> > + * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved.
> > + *
> 
> > +
> > +static void qcrypto_afsplit_xor(size_t blocklen,
> > +                                const uint8_t *in1,
> > +                                const uint8_t *in2,
> > +                                uint8_t *out)
> > +{
> > +    size_t i;
> > +    for (i = 0; i < blocklen; i++) {
> > +        out[i] = in1[i] ^ in2[i];
> > +    }
> 
> Could be made faster using larger data types, if we know that things are
> properly aligned.  I'm not sure if the compiler can automatically
> optimize this.  I'm also not sure if this is ever on a hot path where it
> matters.

It is used when opening an encrypted volume, or when formatting
an encrypted volume. Both those codepaths are made intentionally
very slow by the pbkdf algorithm, so slowness in the afsplit code
is not going to be noticable overall.

> > +static int qcrypto_afsplit_hash(QCryptoHashAlgorithm hash,
> > +                                size_t blocklen,
> > +                                uint8_t *block,
> > +                                Error **errp)
> > +{
> > +    size_t digestlen = qcrypto_hash_digest_len(hash);

[snip]

> > +        if (qcrypto_hash_bytesv(hash,
> > +                                in,
> > +                                G_N_ELEMENTS(in),
> > +                                &out, &outlen,
> > +                                errp) < 0) {
> > +            return -1;
> > +        }
> > +
> > +        if (outlen != digestlen) {
> > +            error_setg(errp, "Hash output %zu not %zu",
> > +                       outlen, digestlen);
> > +            g_free(out);
> 
> Is this error even possible, or can it be an assert?

Yes it can assert, since it should be impossible.

> > +int qcrypto_afsplit_encode(QCryptoHashAlgorithm hash,
> > +                           size_t blocklen,
> > +                           uint32_t stripes,
> > +                           const uint8_t *in,
> > +                           uint8_t *out,
> > +                           Error **errp)
> > +{
> > +    uint8_t *block = g_new0(uint8_t, blocklen);
> > +    size_t i;
> 
> > 
> > +
> > +        qcrypto_afsplit_xor(blocklen,
> > +                            out + (i * blocklen),
> > +                            block,
> > +                            block);
> 
> Should we ensure no overflow in the multiplication here (or rather, that
> the input blocklen * stripes is reasonable on input)?

The use case for this code is splitting an encryption key. So we're
talking blocklen in the 100's and stripes i the 1000's. So we're
not going to be anywhere near possible overflow.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm
  2016-02-04 23:26   ` Eric Blake
  2016-02-05 12:37     ` Daniel P. Berrange
@ 2016-02-05 12:39     ` Daniel P. Berrange
  1 sibling, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 12:39 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 04:26:42PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > The LUKS format specifies an anti-forensic split algorithm which
> > is used to artificially expand the size of the key material on
> > disk. This is an implementation of that algorithm.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  crypto/Makefile.objs        |   1 +
> >  crypto/afsplit.c            | 162 ++++++++++++++++++++++++++++++++++++++++
> >  include/crypto/afsplit.h    | 135 +++++++++++++++++++++++++++++++++
> >  tests/.gitignore            |   1 +
> >  tests/Makefile              |   2 +
> >  tests/test-crypto-afsplit.c | 176 ++++++++++++++++++++++++++++++++++++++++++++
> >  6 files changed, 477 insertions(+)
> >  create mode 100644 crypto/afsplit.c
> >  create mode 100644 include/crypto/afsplit.h
> >  create mode 100644 tests/test-crypto-afsplit.c
> > 
> > + * @errp: pointer to a NULL-initialized error object
> > + *
> > + * Join the data in @in, which is @blocklen * @stripes
> > + * bytes in size, to form the original small piece o
> > + * data @out, which is @blocklen bytes in size.
> > + *
> > + * Returns: 0 on success, -1 on error;
> > + */
> > +int qcrypto_afsplit_decode(QCryptoHashAlgorithm hash,
> > +                           size_t blocklen,
> > +                           uint32_t stripes,
> > +                           const uint8_t *in,
> > +                           uint8_t *out,
> > +                           Error **errp);
> 
> Markus may have an opinion on whether these functions could return void
> and just use the errp pointer to report errors; but I'm fine with how
> you've done it.

I have a preference to be non-void, as it avoids constantly having to
use a 'Error local_err' and error_propagate to check for failure, so
results in smaller & simpler code.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework
  2016-02-05  0:23   ` Eric Blake
@ 2016-02-05 12:43     ` Daniel P. Berrange
  2016-02-05 18:48       ` Eric Blake
  0 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 12:43 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Thu, Feb 04, 2016 at 05:23:32PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Add a generic framework for support different block encryption
> > formats. Upon instantiating a QCryptoBlock object, it will read
> > the encryption header and extract the encryption keys. It is
> > then possible to call methods to encrypt/decrypt data buffers.
> > 
> > There is also a mode whereby it will create/initialize a new
> > encryption header on a previously unformatted volume.
> > 
> > The initial framework comes with support for the legacy QCow
> > AES based encryption. This enables code in the QCow driver to
> > be consolidated later.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  crypto/Makefile.objs      |   2 +
> >  crypto/block-qcow.c       | 167 +++++++++++++++++++++++++++++
> >  crypto/block-qcow.h       |  28 +++++
> >  crypto/block.c            | 263 ++++++++++++++++++++++++++++++++++++++++++++++
> >  crypto/blockpriv.h        |  90 ++++++++++++++++
> >  include/crypto/block.h    | 233 ++++++++++++++++++++++++++++++++++++++++
> >  qapi/crypto.json          |  67 ++++++++++++
> >  tests/.gitignore          |   1 +
> >  tests/Makefile            |   2 +
> >  tests/test-crypto-block.c | 239 +++++++++++++++++++++++++++++++++++++++++
> >  10 files changed, 1092 insertions(+)
> >  create mode 100644 crypto/block-qcow.c
> >  create mode 100644 crypto/block-qcow.h
> >  create mode 100644 crypto/block.c
> >  create mode 100644 crypto/blockpriv.h
> >  create mode 100644 include/crypto/block.h
> >  create mode 100644 tests/test-crypto-block.c
> > 
> 
> > +++ b/crypto/block-qcow.c
> > @@ -0,0 +1,167 @@
> > +/*
> > + * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format
> > + *
> > + * Copyright (c) 2015-2016 Red Hat, Inc.
> > + *
> > + * This library is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU Lesser General Public
> > + * License as published by the Free Software Foundation; either
> > + * version 2 of the License, or (at your option) any later version.
> > + *
> > + * This library 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
> > + * Lesser General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU Lesser General Public
> > + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> > + *
> > + */
> 
> Maybe worth a big comment stating that this file exists for backwards
> compatibility, and no one in their right mind should copy the code
> and/or encrypt new files with it.

Heh, yeah.

> > +static gboolean
> > +qcrypto_block_qcow_has_format(const uint8_t *buf G_GNUC_UNUSED,
> > +                              size_t buf_size G_GNUC_UNUSED)
> > +{
> > +    return false;
> > +}
> 
> When I see gboolean, I think TRUE/FALSE.  Yes, C99 'false' happens to
> promote to the correct value for whatever integer type gboolean is, but
> this would read nicer if it returned 'bool'.

Yeah, dunno what I was thinking really. I use gboolean in a few places
due to the gmainloop callback API contract, but this should not have
needed it.

> > +static int
> > +qcrypto_block_qcow_init(QCryptoBlock *block,
> > +                        const char *keysecret,
> > +                        Error **errp)
> > +{
> > +    char *password;
> > +    int ret;
> > +    uint8_t keybuf[16];
> > +    int len, i;
> > +
> > +    memset(keybuf, 0, 16);
> > +
> > +    password = qcrypto_secret_lookup_as_utf8(keysecret, errp);
> > +    if (!password) {
> > +        return -1;
> > +    }
> > +
> > +    len = strlen(password);
> > +    if (len > 16) {
> > +        len = 16;
> > +    }
> > +    for (i = 0; i < len; i++) {
> > +        keybuf[i] = password[i];
> > +    }
> 
> What - we really throw away anything longer than 16 bytes?

Yes, that's how awesome legacy qcow2 encryption is :-)

> Would a memcpy() be any nicer than an open-coded loop?

Sure.

> > +++ b/crypto/blockpriv.h
> 
> > +
> > +typedef struct QCryptoBlockDriver QCryptoBlockDriver;
> > +
> > +struct QCryptoBlock {
> > +    QCryptoBlockFormat format;
> > +
> > +    const QCryptoBlockDriver *driver;
> > +    void *opaque;
> > +
> > +    QCryptoCipher *cipher;
> > +    QCryptoIVGen *ivgen;
> > +    QCryptoHashAlgorithm kdfhash;
> > +    size_t niv;
> > +    uint64_t payload_offset; /* In 512 byte sectors */
> 
> Someday, we may want to support 4k sectors natively.  I don't envy the
> person making the conversion, but we could at least make their life
> easier by representing this in bytes instead of units of 512-byte sectors.

Ok will do.

> > +};
> > +
> > +struct QCryptoBlockDriver {
> > +    int (*open)(QCryptoBlock *block,
> > +                QCryptoBlockOpenOptions *options,
> > +                QCryptoBlockReadFunc readfunc,
> > +                void *opaque,
> > +                unsigned int flags,
> > +                Error **errp);
> 
> No documentation on any of these contracts?

They're essentially identical to the public API contracts, so I
figured it was redundant to duplicate those docs.


> > +/**
> > + * qcrypto_block_has_format:
> > + * @format: the encryption format
> > + * @buf: the data from head of the volume
> > + * @len: the length of @buf in bytes
> > + *
> > + * Given @len bytes of data from the head of a storage volume
> > + * in @buf, probe to determine if the volume has the encryption
> > + * format specified in @format.
> > + *
> > + * Returns: true if the data in @buf matches @format
> > + */
> > +gboolean qcrypto_block_has_format(QCryptoBlockFormat format,
> 
> Again, this should probably be 'bool', not gboolean.

Yep


> > +QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
> > +                                   QCryptoBlockInitFunc initfunc,
> > +                                   QCryptoBlockWriteFunc writefunc,
> > +                                   void *opaque,
> > +                                   Error **errp);
> > +
> > +/**
> > + * @qcrypto_block_decrypt:
> > + * @block: the block encryption object
> > + * @startsector: the sector from which @buf was read
> 
> From the guest's point of view, right?

Yes & no, it is the sector of whatever underlying storage holds
the image. If there are other formats beneath the crypto format
it might not correspond to the guest OS seen sector.


> > +/**
> > + * qcrypto_block_get_payload_offset:
> > + * @block: the block encryption object
> > + *
> > + * Get the offset to the payload indicated by the
> > + * encryption header. The offset is measured in
> > + * 512 byte sectors
> > + *
> > + * Returns: the payload offset in sectors.
> > + */
> > +uint64_t qcrypto_block_get_payload_offset(QCryptoBlock *block);
> 
> Please, let's use bytes here, not sectors.

Ok


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors
  2016-02-05 10:23     ` Daniel P. Berrange
@ 2016-02-05 13:23       ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-05 13:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Fri, Feb 05, 2016 at 10:23:18AM +0000, Daniel P. Berrange wrote:
> On Thu, Feb 04, 2016 at 03:57:33PM -0700, Eric Blake wrote:
> > On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > > There are a number of different algorithms that can be used
> > > to generate initialization vectors for disk encryption. This
> > > introduces a simple internal QCryptoBlockIV object to provide
> > > a consistent internal API to the different algorithms. The
> > > initially implemented algorithms are 'plain', 'plain64' and
> > > 'essiv', each matching the same named algorithm provided
> > > by the Linux kernel dm-crypt driver.
> > > 
> > > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > > ---
> > 
> > > +++ b/crypto/ivgen-essiv.c

> > > +++ b/crypto/ivgen-plain.c
> > > +static int qcrypto_ivgen_plain_calculate(QCryptoIVGen *ivgen,
> > > +                                         uint64_t sector,
> > > +                                         uint8_t *iv, size_t niv,
> > > +                                         Error **errp)
> > > +{
> > > +    size_t ivprefix;
> > > +    uint32_t shortsector = cpu_to_le32((uint32_t)(sector & 0xffffffff));
> > 
> > Why do you need both the cast and the mask to 32 bits?
> 
> I'll remove the cast. I'll also add in
> 
>     if (shortsector != sector) {
>         error_setg(errp, "Sector '%llu' is too large for 'plain' "
>                    "initialization vector generator",
>                    (long long unsigned)sector);
>         return -1;
>     }

[snip]

> > > + *
> > > + * - QCRYPTO_IVGEN_ALG_PLAIN
> > > + *
> > > + * The IVs are generated by the 32-bit truncated sector
> > > + * number. This should never be used for block devices
> > > + * that are larger than 2^32 sectors in size
> > 
> > Should the code assert/set errp if sector is too large, rather than
> > blindly truncating it?  I guess it is user-triggerable so assert is
> > probably bad, but it may still be nice to tell the user up front that
> > they can't use this mode based on the size of their block device, if we
> > can figure that out.
> 
> I put an error check in as mentioned above.

Actually we must not treat this is an error - we must silently truncate
to 32-bits, in order to retain compatibility with Linux dm-crypt formatted
volumes.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format Daniel P. Berrange
@ 2016-02-05 17:38   ` Eric Blake
  2016-02-08 16:03     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-05 17:38 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 28076 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Provide a block encryption implementation that follows the
> LUKS/dm-crypt specification.
> 
> This supports all combinations of hash, cipher algorithm,
> cipher mode and iv generator that are implemented by the
> current crypto layer.
> 
> The notable missing feature is support for the 'xts'
> cipher mode, which is commonly used for disk encryption
> instead of 'cbc'. This is because it is not provided by
> either nettle or libgcrypt. A suitable implementation
> will be identified & integrated later.
> 
> There is support for opening existing volumes formatted
> by dm-crypt, and for formatting new volumes. In the latter
> case it will only use key slot 0.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> +++ b/crypto/block-luks.c
> @@ -0,0 +1,1105 @@
> +/*
> + * QEMU Crypto block device encryption LUKS format
> + *

> +
> +/*
> + * Reference for format is
> + *
> + * docs/on-disk-format.pdf
> + *
> + * In 'cryptsetup' package source code
> + *

Worth calling out the document version number? I reviewed based on
version 1.2.1, dated Oct 2011.

> + */
> +
> +typedef struct QCryptoBlockLUKS QCryptoBlockLUKS;
> +typedef struct QCryptoBlockLUKSHeader QCryptoBlockLUKSHeader;
> +typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
> +
> +#define QCRYPTO_BLOCK_LUKS_VERSION 1
> +
> +#define QCRYPTO_BLOCK_LUKS_MAGIC_LEN 6
> +#define QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN 32
> +#define QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN 32
> +#define QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN 32
> +#define QCRYPTO_BLOCK_LUKS_DIGEST_LEN 20
> +#define QCRYPTO_BLOCK_LUKS_SALT_LEN 32
> +#define QCRYPTO_BLOCK_LUKS_UUID_LEN 40

Matches the spec, but seems awfully wasteful.  A binary UUID is 16
bytes; the standard ASCII representation is only 36 bytes.  But the spec
calls it out as char[] (requiring a 37th byte for a NUL terminator, then
rounding it up for aligned field access).

> +#define QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS 8
> +#define QCRYPTO_BLOCK_LUKS_STRIPES 4000
> +#define QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS 1000
> +#define QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS 1000
> +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET 4096
> +
> +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED 0x0000DEAD
> +#define QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED 0x00AC71F3

Cute l33t spelling choice by the upstream folks :)


> +
> +static const QCryptoBlockLUKSCipherSizeMap
> +qcrypto_block_luks_cipher_size_map_aes[] = {
> +    { 16, QCRYPTO_CIPHER_ALG_AES_128 },
> +    { 24, QCRYPTO_CIPHER_ALG_AES_192 },
> +    { 32, QCRYPTO_CIPHER_ALG_AES_256 },
> +    { 0, 0 },
> +};
> +
> +static const QCryptoBlockLUKSCipherNameMap
> +qcrypto_block_luks_cipher_name_map[] = {
> +    { "aes", qcrypto_block_luks_cipher_size_map_aes, },

Inconsistent on the use of trailing , within the inner {} between these
two arrays.

> +};
> +
> +
> +struct QCryptoBlockLUKSKeySlot {
> +    /* state of keyslot, enabled/disable */
> +    uint32_t active;

May be worth a comment that this struct is written to disk in
big-endian, but used in native-endian for most operations for
convenience; to make sure future developers remember to put byteswaps in
the correct locations.  But it looks like you handled endianness correctly.

> +    /* iterations for PBKDF2 */
> +    uint32_t iterations;
> +    /* salt for PBKDF2 */
> +    uint8_t salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> +    /* start sector of key material */
> +    uint32_t key_offset;
> +    /* number of anti-forensic stripes */
> +    uint32_t stripes;
> +} QEMU_PACKED;

Packed makes sense since we are writing it straight to disk; although it
looks like natural alignment prevails and this did not need to be
packed.  Do we want some sort of BUG_ON() that we have the right size
struct?

> +
> +struct QCryptoBlockLUKSHeader {
> +    /* 'L', 'U', 'K', 'S', '0xBA', '0xBE' */
> +    char magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN];
> +
> +    /* LUKS version, currently 1 */
> +    uint16_t version;
> +
> +    /* cipher name specification (aes, etc */

Missing )

> +    char cipher_name[QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN];
> +
> +    /* cipher mode specification () */

text in the ()?

> +    char cipher_mode[QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN];
> +
> +    /* hash specication () */

s/specication/specification/, text in the ()?

> +    char hash_spec[QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN];
> +
> +    /* start offset of the volume data (in sectors) */
> +    uint32_t payload_offset;

Should we say "in 512-byte sectors"?  I could not see anything in the
LUKS specification that said whether SECTOR_SIZE can be larger than 512
- if you have a bare metal 4k sector disk, does a payload_offset of 2
mean 1024 or 8192 bytes into the disk?  Eww. Sounds like we should
submit a bug against the LUKS spec to have it clarified.

> +
> +    /* Number of key bytes */
> +    uint32_t key_bytes;
> +
> +    /* master key checksum after PBKDF2 */
> +    uint8_t master_key_digest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> +
> +    /* salt for master key PBKDF2 */
> +    uint8_t master_key_salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> +
> +    /* iterations for master key PBKDF2 */
> +    uint32_t master_key_iterations;
> +
> +    /* UUID of the partition */

Should we say "in standard ASCII representation"?

> +    uint8_t uuid[QCRYPTO_BLOCK_LUKS_UUID_LEN];
> +
> +    /* key slots */
> +    QCryptoBlockLUKSKeySlot key_slots[QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS];
> +} QEMU_PACKED;

Again, alignment should mean that we could get away without packing; but
the packing doesn't hurt; and a BUG_ON may be nice to ensure size.


> +}
> +
> +/* XXX doesn't QEMU already have a string -> enum val convertor ? */
> +static int qcrypto_block_luks_name_lookup(const char *name,

Yes, spelled qapi_enum_parse().  Works if the enum was defined in a
.json file.


> +
> +    error_setg(errp, "%s %s not supported", type, name);
> +    return 0;
> +}
> +
> +#define qcrypto_block_luks_cipher_mode_lookup(name, errp)               \
> +    qcrypto_block_luks_name_lookup(name,                                \
> +                                   QCryptoCipherMode_lookup,            \
> +                                   QCRYPTO_CIPHER_MODE__MAX,            \
> +                                   "Cipher mode",                       \
> +                                   errp)
> +
> +#define qcrypto_block_luks_hash_name_lookup(name, errp)                 \
> +    qcrypto_block_luks_name_lookup(name,                                \
> +                                   QCryptoHashAlgorithm_lookup,         \
> +                                   QCRYPTO_HASH_ALG__MAX,               \
> +                                   "Hash algorithm",                    \
> +                                   errp)

That said, your macro wrappers tweak the error message, so that it is
perhaps more legible than the standard failure mode of
qapi_enum_parse().  So up to you if you want to keep the wrappers.

> +/*
> + * Given a key slot, and user password, this will attempt to unlock
> + * the master encryption key from the key slot
> + */
> +static int
> +qcrypto_block_luks_load_key(QCryptoBlock *block,
> +                            QCryptoBlockLUKSKeySlot *slot,
> +                            const char *password,
> +                            QCryptoCipherAlgorithm cipheralg,
> +                            QCryptoCipherMode ciphermode,
> +                            QCryptoHashAlgorithm hash,
> +                            QCryptoIVGenAlgorithm ivalg,
> +                            QCryptoHashAlgorithm ivhash,
> +                            uint8_t *masterkey,
> +                            size_t masterkeylen,
> +                            QCryptoBlockReadFunc readfunc,
> +                            void *opaque,
> +                            Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    uint8_t *splitkey;
> +    size_t splitkeylen;
> +    uint8_t *possiblekey;
> +    int ret = -1;
> +    ssize_t rv;
> +    QCryptoCipher *cipher = NULL;
> +    uint8_t keydigest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> +    QCryptoIVGen *ivgen = NULL;
> +    size_t niv;
> +
> +    if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) {
> +        return 0;
> +    }

Would be worth documenting the convention of '0' (no key found, but no
errp set), '1' (successful key), and negative (errp set).

> +
> +    splitkeylen = masterkeylen * slot->stripes;

Should we do any validation that masterkeylen was in bounds and this
doesn't overflow?  I don't want a malicious byte stream masquerading as
a LUKS header to cause us to misbehave.

For that matter, the spec states that luks->payload_offset is a
write-once calculation at creation time, and that it could be recomputed
from key layout.  Should we validate that the offset listed makes
algorithmic sense with what we know about header and key data sizes for
the parameters listed in the header?

Side note: I'm a bit surprised the upstream LUKS spec does not include
any checksum over the header contents - it does a great job at ensuring
the master key is only going to be found by someone knowing a correct
password and that a single byte error in a key material section
invalidates that entire slot's usefulness, but it is not as careful on
what happens with certain single-byte errors in the header.

> +    splitkey = g_new0(uint8_t, splitkeylen);
> +    possiblekey = g_new0(uint8_t, masterkeylen);
> +
> +    /*
> +     * The user password is used to generate a (possible)
> +     * decryption key. This may or may not successfully
> +     * decrypt the master key - we just blindly assume
> +     * the key is correct and validate the results of
> +     * decryption later.
> +     */
> +    if (qcrypto_pbkdf2(hash,
> +                       (const uint8_t *)password, strlen(password),
> +                       slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN,
> +                       slot->iterations,
> +                       possiblekey, masterkeylen,
> +                       errp) < 0) {
> +        goto cleanup;
> +    }
> +
> +    /*
> +     * We need to read the master key material from the
> +     * LUKS key material header. What we're reading is
> +     * not the raw master key, but rather the data after
> +     * it has been passed through AFSplit and the result
> +     * then encrypted.
> +     */
> +    rv = readfunc(block,
> +                  slot->key_offset * 512ll,

I tend to write LL rather than ll, to make it obvious I didn't typo '1'.
 Looks like we're hard-coding our sector size (not the first time); but
maybe a constant instead of a magic number will make it easier for the
poor guy down the road that tries to switch to 4k sectors.


...
> +
> +    /*
> +     * Now we're decrypted the split master key, join

s/we're/that we've/

> +     * it back together to get the actual master key.
> +     */
> +    if (qcrypto_afsplit_decode(hash,

...
> +
> +    if (memcmp(keydigest, luks->header.master_key_digest,
> +               QCRYPTO_BLOCK_LUKS_DIGEST_LEN) == 0) {
> +        /* Success, we got the right master key */
> +        ret = 1;
> +        goto cleanup;
> +    }
> +
> +    /* Fail, user's password was not valid for this key slot,
> +     * tell caller to try another slot */
> +    ret = 0;
> +

Matches the spec.  Yay!

Side observation: The LUKS spec doesn't say anything about parallelizing
the lookup loop across all 8 key slots, nor any randomization on which
slot should be attempted first.  The whole point of PBKDF2 iteration
count is to burn CPU time so that an attacker can't brute force things
easily.  That means it is VERY easy to tell by timing how many active
slots were attempted before a key was found, if slots are tried serially
and we immediately break the loop on the first successful decryption.
Is there any information leak caused by the timing observations when
serially searching among 8 active slots, when the master key is only
found in slot 7 vs. slot 0?  But I don't see it as your problem to
solve, so much as something to ask the upstream LUKS design team.

> +
> +
> +static int
> +qcrypto_block_luks_open(QCryptoBlock *block,
> +                        QCryptoBlockOpenOptions *options,
> +                        QCryptoBlockReadFunc readfunc,
> +                        void *opaque,
> +                        unsigned int flags,
> +                        Error **errp)
> +{

> +    /*
> +     * The cipher_mode header contains a string that we have
> +     * to further parse of the format

s/parse/parse,/

> +     *
> +     *    <cipher-mode>-<iv-generator>[:<iv-hash>]
> +     *
> +     * eg  cbc-essiv:sha256, cbc-plain64
> +     */
> +    ivgen_name = strchr(luks->header.cipher_mode, '-');
> +    if (!ivgen_name) {
> +        ret = -EINVAL;
> +        error_setg(errp, "Unexpected cipher mode string format %s",
> +                   luks->header.cipher_mode);
> +        goto fail;
> +    }
> +    *ivgen_name = '\0';
> +    ivgen_name++;

We're modifying luks->header in place; we'd better not write it back out
to disk in the modified form.  I guess you are okay for now - your code
only reads existing LUKS disks, and can only create a single key in slot
0 on conversion (that is, I don't see code for key management of an
existing image).  Probably things we should add later, though, at which
point we'll need to be careful that we don't overwrite too much in the
header.

> +
> +    block->payload_offset = luks->header.payload_offset;

Earlier, I argued that block->payload_offset should be in bytes.  You
are constrained that luks->header.payload_offset is in sectors, but we
need not propagate that craziness any higher.

> +
> +static int
> +qcrypto_block_luks_create(QCryptoBlock *block,
> +                          QCryptoBlockCreateOptions *options,
> +                          QCryptoBlockInitFunc initfunc,
> +                          QCryptoBlockWriteFunc writefunc,
> +                          void *opaque,
> +                          Error **errp)
> +{

> +
> +    memcpy(&luks_opts, options->u.luks, sizeof(luks_opts));
> +    if (!luks_opts.has_cipher_alg) {
> +        luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> +    }
> +    if (!luks_opts.has_cipher_mode) {
> +        /* XXX switch to XTS */
> +        luks_opts.cipher_mode = QCRYPTO_CIPHER_MODE_CBC;
> +    }

Yeah, my (quick) reading made it seem like XTS is a slightly more secure
default than CBC.  But as you said, XTS implementation will come later.


> +    luks = g_new0(QCryptoBlockLUKS, 1);
> +    block->opaque = luks;
> +
> +    memcpy(luks->header.magic, qcrypto_block_luks_magic,
> +           QCRYPTO_BLOCK_LUKS_MAGIC_LEN);
> +
> +    luks->header.version = QCRYPTO_BLOCK_LUKS_VERSION;

Maybe a comment here that endianness will be addressed later, as a lot
of code appears between here and the actual write-to-disk.

> +    uuid_generate(uuid);
> +    uuid_unparse(uuid, (char *)luks->header.uuid);
> +
> +    switch (luks_opts.cipher_alg) {
> +    case QCRYPTO_CIPHER_ALG_AES_128:
> +    case QCRYPTO_CIPHER_ALG_AES_192:
> +    case QCRYPTO_CIPHER_ALG_AES_256:
> +        cipher_alg = "aes";
> +        break;
> +    default:
> +        error_setg(errp, "Cipher algorithm is not supported");
> +        goto error;

That is, we aren't supporting "twofish", "serpent", "cast5", or "cast6".
 Fine for now.

> +    }
> +
> +    cipher_mode = QCryptoCipherMode_lookup[luks_opts.cipher_mode];
> +    ivgen_alg = QCryptoIVGenAlgorithm_lookup[luks_opts.ivgen_alg];
> +    if (luks_opts.has_ivgen_hash_alg) {
> +        ivgen_hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.ivgen_hash_alg];
> +        cipher_mode_spec = g_strdup_printf("%s-%s:%s", cipher_mode, ivgen_alg,
> +                                           ivgen_hash_alg);
> +    } else {
> +        cipher_mode_spec = g_strdup_printf("%s-%s", cipher_mode, ivgen_alg);
> +    }

Should we validate that the mode spec is among the set documented by the
LUKS spec ("ecb", "cbc-plain", "cbc-essiv:hash", "xts-plain64") (well,
we don't support xts yet), and reject combinations that aren't supported
("cbc-plain64" comes to mind as something the LUKS spec doesn't allow,
even though we have the pieces to logically make it happen)?

> +    hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.hash_alg];

Likewise, should we validate that the hash is one of the specified names
("sha1", "sha256", "sha512", "ripemd160")?


> +
> +    memcpy(luks->header.cipher_name, cipher_alg,
> +           strlen(cipher_alg) + 1);
> +    memcpy(luks->header.cipher_mode, cipher_mode_spec,
> +           strlen(cipher_mode_spec) + 1);
> +    memcpy(luks->header.hash_spec, hash_alg,
> +           strlen(hash_alg) + 1);

Couldn't these just be strcpy()?

> +
> +    /* Determine how many iterations we need to hash the master
> +     * key with in order to have 1 second of compute time used

s/with //

> +     */
> +    luks->header.master_key_iterations =
> +        qcrypto_pbkdf2_count_iters(luks_opts.hash_alg,
> +                                   masterkey, luks->header.key_bytes,
> +                                   luks->header.master_key_salt,
> +                                   QCRYPTO_BLOCK_LUKS_SALT_LEN,
> +                                   &local_err);
> +    if (local_err) {
> +        error_propagate(errp, local_err);
> +        goto error;
> +    }
> +
> +    /* Why /= 8 ?  That matches cryptsetup, but there's no
> +     * explanation why they chose /= 8... */

My guess?  There's 8 key slots, and if all 8 are active, you'll spend 1
second serially decrypting each of them.  Users got ticked waiting that
long, so the /=8 reduces it to 125ms per slot, and for the typical use
case of slot0 being the user's password, much faster response at opening
the device.  But that's a guess - you're right that it could be
documented better.

> +    luks->header.master_key_iterations /= 8;
> +    luks->header.master_key_iterations = MAX(
> +        luks->header.master_key_iterations,
> +        QCRYPTO_BLOCK_LUKS_MIN_MASTER_KEY_ITERS);
> +
> +
> +    /* Hash the master key, saving the result in the LUKS
> +     * header. This hash is used when opening the encrypted
> +     * device to verify that the user password unlocked a
> +     * valid master key
> +     */
> +    if (qcrypto_pbkdf2(luks_opts.hash_alg,
> +                       masterkey, luks->header.key_bytes,
> +                       luks->header.master_key_salt,
> +                       QCRYPTO_BLOCK_LUKS_SALT_LEN,
> +                       luks->header.master_key_iterations,
> +                       luks->header.master_key_digest,
> +                       QCRYPTO_BLOCK_LUKS_DIGEST_LEN,
> +                       errp) < 0) {
> +        goto error;
> +    }
> +
> +
> +    /* Although LUKS has multiple key slots, we're just going
> +     * to use the first key slot */
> +
> +    splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES;

The LUKS spec says that it is okay to allow the user to specify stripes.
 Should that be one of our options, with a sane default?  But it can be
added later as a followup, this patch is already quite big and I'm fine
with hardcoding stripes for now.

> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        luks->header.key_slots[i].active = i == 0 ?
> +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED :
> +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> +        luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES;
> +        luks->header.key_slots[i].key_offset =
> +            (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> +            (ROUND_UP(((splitkeylen + 511) / 512),
> +                      (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) * i);

Eww. The spec seems buggy, saying:

> // integer divisions, result rounded down:
> baseOffset = (size of phdr)/SECTOR SIZE + 1
> keymaterialSectors = (stripes*masterKeyLength)/SECTOR SIZE + 1

First, look at the calculation of baseOffset.  Since size of phdr is 592
bytes, and that is NOT a SECTOR SIZE in any disk I know of, that makes
sense that baseOffset will be at least 2 (512-byte) sectors into the
disk, or, if SECTOR SIZE is 4, that will result in an offset of 1
(4096-byte) sector.  However, you've defined
QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET as 4096, which means for our 512-byte
sector usage, you are setting the first key material at sector 4 no
matter what.  I guess that matches what cryptsetup does?  Maybe worth a
comment?

Now, look at the calculation of keymaterial Sectors.  The algorithm in
the spec mishandles the case where stripes happens to be an exact
multiple of sector size (that is, for a keysize of 20 bytes coupled with
512 stripes, it would reserve 21 sectors rather than the 20 actually
required).  I think your use of ROUND_UP() makes more sense, but it
would be nice to file a bug against the LUKS spec to have them fix their
math, and/or document that it is okay to use values larger than the
absolute minimum.

> +    }
> +
> +    if (qcrypto_random_bytes(luks->header.key_slots[0].salt,
> +                             QCRYPTO_BLOCK_LUKS_SALT_LEN,
> +                             errp) < 0) {
> +        goto error;
> +    }
> +
> +    /* Again we determine how many iterations are required to
> +     * hash the user password while consuming 1 second of compute
> +     * time */
> +    luks->header.key_slots[0].iterations =
> +        qcrypto_pbkdf2_count_iters(luks_opts.hash_alg,
> +                                   (uint8_t *)password, strlen(password),
> +                                   luks->header.key_slots[0].salt,
> +                                   QCRYPTO_BLOCK_LUKS_SALT_LEN,
> +                                   &local_err);
> +    if (local_err) {
> +        error_propagate(errp, local_err);
> +        goto error;
> +    }
> +    /* Why /= 2 ?  That matches cryptsetup, but there's no
> +     * explanation why they chose /= 2... */

Again, guessing that half a second feels nicer than 1 second for
responsiveness.

> +    luks->header.key_slots[0].iterations /= 2;
> +    luks->header.key_slots[0].iterations = MAX(
> +        luks->header.key_slots[0].iterations,
> +        QCRYPTO_BLOCK_LUKS_MIN_SLOT_KEY_ITERS);
> +

> +
> +    /* Before storing the master key, we need to vastly
> +     * increase its size, as protection against forensic
> +     * disk data recovery */
> +    splitkey = g_new0(uint8_t, splitkeylen);

If we later add the ability to do key management on an active disk, this
allocation may be large enough that it would warrant the _try variant
and reporting ENOMEM, rather than failing.


> +    }
> +
> +
> +
> +    /* The total size of the LUKS headers is the partition header + key

Three blank lines?

> +     * slot headers, rounded up to the nearest sector, combined with
> +     * the size of each master key material region, also rounded up
> +     * to the nearest sector */
> +    luks->header.payload_offset =
> +        (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> +        (ROUND_UP(((splitkeylen + 511) / 512),
> +                  (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) *
> +         QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +
> +    block->payload_offset = luks->header.payload_offset;

Again, I argue that block->payload_offset would be saner in bytes.

> +
> +    /* Reserve header space to match payload offset */
> +    initfunc(block, block->payload_offset * 512, &local_err, opaque);

Especially since initfunc() takes bytes.

> +    if (local_err) {
> +        error_propagate(errp, local_err);
> +        goto error;
> +    }
> +
> +    /* Everything on disk uses Big Endian, so flip header fields
> +     * before writing them */

> +
> +    /* Write out the master key material, starting at the
> +     * sector immediately following the partition header. */
> +    if (writefunc(block,
> +                  luks->header.key_slots[0].key_offset * 512,
> +                  splitkey, splitkeylen,
> +                  errp,
> +                  opaque) != splitkeylen) {
> +        goto error;
> +    }
> +
> +    memset(masterkey, 0, luks->header.key_bytes);
> +    g_free(masterkey);

Nice, trying to avoid leaking the key from a heap attack.

> +
> +static int
> +qcrypto_block_luks_decrypt(QCryptoBlock *block,
> +                           uint64_t startsector,
> +                           uint8_t *buf,
> +                           size_t len,
> +                           Error **errp)
> +{
> +    return qcrypto_block_decrypt_helper(block->cipher,
> +                                        block->niv, block->ivgen,
> +                                        startsector, buf, len, errp);
> +}

What happens when we try to read a LUKS device with 4k sectors?  Worth
worrying about, maybe just as a comment that we are hard-coded to 512
bytes at the moment?

> +++ b/qapi/crypto.json
> @@ -102,12 +102,13 @@
>  #
>  # @qcow: QCow/QCow2 built-in AES-CBC encryption. Use only
>  #        for liberating data from old images.
> +# @luks: LUKS encryption format. Recommended for new images
>  #
>  # Since: 2.6
>  ##
>  { 'enum': 'QCryptoBlockFormat',
>  #  'prefix': 'QCRYPTO_BLOCK_FORMAT',
> -  'data': ['qcow']}
> +  'data': ['qcow', 'luks']}
>  
>  ##
>  # QCryptoBlockOptionsBase:
> @@ -136,6 +137,40 @@
>    'data': { '*key-secret': 'str' }}
>  
>  ##
> +# QCryptoBlockOptionsLUKS:
> +#
> +# The options that apply to LUKS encryption format
> +#
> +# @key-secret: #optional the ID of a QCryptoSecret object providing the
> +#              decryption key

Maybe add "Mandatory except when probing the image for metadata only"

> +# Since: 2.6
> +##
> +{ 'struct': 'QCryptoBlockOptionsLUKS',
> +  'data': { '*key-secret': 'str' }}
> +
> +
> +##
> +# QCryptoBlockCreateOptionsLUKS:
> +#
> +# The options that apply to LUKS encryption format initialization
> +#
> +# @cipher-alg: #optional the cipher algorithm for data encryption
> +# @cipher-mode: #optional the cipher mode for data encryption
> +# @ivgen-alg: #optional the initialization vector generator
> +# @ivgen-hash-alg: #optional the initialization vector generator hash
> +# @hash-alg: #optional the master key hash algorithm

Would be nice to mention the defaults, particularly since we may later
change the default from cbc to xts.

> +# Since: 2.6
> +##
> +{ 'struct': 'QCryptoBlockCreateOptionsLUKS',
> +  'base': 'QCryptoBlockOptionsLUKS',
> +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm',
> +            '*cipher-mode': 'QCryptoCipherMode',
> +            '*ivgen-alg': 'QCryptoIVGenAlgorithm',
> +            '*ivgen-hash-alg': 'QCryptoHashAlgorithm',
> +            '*hash-alg': 'QCryptoHashAlgorithm'}}
> +

As mentioned earlier, we might also want to make stripes be customizable.

> +++ b/tests/test-crypto-block.c
> @@ -40,6 +40,69 @@ static QCryptoBlockOpenOptions qcow_open_opts = {
>      .u.qcow = &qcow_opts,
>  };
>  

> +    {
> +        .path = "/crypto/block/luks/aes-256-cbc-eesiv",
> +        .create_opts = &luks_create_opts_aes256_cbc_essiv,

s/eesiv/essiv/

Wow. A lot to read through, but looks mostly sane. v3 will probably be
ready to go; meanwhile, I spotted enough typos in the LUKS spec that I'm
half tempted to check out their source code and submit a patch.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework
  2016-02-05 12:43     ` Daniel P. Berrange
@ 2016-02-05 18:48       ` Eric Blake
  0 siblings, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-05 18:48 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

[-- Attachment #1: Type: text/plain, Size: 3091 bytes --]

On 02/05/2016 05:43 AM, Daniel P. Berrange wrote:
> On Thu, Feb 04, 2016 at 05:23:32PM -0700, Eric Blake wrote:
>> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
>>> Add a generic framework for support different block encryption
>>> formats. Upon instantiating a QCryptoBlock object, it will read
>>> the encryption header and extract the encryption keys. It is
>>> then possible to call methods to encrypt/decrypt data buffers.
>>>
>>> There is also a mode whereby it will create/initialize a new
>>> encryption header on a previously unformatted volume.
>>>
>>> The initial framework comes with support for the legacy QCow
>>> AES based encryption. This enables code in the QCow driver to
>>> be consolidated later.
>>>

> 
>>> +static gboolean
>>> +qcrypto_block_qcow_has_format(const uint8_t *buf G_GNUC_UNUSED,
>>> +                              size_t buf_size G_GNUC_UNUSED)
>>> +{
>>> +    return false;
>>> +}
>>
>> When I see gboolean, I think TRUE/FALSE.  Yes, C99 'false' happens to
>> promote to the correct value for whatever integer type gboolean is, but
>> this would read nicer if it returned 'bool'.
> 
> Yeah, dunno what I was thinking really. I use gboolean in a few places
> due to the gmainloop callback API contract, but this should not have
> needed it.

To be honest, when I see 'gboolean', I _really_ think "Eww. Why did glib
have to botch it?".  Gnulib's <stdbool.h> replacement that gets C99
semantics in all but a few corner cases with a C89 compiler is so much
nicer than glib's blatant wrong typing.


>>> +
>>> +    len = strlen(password);
>>> +    if (len > 16) {
>>> +        len = 16;
>>> +    }
>>> +    for (i = 0; i < len; i++) {
>>> +        keybuf[i] = password[i];
>>> +    }
>>
>> What - we really throw away anything longer than 16 bytes?
> 
> Yes, that's how awesome legacy qcow2 encryption is :-)

I should have mentioned that I saw nothing with this part of your patch,
and was just vocalizing my disgust at what code we are maintaining.  But
it looks like you got my meaning :)


>>> +
>>> +struct QCryptoBlockDriver {
>>> +    int (*open)(QCryptoBlock *block,
>>> +                QCryptoBlockOpenOptions *options,
>>> +                QCryptoBlockReadFunc readfunc,
>>> +                void *opaque,
>>> +                unsigned int flags,
>>> +                Error **errp);
>>
>> No documentation on any of these contracts?
> 
> They're essentially identical to the public API contracts, so I
> figured it was redundant to duplicate those docs.

Fair enough.  Although I probably could have been spared some confusion
if you had done:

git config diff.orderFile /path/to/foo

the populated foo such that include/* comes before any other file.  I
recently learned that trick; it's also available as git format-patch
-O/path/to/file (with no space after -O), and can make patches a lot
easier to read when you focus the reader on interface first.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed Daniel P. Berrange
@ 2016-02-05 19:08   ` Eric Blake
  0 siblings, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-05 19:08 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 1058 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> When opening an image it is useful to know whether the caller
> intends to perform I/O on the image or not. In the case of
> encrypted images this will allow the block driver to avoid
> having to prompt for decryption keys when we merely want to
> query header metadata about the image. eg qemu-img info
> 
> This flag is enforced at the top level only, since even if
> we don't want todo I/O on the 'qcow2' file payload, the
> underlying 'file' driver will still need todo I/O to read
> the qcow2 header, for example.

s/todo/to do/2

> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block.c               |  5 +++--
>  block/io.c            |  2 ++
>  include/block/block.h |  1 +
>  qemu-img.c            | 33 +++++++++++++++++----------------
>  4 files changed, 23 insertions(+), 18 deletions(-)
> 

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required Daniel P. Berrange
@ 2016-02-05 19:52   ` Eric Blake
  0 siblings, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-05 19:52 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 818 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> The qemu-img/qemu-io tools prompt for disk encryption passwords
> regardless of whether any are actually required. Adding a check
> on bdrv_key_required() avoids this prompt for disk formats which
> have been converted to the QCryptoSecret APIs.
> 
> This is just a temporary hack to ensure the block I/O tests
> continue to work after each patch, since the last patch will
> completely delete all the password prompting code.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  qemu-img.c | 3 ++-
>  qemu-io.c  | 2 +-
>  2 files changed, 3 insertions(+), 2 deletions(-)
> 

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver Daniel P. Berrange
  2016-01-21  9:12   ` Fam Zheng
@ 2016-02-05 22:20   ` Eric Blake
  2016-02-08 16:28     ` Daniel P. Berrange
  1 sibling, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-05 22:20 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 8596 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Add a block driver that is capable of supporting any full disk
> encryption format. This utilizes the previously added block
> encryption code, and at this time supports the LUKS format.
> 
> The driver code is capable of supporting any format supported
> by the QCryptoBlock module, so it registers one block driver
> for each format.
> 
> At this time, the "luks" driver is registered. New LUKS
> compatible volume can be formatted using qemu-img
> 
> $ qemu-img create --object secret,data=123456,id=sec0 \
>       -f luks -o key-secret=sec0,cipher-alg=aes-256,\
>                  cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \
>       demo.luks 10G

Are you still adding -o options like this, or is it better to use the
new --image-opts flag from your other series for this purpose?

> 
> And query its size
> 
> $ qemu-img info --object secret,data=123456,id=sec0  --source demo.luks,driver=luks,key-secret=sec0

And this definitely looks stale.

> image: json:{"key-secret": "sec0", "driver": "luks", "file": {"driver": "file", "filename": "demo.luks"}}
> file format: luks
> virtual size: 10.0G (10737416192 bytes)

That size is 2048 bytes less than 10G; what happened?  With default
striping, you have 4000 stripes * 32 byte aes-256 key * 8 key material +
header (where I pointed out that you are using a default offset of 4096,
even though 1024 would be sufficient on 512-byte sectors), or roughly
1M, occupied by the LUKS header (in fact, having the payload aligned to
1M is probably wise, even if it can pack in tighter, just because a
1M-aligned disk with a 1M header is a GOOD thing).  Therefore, you
should either be counting the _entire_ LUKS header as part of the image
(and report a full 10G), or you should _only_ be counting the payload
size (and report 10G-1M, not 10G-2k).

My vote: do the same as we do for qcow2 or any other format.  Make the
size requested by the user as the size visible to the guest, and a
fully-allocated image will take more space on the host than what the
guest is using (that is, a fully-allocated 10G LUKS disk would present
10G payload to the guest but occupy 10G+1M on the host).

> disk size: 132K

This, however, looks reasonable - with the defaults, key material 0 will
occupy just under 128k, and nothing is written in key material 1-7.

> 
> All volumes created by this new 'luks' driver should be
> capable of being opened by the kernel dm-crypt driver.
> With this initial impl, not all volumes created with
> dm-crypt can be opened by the QEMU 'luks' driver. This
> is due to lack of support for certain algorithms, in
> particular the 'xts' cipher mode. These limitations will
> be addressed in a later series of patches, with the
> intent that QEMU should be able to open anything that
> dm-crypt LUKS supports.

Cool!  Would it be worth augmenting the commit message to show a
sequence of commands where you loopback mount a LUKS image file created
by qemu, then wire that up with cryptsetup, as a proof that the two are
compatible implementations?

> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/Makefile.objs  |   2 +
>  block/crypto.c       | 541 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi/block-core.json |  18 +-
>  3 files changed, 560 insertions(+), 1 deletion(-)
>  create mode 100644 block/crypto.c
> 

> +++ b/block/crypto.c

> +
> +#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
> +#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
> +#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
> +#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
> +#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
> +#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"

I suggested in 7/17 that user-specified striping might be a parameter
worth adding; I guess this would be impacted as well.

> +
> +static int block_crypto_probe_generic(QCryptoBlockFormat format,
> +                                      const uint8_t *buf,
> +                                      int buf_size,
> +                                      const char *filename)
> +{
> +    if (qcrypto_block_has_format(format,
> +                                 buf, buf_size)) {
> +        return 100;

I don't know why we have particular values for any of the probes, but at
least this matches the arbitrary value reported by qcow2.

> +    } else {
> +        return 0;
> +    }
> +}
> +
> +
> +static ssize_t block_crypto_read_func(QCryptoBlock *block,
> +                                      size_t offset,
> +                                      uint8_t *buf,
> +                                      size_t buflen,
> +                                      Error **errp,
> +                                      void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    ssize_t ret;
> +
> +    ret = bdrv_pread(bs->file->bs, offset, buf, buflen);
> +    if (ret < 0) {
> +        error_setg_errno(errp, -ret, "Could not read encryption header");

Are we guaranteed that the offset being read here will always lie in the
encryption header portion of the file?

> +        return ret;
> +    }
> +    return ret;
> +}
> +
> +
> +static ssize_t block_crypto_write_func(QCryptoBlock *block,
> +                                       size_t offset,
> +                                       const uint8_t *buf,
> +                                       size_t buflen,
> +                                       Error **errp,
> +                                       void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    ssize_t ret;
> +
> +    ret = bdrv_pwrite(bs, offset, buf, buflen);
> +    if (ret < 0) {
> +        error_setg_errno(errp, -ret, "Could not write encryption header");

Likewise.

> +
> +#define BLOCK_CRYPTO_MAX_SECTORS 32

16k is not enough space to represent the LUKS header and the first key
material.  Does this number need to be larger to allow reading up to 1M
of the image to find all 8 key materials?

Or is this just being used to limit the maximum amount we'll
encrypt/decrypt in one pass of the loop?

> +
> +static coroutine_fn int
> +block_crypto_co_readv(BlockDriverState *bs, int64_t sector_num,
> +                      int remaining_sectors, QEMUIOVector *qiov)
> +{
> +    BlockCrypto *crypto = bs->opaque;
> +    int cur_nr_sectors; /* number of sectors in current iteration */
> +    uint64_t bytes_done = 0;
> +    uint8_t *cipher_data = NULL;
> +    QEMUIOVector hd_qiov;
> +    int ret = 0;
> +    size_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
> +
> +    qemu_iovec_init(&hd_qiov, qiov->niov);
> +
> +    qemu_co_mutex_lock(&crypto->lock);
> +
> +    while (remaining_sectors) {
> +        cur_nr_sectors = remaining_sectors;
> +
> +        if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
> +            cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
> +        }
> +        cipher_data =
> +            qemu_try_blockalign(bs->file->bs, cur_nr_sectors * 512);

Why are you allocating the block in the loop?  Can you hoist the
allocation outside, and reuse the memory across iterations?

> +
> +static int64_t block_crypto_getlength(BlockDriverState *bs)
> +{
> +    BlockCrypto *crypto = bs->opaque;
> +    int64_t len = bdrv_getlength(bs->file->bs);
> +
> +    ssize_t offset = qcrypto_block_get_payload_offset(crypto->block);
> +
> +    len -= (offset * 512);

Will need a tweak if you fix qcrypto_block_get_payload_offset to be in
bytes.

> +++ b/qapi/block-core.json
> @@ -1546,7 +1546,7 @@
>  { 'enum': 'BlockdevDriver',
>    'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
>              'dmg', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
> -            'http', 'https', 'null-aio', 'null-co', 'parallels',
> +            'http', 'https', 'luks', 'null-aio', 'null-co', 'parallels',

Missing documentation; BlockDeviceInfo has been where we've documented
which release introduced which drivers.

>  ##
> +# @BlockdevOptionsLUKS
> +#
> +# Driver specific block device options for LUKS.
> +#
> +# @key-secret: #optional the ID of a QCryptoSecret object providing
> +#              the decryption key (since 2.6)

Worth mentioning that it is mandatory except when doing a metadata-only
probe.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
  2016-01-21  9:13   ` Fam Zheng
@ 2016-02-05 23:22   ` Eric Blake
  1 sibling, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-05 23:22 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 695 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Instead of requiring separate input/output buffers for
> encrypting data, change qcow2_encrypt_sectors() to assume
> use of a single buffer, encrypting in place. The current
> callers all used the same buffer for input/output already.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow2-cluster.c | 17 +++++------------
>  block/qcow2.c         |  5 ++---
>  block/qcow2.h         |  3 +--
>  3 files changed, 8 insertions(+), 17 deletions(-)

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format
  2016-02-05 17:38   ` Eric Blake
@ 2016-02-08 16:03     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-08 16:03 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Fri, Feb 05, 2016 at 10:38:45AM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Provide a block encryption implementation that follows the
> > LUKS/dm-crypt specification.
> > 
> > This supports all combinations of hash, cipher algorithm,
> > cipher mode and iv generator that are implemented by the
> > current crypto layer.
> > 
> > The notable missing feature is support for the 'xts'
> > cipher mode, which is commonly used for disk encryption
> > instead of 'cbc'. This is because it is not provided by
> > either nettle or libgcrypt. A suitable implementation
> > will be identified & integrated later.
> > 
> > There is support for opening existing volumes formatted
> > by dm-crypt, and for formatting new volumes. In the latter
> > case it will only use key slot 0.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>


> > +
> > +/*
> > + * Reference for format is
> > + *
> > + * docs/on-disk-format.pdf
> > + *
> > + * In 'cryptsetup' package source code
> > + *
> 
> Worth calling out the document version number? I reviewed based on
> version 1.2.1, dated Oct 2011.

Yep


> > +};
> > +
> > +
> > +struct QCryptoBlockLUKSKeySlot {
> > +    /* state of keyslot, enabled/disable */
> > +    uint32_t active;
> 
> May be worth a comment that this struct is written to disk in
> big-endian, but used in native-endian for most operations for
> convenience; to make sure future developers remember to put byteswaps in
> the correct locations.  But it looks like you handled endianness correctly.

Yes, will note it.

> 
> > +    /* iterations for PBKDF2 */
> > +    uint32_t iterations;
> > +    /* salt for PBKDF2 */
> > +    uint8_t salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> > +    /* start sector of key material */
> > +    uint32_t key_offset;
> > +    /* number of anti-forensic stripes */
> > +    uint32_t stripes;
> > +} QEMU_PACKED;
> 
> Packed makes sense since we are writing it straight to disk; although it
> looks like natural alignment prevails and this did not need to be
> packed.  Do we want some sort of BUG_ON() that we have the right size
> struct?

Yes, I'll add QEMU_BUILD_BUG_ON

> > +
> > +struct QCryptoBlockLUKSHeader {
> > +    /* 'L', 'U', 'K', 'S', '0xBA', '0xBE' */
> > +    char magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN];
> > +
> > +    /* LUKS version, currently 1 */
> > +    uint16_t version;
> > +
> > +    /* cipher name specification (aes, etc */
> 
> Missing )
> 
> > +    char cipher_name[QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN];
> > +
> > +    /* cipher mode specification () */
> 
> text in the ()?
> 
> > +    char cipher_mode[QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN];
> > +
> > +    /* hash specication () */
> 
> s/specication/specification/, text in the ()?

Will fix all these examples

> 
> > +    char hash_spec[QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN];
> > +
> > +    /* start offset of the volume data (in sectors) */
> > +    uint32_t payload_offset;
> 
> Should we say "in 512-byte sectors"?  I could not see anything in the
> LUKS specification that said whether SECTOR_SIZE can be larger than 512
> - if you have a bare metal 4k sector disk, does a payload_offset of 2
> mean 1024 or 8192 bytes into the disk?  Eww. Sounds like we should
> submit a bug against the LUKS spec to have it clarified.

cryptsetup is hardcoded to use 512 byte sectors currently. IIUC
they'll need a LUKS spec update before doing 4k sectors natively.

> 
> > +
> > +    /* Number of key bytes */
> > +    uint32_t key_bytes;
> > +
> > +    /* master key checksum after PBKDF2 */
> > +    uint8_t master_key_digest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> > +
> > +    /* salt for master key PBKDF2 */
> > +    uint8_t master_key_salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> > +
> > +    /* iterations for master key PBKDF2 */
> > +    uint32_t master_key_iterations;
> > +
> > +    /* UUID of the partition */
> 
> Should we say "in standard ASCII representation"?
> 
> > +    uint8_t uuid[QCRYPTO_BLOCK_LUKS_UUID_LEN];
> > +
> > +    /* key slots */
> > +    QCryptoBlockLUKSKeySlot key_slots[QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS];
> > +} QEMU_PACKED;
> 
> Again, alignment should mean that we could get away without packing; but
> the packing doesn't hurt; and a BUG_ON may be nice to ensure size.

Yep, add QEMU_BUILD_BUG_ON

> > +/* XXX doesn't QEMU already have a string -> enum val convertor ? */
> > +static int qcrypto_block_luks_name_lookup(const char *name,
> 
> Yes, spelled qapi_enum_parse().  Works if the enum was defined in a
> .json file.
> 
> 
> > +
> > +    error_setg(errp, "%s %s not supported", type, name);
> > +    return 0;
> > +}
> > +
> > +#define qcrypto_block_luks_cipher_mode_lookup(name, errp)               \
> > +    qcrypto_block_luks_name_lookup(name,                                \
> > +                                   QCryptoCipherMode_lookup,            \
> > +                                   QCRYPTO_CIPHER_MODE__MAX,            \
> > +                                   "Cipher mode",                       \
> > +                                   errp)
> > +
> > +#define qcrypto_block_luks_hash_name_lookup(name, errp)                 \
> > +    qcrypto_block_luks_name_lookup(name,                                \
> > +                                   QCryptoHashAlgorithm_lookup,         \
> > +                                   QCRYPTO_HASH_ALG__MAX,               \
> > +                                   "Hash algorithm",                    \
> > +                                   errp)
> 
> That said, your macro wrappers tweak the error message, so that it is
> perhaps more legible than the standard failure mode of
> qapi_enum_parse().  So up to you if you want to keep the wrappers.

I'll change my XXX note to mention qapi_enum_parse() as a future
todo if we can improve its error reporting in some manner.


> > +static int
> > +qcrypto_block_luks_load_key(QCryptoBlock *block,
> > +                            QCryptoBlockLUKSKeySlot *slot,
> > +                            const char *password,
> > +                            QCryptoCipherAlgorithm cipheralg,
> > +                            QCryptoCipherMode ciphermode,
> > +                            QCryptoHashAlgorithm hash,
> > +                            QCryptoIVGenAlgorithm ivalg,
> > +                            QCryptoHashAlgorithm ivhash,
> > +                            uint8_t *masterkey,
> > +                            size_t masterkeylen,
> > +                            QCryptoBlockReadFunc readfunc,
> > +                            void *opaque,
> > +                            Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    uint8_t *splitkey;
> > +    size_t splitkeylen;
> > +    uint8_t *possiblekey;
> > +    int ret = -1;
> > +    ssize_t rv;
> > +    QCryptoCipher *cipher = NULL;
> > +    uint8_t keydigest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> > +    QCryptoIVGen *ivgen = NULL;
> > +    size_t niv;
> > +
> > +    if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) {
> > +        return 0;
> > +    }
> 
> Would be worth documenting the convention of '0' (no key found, but no
> errp set), '1' (successful key), and negative (errp set).

Yes, will document that.

> > +    splitkeylen = masterkeylen * slot->stripes;
> 
> Should we do any validation that masterkeylen was in bounds and this
> doesn't overflow?  I don't want a malicious byte stream masquerading as
> a LUKS header to cause us to misbehave.

masterkeylen comes from luks->header.key_bytes. We should put some
validation in for that earlier when we first get it off disk.

> For that matter, the spec states that luks->payload_offset is a
> write-once calculation at creation time, and that it could be recomputed
> from key layout.  Should we validate that the offset listed makes
> algorithmic sense with what we know about header and key data sizes for
> the parameters listed in the header?

While it says that the offsets could be recomputed, from a technical POV
there's no reason why all impls must use the same layout / alignment for
the key slots. So I feel it safer to just rely on the header values and
not try to enforce that they match a particular offset.

> Side note: I'm a bit surprised the upstream LUKS spec does not include
> any checksum over the header contents - it does a great job at ensuring
> the master key is only going to be found by someone knowing a correct
> password and that a single byte error in a key material section
> invalidates that entire slot's usefulness, but it is not as careful on
> what happens with certain single-byte errors in the header.
> 
> > +    splitkey = g_new0(uint8_t, splitkeylen);
> > +    possiblekey = g_new0(uint8_t, masterkeylen);
> > +
> > +    /*
> > +     * The user password is used to generate a (possible)
> > +     * decryption key. This may or may not successfully
> > +     * decrypt the master key - we just blindly assume
> > +     * the key is correct and validate the results of
> > +     * decryption later.
> > +     */
> > +    if (qcrypto_pbkdf2(hash,
> > +                       (const uint8_t *)password, strlen(password),
> > +                       slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN,
> > +                       slot->iterations,
> > +                       possiblekey, masterkeylen,
> > +                       errp) < 0) {
> > +        goto cleanup;
> > +    }
> > +
> > +    /*
> > +     * We need to read the master key material from the
> > +     * LUKS key material header. What we're reading is
> > +     * not the raw master key, but rather the data after
> > +     * it has been passed through AFSplit and the result
> > +     * then encrypted.
> > +     */
> > +    rv = readfunc(block,
> > +                  slot->key_offset * 512ll,
> 
> I tend to write LL rather than ll, to make it obvious I didn't typo '1'.
>  Looks like we're hard-coding our sector size (not the first time); but
> maybe a constant instead of a magic number will make it easier for the
> poor guy down the road that tries to switch to 4k sectors.

I'll introduce a QCRYPTO_LUKS_SECTOR_SIZE constant throughout for
now.


> > +     * it back together to get the actual master key.
> > +     */
> > +    if (qcrypto_afsplit_decode(hash,
> 
> ...
> > +
> > +    if (memcmp(keydigest, luks->header.master_key_digest,
> > +               QCRYPTO_BLOCK_LUKS_DIGEST_LEN) == 0) {
> > +        /* Success, we got the right master key */
> > +        ret = 1;
> > +        goto cleanup;
> > +    }
> > +
> > +    /* Fail, user's password was not valid for this key slot,
> > +     * tell caller to try another slot */
> > +    ret = 0;
> > +
> 
> Matches the spec.  Yay!
> 
> Side observation: The LUKS spec doesn't say anything about parallelizing
> the lookup loop across all 8 key slots, nor any randomization on which
> slot should be attempted first.  The whole point of PBKDF2 iteration
> count is to burn CPU time so that an attacker can't brute force things
> easily.  That means it is VERY easy to tell by timing how many active
> slots were attempted before a key was found, if slots are tried serially
> and we immediately break the loop on the first successful decryption.
> Is there any information leak caused by the timing observations when
> serially searching among 8 active slots, when the master key is only
> found in slot 7 vs. slot 0?  But I don't see it as your problem to
> solve, so much as something to ask the upstream LUKS design team.

FWIW, cryptsetup will short circuit the search as soon as it finds
a valid slot, so we're matching their behaviour at least.

> > +static int
> > +qcrypto_block_luks_open(QCryptoBlock *block,
> > +                        QCryptoBlockOpenOptions *options,
> > +                        QCryptoBlockReadFunc readfunc,
> > +                        void *opaque,
> > +                        unsigned int flags,
> > +                        Error **errp)
> > +{
> 
> > +    /*
> > +     * The cipher_mode header contains a string that we have
> > +     * to further parse of the format
> 
> s/parse/parse,/
> 
> > +     *
> > +     *    <cipher-mode>-<iv-generator>[:<iv-hash>]
> > +     *
> > +     * eg  cbc-essiv:sha256, cbc-plain64
> > +     */
> > +    ivgen_name = strchr(luks->header.cipher_mode, '-');
> > +    if (!ivgen_name) {
> > +        ret = -EINVAL;
> > +        error_setg(errp, "Unexpected cipher mode string format %s",
> > +                   luks->header.cipher_mode);
> > +        goto fail;
> > +    }
> > +    *ivgen_name = '\0';
> > +    ivgen_name++;
> 
> We're modifying luks->header in place; we'd better not write it back out
> to disk in the modified form.  I guess you are okay for now - your code
> only reads existing LUKS disks, and can only create a single key in slot
> 0 on conversion (that is, I don't see code for key management of an
> existing image).  Probably things we should add later, though, at which
> point we'll need to be careful that we don't overwrite too much in the
> header.

Yeah, certainly something to be careful of later.

> > +    block->payload_offset = luks->header.payload_offset;
> 
> Earlier, I argued that block->payload_offset should be in bytes.  You
> are constrained that luks->header.payload_offset is in sectors, but we
> need not propagate that craziness any higher.

Yes, I've made that change.

> > +static int
> > +qcrypto_block_luks_create(QCryptoBlock *block,
> > +                          QCryptoBlockCreateOptions *options,
> > +                          QCryptoBlockInitFunc initfunc,
> > +                          QCryptoBlockWriteFunc writefunc,
> > +                          void *opaque,
> > +                          Error **errp)
> > +{
> 
> > +
> > +    memcpy(&luks_opts, options->u.luks, sizeof(luks_opts));
> > +    if (!luks_opts.has_cipher_alg) {
> > +        luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> > +    }
> > +    if (!luks_opts.has_cipher_mode) {
> > +        /* XXX switch to XTS */
> > +        luks_opts.cipher_mode = QCRYPTO_CIPHER_MODE_CBC;
> > +    }
> 
> Yeah, my (quick) reading made it seem like XTS is a slightly more secure
> default than CBC.  But as you said, XTS implementation will come later.

Yes, I still hope to post an XTS impl before 2.6, so if this code
merges before 2.6, hopefully we'll stil have time to use XTS as
the default too, matching cryptsetup

> > +    luks = g_new0(QCryptoBlockLUKS, 1);
> > +    block->opaque = luks;
> > +
> > +    memcpy(luks->header.magic, qcrypto_block_luks_magic,
> > +           QCRYPTO_BLOCK_LUKS_MAGIC_LEN);
> > +
> > +    luks->header.version = QCRYPTO_BLOCK_LUKS_VERSION;
> 
> Maybe a comment here that endianness will be addressed later, as a lot
> of code appears between here and the actual write-to-disk.

Will do

> > +    uuid_generate(uuid);
> > +    uuid_unparse(uuid, (char *)luks->header.uuid);
> > +
> > +    switch (luks_opts.cipher_alg) {
> > +    case QCRYPTO_CIPHER_ALG_AES_128:
> > +    case QCRYPTO_CIPHER_ALG_AES_192:
> > +    case QCRYPTO_CIPHER_ALG_AES_256:
> > +        cipher_alg = "aes";
> > +        break;
> > +    default:
> > +        error_setg(errp, "Cipher algorithm is not supported");
> > +        goto error;
> 
> That is, we aren't supporting "twofish", "serpent", "cast5", or "cast6".
>  Fine for now.
> 
> > +    }
> > +
> > +    cipher_mode = QCryptoCipherMode_lookup[luks_opts.cipher_mode];
> > +    ivgen_alg = QCryptoIVGenAlgorithm_lookup[luks_opts.ivgen_alg];
> > +    if (luks_opts.has_ivgen_hash_alg) {
> > +        ivgen_hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.ivgen_hash_alg];
> > +        cipher_mode_spec = g_strdup_printf("%s-%s:%s", cipher_mode, ivgen_alg,
> > +                                           ivgen_hash_alg);
> > +    } else {
> > +        cipher_mode_spec = g_strdup_printf("%s-%s", cipher_mode, ivgen_alg);
> > +    }
> 
> Should we validate that the mode spec is among the set documented by the
> LUKS spec ("ecb", "cbc-plain", "cbc-essiv:hash", "xts-plain64") (well,
> we don't support xts yet), and reject combinations that aren't supported
> ("cbc-plain64" comes to mind as something the LUKS spec doesn't allow,
> even though we have the pieces to logically make it happen)?

The spec appears to be non-exhaustive, in that cryptsetup / linux allow
for cbc-plain64 and other combinations that aren't listed. So QEMU should
follow that for interoperability.

> > +    hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.hash_alg];
> 
> Likewise, should we validate that the hash is one of the specified names
> ("sha1", "sha256", "sha512", "ripemd160")?

The only one we're permitting that's not listed there is 'md5' and
that should fail anyway since it provides too small a digest size
but I see we're not validating the digest size.

> > +
> > +    memcpy(luks->header.cipher_name, cipher_alg,
> > +           strlen(cipher_alg) + 1);
> > +    memcpy(luks->header.cipher_mode, cipher_mode_spec,
> > +           strlen(cipher_mode_spec) + 1);
> > +    memcpy(luks->header.hash_spec, hash_alg,
> > +           strlen(hash_alg) + 1);
> 
> Couldn't these just be strcpy()?

Yes


> > +    /* Although LUKS has multiple key slots, we're just going
> > +     * to use the first key slot */
> > +
> > +    splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES;
> 
> The LUKS spec says that it is okay to allow the user to specify stripes.
>  Should that be one of our options, with a sane default?  But it can be
> added later as a followup, this patch is already quite big and I'm fine
> with hardcoding stripes for now.

Cryptsetup doesn't allow the user to specify the stripes value, just
fixing it at 4k, so I figure we're best todo the same, otherwise we'll
create images that cant be read by linux.

> 
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        luks->header.key_slots[i].active = i == 0 ?
> > +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED :
> > +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> > +        luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES;
> > +        luks->header.key_slots[i].key_offset =
> > +            (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> > +            (ROUND_UP(((splitkeylen + 511) / 512),
> > +                      (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) * i);
> 
> Eww. The spec seems buggy, saying:
> 
> > // integer divisions, result rounded down:
> > baseOffset = (size of phdr)/SECTOR SIZE + 1
> > keymaterialSectors = (stripes*masterKeyLength)/SECTOR SIZE + 1
> 
> First, look at the calculation of baseOffset.  Since size of phdr is 592
> bytes, and that is NOT a SECTOR SIZE in any disk I know of, that makes
> sense that baseOffset will be at least 2 (512-byte) sectors into the
> disk, or, if SECTOR SIZE is 4, that will result in an offset of 1
> (4096-byte) sector.  However, you've defined
> QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET as 4096, which means for our 512-byte
> sector usage, you are setting the first key material at sector 4 no
> matter what.  I guess that matches what cryptsetup does?  Maybe worth a
> comment?
> 
> Now, look at the calculation of keymaterial Sectors.  The algorithm in
> the spec mishandles the case where stripes happens to be an exact
> multiple of sector size (that is, for a keysize of 20 bytes coupled with
> 512 stripes, it would reserve 21 sectors rather than the 20 actually
> required).  I think your use of ROUND_UP() makes more sense, but it
> would be nice to file a bug against the LUKS spec to have them fix their
> math, and/or document that it is okay to use values larger than the
> absolute minimum.

Yeah, what I am doing matches the cryptsetup code. I had originally
written it to be more or less what the spec suggests, and noticed
that my images were different from those created by cryptsetup. So
I switched to use the same algorithm. I figure it is probably an
optimization for on disk alignment to stop crossing sector boundaries.


> > +     * slot headers, rounded up to the nearest sector, combined with
> > +     * the size of each master key material region, also rounded up
> > +     * to the nearest sector */
> > +    luks->header.payload_offset =
> > +        (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> > +        (ROUND_UP(((splitkeylen + 511) / 512),
> > +                  (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) *
> > +         QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +
> > +    block->payload_offset = luks->header.payload_offset;
> 
> Again, I argue that block->payload_offset would be saner in bytes.

Agreed

> > +    /* Reserve header space to match payload offset */
> > +    initfunc(block, block->payload_offset * 512, &local_err, opaque);
> 
> Especially since initfunc() takes bytes.
> 


> > +
> > +static int
> > +qcrypto_block_luks_decrypt(QCryptoBlock *block,
> > +                           uint64_t startsector,
> > +                           uint8_t *buf,
> > +                           size_t len,
> > +                           Error **errp)
> > +{
> > +    return qcrypto_block_decrypt_helper(block->cipher,
> > +                                        block->niv, block->ivgen,
> > +                                        startsector, buf, len, errp);
> > +}
> 
> What happens when we try to read a LUKS device with 4k sectors?  Worth
> worrying about, maybe just as a comment that we are hard-coded to 512
> bytes at the moment?

qcrypto_block_decrypt_helper() method processes 'buf' in units of 512
so even if the underling dev is 4k, we'll also do 512 byte I/O

> >  ##
> > +# QCryptoBlockOptionsLUKS:
> > +#
> > +# The options that apply to LUKS encryption format
> > +#
> > +# @key-secret: #optional the ID of a QCryptoSecret object providing the
> > +#              decryption key
> 
> Maybe add "Mandatory except when probing the image for metadata only"

Yes


> > +# Since: 2.6
> > +##
> > +{ 'struct': 'QCryptoBlockOptionsLUKS',
> > +  'data': { '*key-secret': 'str' }}
> > +
> > +
> > +##
> > +# QCryptoBlockCreateOptionsLUKS:
> > +#
> > +# The options that apply to LUKS encryption format initialization
> > +#
> > +# @cipher-alg: #optional the cipher algorithm for data encryption
> > +# @cipher-mode: #optional the cipher mode for data encryption
> > +# @ivgen-alg: #optional the initialization vector generator
> > +# @ivgen-hash-alg: #optional the initialization vector generator hash
> > +# @hash-alg: #optional the master key hash algorithm
> 
> Would be nice to mention the defaults, particularly since we may later
> change the default from cbc to xts.

Yes, will do


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-02-05 22:20   ` Eric Blake
@ 2016-02-08 16:28     ` Daniel P. Berrange
  2016-02-08 20:23       ` Eric Blake
  0 siblings, 1 reply; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-08 16:28 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Fri, Feb 05, 2016 at 03:20:43PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Add a block driver that is capable of supporting any full disk
> > encryption format. This utilizes the previously added block
> > encryption code, and at this time supports the LUKS format.
> > 
> > The driver code is capable of supporting any format supported
> > by the QCryptoBlock module, so it registers one block driver
> > for each format.
> > 
> > At this time, the "luks" driver is registered. New LUKS
> > compatible volume can be formatted using qemu-img
> > 
> > $ qemu-img create --object secret,data=123456,id=sec0 \
> >       -f luks -o key-secret=sec0,cipher-alg=aes-256,\
> >                  cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \
> >       demo.luks 10G
> 
> Are you still adding -o options like this, or is it better to use the
> new --image-opts flag from your other series for this purpose?

These are options for image creation, so they must still use -o.
My patches only deal with options for pre-existing images.

> > And query its size
> > 
> > $ qemu-img info --object secret,data=123456,id=sec0  --source demo.luks,driver=luks,key-secret=sec0
> 
> And this definitely looks stale.

Yes, its wrong.

> 
> > image: json:{"key-secret": "sec0", "driver": "luks", "file": {"driver": "file", "filename": "demo.luks"}}
> > file format: luks
> > virtual size: 10.0G (10737416192 bytes)
> 
> That size is 2048 bytes less than 10G; what happened?  With default
> striping, you have 4000 stripes * 32 byte aes-256 key * 8 key material +
> header (where I pointed out that you are using a default offset of 4096,
> even though 1024 would be sufficient on 512-byte sectors), or roughly
> 1M, occupied by the LUKS header (in fact, having the payload aligned to
> 1M is probably wise, even if it can pack in tighter, just because a
> 1M-aligned disk with a 1M header is a GOOD thing).  Therefore, you
> should either be counting the _entire_ LUKS header as part of the image
> (and report a full 10G), or you should _only_ be counting the payload
> size (and report 10G-1M, not 10G-2k).

It is supposed to be reporting  10G - luks header size. Not sure
why this example shown 2k less - probably a bug from a much earlier
version of the patchset.  I think it probably dates from when I had
mistakenly not multiplied payload offset by sector size or some such.

> My vote: do the same as we do for qcow2 or any other format.  Make the
> size requested by the user as the size visible to the guest, and a
> fully-allocated image will take more space on the host than what the
> guest is using (that is, a fully-allocated 10G LUKS disk would present
> 10G payload to the guest but occupy 10G+1M on the host).

Yes, I had a todo item in the code asking which was better

    /* XXX Should we treat size as being total physical size
     * of the image (ie payload + encryption header), or just
     * the logical size of the image (ie payload). If the latter
     * then we need to extend 'size' to include the header
     * size */
    qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);

And Fam suggested the same as you - show full 10 G to the guest.

The complication is that we need to create the backing file
before we format the luks header, and we don't know the size
of the luks header at that point. So either I could format
the luks header into a temporary in-memory buffer, or I have
to create the file and then resize it afterwards, or I have
to provide some way to calculate the eventual header size
prior to creating it. I didn't much like any of those options :-)

> > disk size: 132K
> 
> This, however, looks reasonable - with the defaults, key material 0 will
> occupy just under 128k, and nothing is written in key material 1-7.
> 
> > 
> > All volumes created by this new 'luks' driver should be
> > capable of being opened by the kernel dm-crypt driver.
> > With this initial impl, not all volumes created with
> > dm-crypt can be opened by the QEMU 'luks' driver. This
> > is due to lack of support for certain algorithms, in
> > particular the 'xts' cipher mode. These limitations will
> > be addressed in a later series of patches, with the
> > intent that QEMU should be able to open anything that
> > dm-crypt LUKS supports.
> 
> Cool!  Would it be worth augmenting the commit message to show a
> sequence of commands where you loopback mount a LUKS image file created
> by qemu, then wire that up with cryptsetup, as a proof that the two are
> compatible implementations?

My intent is to add something to tests/qemu-iotests that will check
interoperability between qemu + cryptsetup. Slight complication is
that those io tests all expect to run unprivileged. So it'll need
a manual step run privileged to create the cryptsetup disk images
for testing with.


> > +++ b/block/crypto.c
> 
> > +
> > +#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
> > +#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
> > +#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
> > +#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
> > +#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
> > +#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
> 
> I suggested in 7/17 that user-specified striping might be a parameter
> worth adding; I guess this would be impacted as well.

Same note as before - cryptsetup hardcodes is to 4k, so I figure
it is fine todo the same right now.

> > +static ssize_t block_crypto_read_func(QCryptoBlock *block,
> > +                                      size_t offset,
> > +                                      uint8_t *buf,
> > +                                      size_t buflen,
> > +                                      Error **errp,
> > +                                      void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    ssize_t ret;
> > +
> > +    ret = bdrv_pread(bs->file->bs, offset, buf, buflen);
> > +    if (ret < 0) {
> > +        error_setg_errno(errp, -ret, "Could not read encryption header");
> 
> Are we guaranteed that the offset being read here will always lie in the
> encryption header portion of the file?

I don't think we need to care about that here. We're just honouring
reads from whereever the crypto driver wants - we don't want the
block driver here to have specific knowledge of the underlying
crypto format.

> 
> > +        return ret;
> > +    }
> > +    return ret;
> > +}
> > +
> > +
> > +static ssize_t block_crypto_write_func(QCryptoBlock *block,
> > +                                       size_t offset,
> > +                                       const uint8_t *buf,
> > +                                       size_t buflen,
> > +                                       Error **errp,
> > +                                       void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    ssize_t ret;
> > +
> > +    ret = bdrv_pwrite(bs, offset, buf, buflen);
> > +    if (ret < 0) {
> > +        error_setg_errno(errp, -ret, "Could not write encryption header");
> 
> Likewise.
> 
> > +
> > +#define BLOCK_CRYPTO_MAX_SECTORS 32
> 
> 16k is not enough space to represent the LUKS header and the first key
> material.  Does this number need to be larger to allow reading up to 1M
> of the image to find all 8 key materials?
> 
> Or is this just being used to limit the maximum amount we'll
> encrypt/decrypt in one pass of the loop?

Yeah, this is just used for batching I/O to the underling
block device, so that we don't need to allocate an arbitrarily
large temporary buffer for (en|de)cryption.

> > +static coroutine_fn int
> > +block_crypto_co_readv(BlockDriverState *bs, int64_t sector_num,
> > +                      int remaining_sectors, QEMUIOVector *qiov)
> > +{
> > +    BlockCrypto *crypto = bs->opaque;
> > +    int cur_nr_sectors; /* number of sectors in current iteration */
> > +    uint64_t bytes_done = 0;
> > +    uint8_t *cipher_data = NULL;
> > +    QEMUIOVector hd_qiov;
> > +    int ret = 0;
> > +    size_t payload_offset = qcrypto_block_get_payload_offset(crypto->block);
> > +
> > +    qemu_iovec_init(&hd_qiov, qiov->niov);
> > +
> > +    qemu_co_mutex_lock(&crypto->lock);
> > +
> > +    while (remaining_sectors) {
> > +        cur_nr_sectors = remaining_sectors;
> > +
> > +        if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
> > +            cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
> > +        }
> > +        cipher_data =
> > +            qemu_try_blockalign(bs->file->bs, cur_nr_sectors * 512);
> 
> Why are you allocating the block in the loop?  Can you hoist the
> allocation outside, and reuse the memory across iterations?

No particular reason - this code pattern is just copied from
equivalent methods in qcow2.c

> 
> > +
> > +static int64_t block_crypto_getlength(BlockDriverState *bs)
> > +{
> > +    BlockCrypto *crypto = bs->opaque;
> > +    int64_t len = bdrv_getlength(bs->file->bs);
> > +
> > +    ssize_t offset = qcrypto_block_get_payload_offset(crypto->block);
> > +
> > +    len -= (offset * 512);
> 
> Will need a tweak if you fix qcrypto_block_get_payload_offset to be in
> bytes.

Yep

> > +++ b/qapi/block-core.json
> > @@ -1546,7 +1546,7 @@
> >  { 'enum': 'BlockdevDriver',
> >    'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
> >              'dmg', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device',
> > -            'http', 'https', 'null-aio', 'null-co', 'parallels',
> > +            'http', 'https', 'luks', 'null-aio', 'null-co', 'parallels',
> 
> Missing documentation; BlockDeviceInfo has been where we've documented
> which release introduced which drivers.

Ah ok

> 
> >  ##
> > +# @BlockdevOptionsLUKS
> > +#
> > +# Driver specific block device options for LUKS.
> > +#
> > +# @key-secret: #optional the ID of a QCryptoSecret object providing
> > +#              the decryption key (since 2.6)
> 
> Worth mentioning that it is mandatory except when doing a metadata-only
> probe.

Yes


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
  2016-01-21  9:54   ` Fam Zheng
@ 2016-02-08 18:12   ` Eric Blake
  2016-02-09 12:32     ` Daniel P. Berrange
  1 sibling, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-08 18:12 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 32559 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> This converts the qcow2 driver to make use of the QCryptoBlock
> APIs for encrypting image content. As well as continued support
> for the legacy QCow2 encryption format, the appealing benefit
> is that it enables support for the LUKS format inside qcow2.

I know Fam had some interesting ideas on changing qcow2 to be able to
auto-load a LUKS format driver rather than duplicating code, which may
completely change this patch, but I'll go ahead and review this patch as-is.

> 
> With the LUKS format it is neccessary to store the LUKS

s/neccessary/necessary/

> partition header and key material in the QCow2 file. This
> data can be many MB in size, so cannot go into the QCow2
> header region directly. Thus the spec is defines a FDE

s/is //

> (Full Disk Encryption) header extension that specifies
> the offset of a set of clusters to hold the FDE headers,
> as well as the length of that region. The LUKS header is
> thus stored in these extra allocated clusters before the
> main image payload.
> 
> With this change it is now required to use the QCryptoSecret
> object for providing passwords, instead of the current block
> password APIs / interactive prompting.
> 
>   $QEMU \
>     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
>     -drive file=/home/berrange/encrypted.qcow2,key-secret=sec0
> 
> The new LUKS format is set as the new default format when
> creating encrypted images. ie
> 
>   # qemu-img create --object secret,data=123456,id=sec0 \
>        -f qcow2 -o encryption,key-secret=sec0 \
>        test.qcow2 10G

Did we add '-o encryption' earlier in the series, or is this line a bit
stale in reference to your --image-opts series?

> 
> Results in creation of an image using the LUKS format.

since this is a continuation of the earlier thought, s/Results/results/
s/in/in the/

> 
> For compatibility the old qcow2 AES format can still be used
> via the 'encryption-format' parameter which accepts the
> values 'luks' or 'qcowaes'.

s/qcowaes/qcow/ to match your earlier changes

> 
>   # qemu-img create --object secret,data=123456,id=sec0 \
>        -f qcow2 -o encryption,key-secret=sec0,encryption-format=qcowaes \
>        test.qcow2 10G
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow2-cluster.c      |  46 +----
>  block/qcow2-refcount.c     |  10 +
>  block/qcow2.c              | 502 ++++++++++++++++++++++++++++++++++++++-------
>  block/qcow2.h              |  21 +-
>  crypto/block-luks.c        |   1 -
>  docs/specs/qcow2.txt       |  74 +++++++
>  qapi/block-core.json       |   6 +-

As mentioned earlier in the series, a well-chosen order file
(format-patch -Ofile, or git config diff.orderFile) that hoists these
two changes first might make the overall patch easier to review.

>  tests/qemu-iotests/049     |   2 +-
>  tests/qemu-iotests/049.out |   7 +-
>  tests/qemu-iotests/082.out | 189 +++++++++++++++++
>  tests/qemu-iotests/087     |  28 ++-
>  tests/qemu-iotests/087.out |  12 +-
>  tests/qemu-iotests/134     |  18 +-
>  tests/qemu-iotests/134.out |  21 +-
>  tests/qemu-iotests/common  |   1 +
>  15 files changed, 775 insertions(+), 163 deletions(-)
> 

Starting my review at [1], then I'll return here [2].

> diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> index f5bc4f2..0512256 100644
> --- a/block/qcow2-cluster.c

> +++ b/block/qcow2-refcount.c
> @@ -1847,6 +1847,16 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
>          return ret;
>      }
>  
> +    /* encryption */
> +    if (s->crypto_header.length) {
> +        ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
> +                            s->crypto_header.offset,
> +                            s->crypto_header.length);
> +        if (ret < 0) {
> +            return ret;
> +        }
> +    }
> +

Do we ever allow turning off encryption, where we'd need to decrement
the refcounts as the FDE header is removed?

>      return check_refblocks(bs, res, fix, rebuild, refcount_table, nb_clusters);
>  }
>  
> diff --git a/block/qcow2.c b/block/qcow2.c
> index 2fae692..5249ca2 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -34,6 +34,8 @@
>  #include "qapi-event.h"
>  #include "trace.h"
>  #include "qemu/option_int.h"
> +#include "qapi/opts-visitor.h"
> +#include "qapi-visit.h"
>  
>  /*
>    Differences with QCOW:
> @@ -60,6 +62,7 @@ typedef struct {
>  #define  QCOW2_EXT_MAGIC_END 0
>  #define  QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
>  #define  QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
> +#define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x4c554b53

Aha: ASCII "LUKS", even though we expect the header to be useful for
more than just LUKS encryption schemes.  Would ASCII "CRYP" be any
nicer?  Or a random number, or the next set of hex digits from pi, or...?

I guess we don't have any real rules or patterns describing how magic
numbers are supposed to be chosen, so it works for me.

>  
>  static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
>  {
> @@ -74,6 +77,75 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
>  }
>  
>  
> +static ssize_t qcow2_crypto_hdr_read_func(QCryptoBlock *block, size_t offset,
> +                                          uint8_t *buf, size_t buflen,
> +                                          Error **errp, void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    BDRVQcow2State *s = bs->opaque;
> +    ssize_t ret;
> +
> +    if ((offset + buflen) > s->crypto_header.length) {

The inner () are redundant, but I don't mind them.

> +        error_setg(errp, "Request for data outside of extension header");
> +        return -1;

Nice that you are trying to prevent reads beyond the block of data
reserved by the FDE header; but is s->crypto_header.length the
rounded-up cluster boundary, or just the length of the used portion of
the LUKS header?...

> +    }
> +
> +    ret = bdrv_pread(bs->file->bs,
> +                     s->crypto_header.offset + offset, buf, buflen);
> +    if (ret < 0) {
> +        error_setg_errno(errp, -ret, "Could not read encryption header");
> +        return -1;
> +    }
> +    return ret;
> +}
> +
> +
> +static ssize_t qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
> +                                          Error **errp, void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    BDRVQcow2State *s = bs->opaque;
> +    int64_t ret;
> +
> +    s->crypto_header.length = headerlen + (headerlen % s->cluster_size);

...Huh?  Are you trying to round up to a cluster boundary?  This doesn't
do what you want.  And even if you had correctly rounded up to cluster
boundary size, that means that qcow2_crypto_hdr_read_func() can now read
the tail beyond the size recorded in the crypto header - which could be
dangerous if it means that tail can overlap the same sector number used
for the first guest payload sector which uses a sector number of
payload_offset from the LUKS header for its IV.

> +
> +    ret = qcow2_alloc_clusters(bs, s->crypto_header.length);

Does qcow2_alloc_clusters(bs, headerlen) properly round up the
allocation to a cluster boundary?  If so, I think you want
s->crypto_header.length = headerlen, full stop.

> +    if (ret < 0) {
> +        s->crypto_header.length = 0;

And it may be easier to not modify s->crypto_header.length except on
successful allocation, rather than having to undo it on failure.

> +        error_setg_errno(errp, -ret,
> +                         "Cannot allocate cluster for LUKS header size %zu",
> +                         headerlen);
> +        return -1;
> +    }
> +
> +    s->crypto_header.offset = ret;
> +    return ret;
> +}
> +
> +
> +static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
> +                                           const uint8_t *buf, size_t buflen,
> +                                           Error **errp, void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    BDRVQcow2State *s = bs->opaque;
> +    ssize_t ret;
> +
> +    if ((offset + buflen) > s->crypto_header.length) {
> +        error_setg(errp, "Request for data outside of extension header");

Again, I think you want to be very careful and not allow writes beyond
the initial header length reservation, even if that leaves the tail of
the cluster-aligned FDE area unwritable.

> +        return -1;
> +    }
> +
> +    ret = bdrv_pwrite(bs->file->bs,
> +                      s->crypto_header.offset + offset, buf, buflen);
> +    if (ret < 0) {
> +        error_setg_errno(errp, -ret, "Could not read encryption header");
> +        return -1;
> +    }
> +    return ret;
> +}
> +
> +
>  /* 
>   * read qcow2 extension and fill bs
>   * start reading from start_offset
> @@ -83,6 +155,7 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
>   */
>  static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
>                                   uint64_t end_offset, void **p_feature_table,
> +                                 int flags,
>                                   Error **errp)

Worth packing these last two parameters on a single line?

>  {
>      BDRVQcow2State *s = bs->opaque;
> @@ -159,6 +232,39 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
>              }
>              break;
>  
> +        case QCOW2_EXT_MAGIC_CRYPTO_HEADER: {
> +            unsigned int cflags = 0;
> +            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> +                error_setg(errp, "CRYPTO header extension only "
> +                           "expected with LUKS encryption method");
> +                return -EINVAL;
> +            }
> +            if (ext.len != sizeof(Qcow2CryptoHeaderExtension)) {
> +                error_setg(errp, "CRYPTO header extension size %u, "
> +                           "but expected size %zu", ext.len,
> +                           sizeof(Qcow2CryptoHeaderExtension));
> +                return -EINVAL;
> +            }
> +
> +            ret = bdrv_pread(bs->file->bs, offset, &s->crypto_header, ext.len);
> +            if (ret < 0) {
> +                error_setg_errno(errp, -ret,
> +                                 "Unable to read CRYPTO header extension");
> +                return ret;
> +            }
> +            be64_to_cpu(s->crypto_header.offset);
> +            be64_to_cpu(s->crypto_header.length);

Seeing this made me look back at patch 7.  It's convenient that the LUKS
header also uses big-endian storage (otherwise, you'd have to tweak the
portion of the qcow2 spec that requires big-endian everywhere to special
case LUKS header content - although that is not the content being
byte-swapped here).

> +
> +            if (flags & BDRV_O_NO_IO) {
> +                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
> +            }
> +            s->crypto = qcrypto_block_open(s->crypto_opts,
> +                                           qcow2_crypto_hdr_read_func,
> +                                           bs, cflags, errp);
> +            if (!s->crypto) {
> +                return -EINVAL;
> +            }
> +        }   break;
>          default:
>              /* unknown magic - save it in case we need to rewrite the header */

Hmm. Based solely on the presence or absence of unknown headers, earlier
versions of qemu-img that don't recognize the new FDE extension header
would happily open an image without decrypting it, which may have
disastrous results if it writes to guest data.  But I think we are safe
in that the only time the FDE extension header is present is if the
crypt_method is 2, but older versions of qemu should reject crypt_method
2 as unknown.  Phew - we don't have to burn an incompatible feature bit
to protect newer images from being corrupted by an older qemu.

>              {
> @@ -472,6 +578,11 @@ static QemuOptsList qcow2_runtime_opts = {
>              .type = QEMU_OPT_NUMBER,
>              .help = "Clean unused cache entries after this time (in seconds)",
>          },
> +        {
> +            .name = QCOW2_OPT_KEY_SECRET,
> +            .type = QEMU_OPT_STRING,
> +            .help = "ID of the secret that provides the encryption key",
> +        },
>          { /* end of list */ }
>      },
>  };
> @@ -589,6 +700,111 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
>      }
>  }
>  
> +
> +static QCryptoBlockOpenOptions *
> +qcow2_crypto_open_opts_init(QCryptoBlockFormat format, QemuOpts *opts,

qemu style doesn't usually split return type from function name. I won't
request a reformat, but others might be more picky.

> +static QCryptoBlockCreateOptions *
> +qcow2_crypto_create_opts_init(QCryptoBlockFormat format, QemuOpts *opts,
> +                              Error **errp)
> +{
> +    OptsVisitor *ov;
> +    QCryptoBlockCreateOptions *ret;
> +    Error *local_err = NULL, *end_err = NULL;
> +    Visitor *v;
> +
> +    ret = g_new0(QCryptoBlockCreateOptions, 1);
> +    ret->format = format;
> +
> +    ov = opts_visitor_new(opts);
> +    v = opts_get_visitor(ov);
> +    visit_start_struct(v, NULL, NULL, NULL, 0, &local_err);

Depending on whether Markus' qapi-next branch lands first, this (and
similar) lines will have to change to drop an unused NULL.

> +
> +    visit_end_struct(v, &end_err);

and my pending patches beyond what is in qapi-next affect this line. Oh
well, we'll get it figured out.

> @@ -754,6 +971,28 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
>      r->discard_passthrough[QCOW2_DISCARD_OTHER] =
>          qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
>  
> +    switch (s->crypt_method_header) {
> +    case QCOW_CRYPT_NONE:
> +        break;

Are we allowing in-place updates to change the encryption method?  It
may be safer to require that in-place updates can't change encryption,
for now (that is, you can only set encryption at creation of a new file,
and then copy data from one file to another to change how it is
encrypted).  And even if we DO want to allow a user to convert an image
from encrypted to plain, or from plain to encrypted, I would favor it as
a separate patch, so that we make sure we properly handle the
creation/removal of the FDE extension, as well as en/decrypting every
byte of payload independently from simpler read/write of an encrypted image.

> @@ -788,6 +1027,9 @@ static void qcow2_update_options_commit(BlockDriverState *bs,
>          s->cache_clean_interval = r->cache_clean_interval;
>          cache_clean_timer_init(bs, bdrv_get_aio_context(bs));
>      }
> +
> +    qapi_free_QCryptoBlockOpenOptions(s->crypto_opts);
> +    s->crypto_opts = r->crypto_opts;
>  }
>  
>  static void qcow2_update_options_abort(BlockDriverState *bs,
> @@ -799,6 +1041,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
>      if (r->refcount_block_cache) {
>          qcow2_cache_destroy(bs, r->refcount_block_cache);
>      }
> +    qapi_free_QCryptoBlockOpenOptions(r->crypto_opts);

Again, I don't know that we should allow updating the use of crypto
during an update options task, or if we do, it should be in a separate
patch (let's get reading/writing to an encrypted image correct first,
before we worry about in-place conversion).

> @@ -1104,12 +1337,37 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
>  
>      /* read qcow2 extensions */
>      if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL,
> -        &local_err)) {
> +                              flags, &local_err)) {
>          error_propagate(errp, local_err);
>          ret = -EINVAL;
>          goto fail;
>      }
>  
> +    /* qcow2_read_extension may have setup the crypto context

s/setup/set up/

> +     * if the crypt method needs a header region, some methods
> +     * don't need header extensions, so must check here
> +     */
> +    if (s->crypt_method_header && !s->crypto) {

> @@ -1984,6 +2222,77 @@ static int qcow2_change_backing_file(BlockDriverState *bs,
>      return qcow2_update_header(bs);
>  }
>  
> +
> +static int qcow2_change_encryption(BlockDriverState *bs, QemuOpts *opts,
> +                                   Error **errp)
> +{
> +    BDRVQcow2State *s = bs->opaque;
> +    const char *cryptostr;
> +    QCryptoBlockCreateOptions *cryptoopts = NULL;
> +    QCryptoBlock *crypto = NULL;
> +    size_t i;
> +    int ret = -EINVAL;
> +
> +    /* Default to LUKS if crypto-format is not set */
> +    cryptostr = qemu_opt_get_del(opts, QCOW2_OPT_CRYPTO_FORMAT);
> +    if (cryptostr) {
> +        for (i = 0; i < Q_CRYPTO_BLOCK_FORMAT__MAX; i++) {
> +            if (g_str_equal(QCryptoBlockFormat_lookup[i], cryptostr)) {
> +                cryptoopts = qcow2_crypto_create_opts_init(i, opts, errp);
> +                if (!cryptoopts) {
> +                    ret = -EINVAL;
> +                    goto out;
> +                }
> +                break;
> +            }
> +        }
> +        if (!cryptoopts) {
> +            error_setg(errp, "Unknown crypto format %s", cryptostr);

Could use qapi_enum_parse() here.

> @@ -2267,11 +2581,17 @@ static int qcow2_create2(const char *filename, int64_t total_size,
>      bdrv_unref(bs);
>      bs = NULL;
>  
> -    /* Reopen the image without BDRV_O_NO_FLUSH to flush it before returning */
> +    /* Reopen the image without BDRV_O_NO_FLUSH to flush it before returning

s/$/./

> +     * Using BDRV_O_NO_IO, since encryption is now setup we don't want to
> +     * have to setup decryption context. We're not doing any I/O on the top
> +     * level BlockDriverState, only lower layers, where BDRV_O_NO_IO does
> +     * not have effect.
> +     */
>      options = qdict_new();
>      qdict_put(options, "driver", qstring_from_str("qcow2"));
>      ret = bdrv_open(&bs, filename, NULL, options,
> -                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING,
> +                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING |
> +                    BDRV_O_NO_IO, /* Don't want to activate encryption */

Do we really need the second comment, after the first?

>                      &local_err);
>      if (local_err) {
>          error_propagate(errp, local_err);
> @@ -3046,9 +3366,9 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
>              backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
>          } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT)) {
>              encrypt = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT,
> -                                        !!s->cipher);
> +                                        !!s->crypto);
>  
> -            if (encrypt != !!s->cipher) {
> +            if (encrypt != !!s->crypto) {
>                  error_report("Changing the encryption flag is not supported");

Oh, so we don't support changing the encryptiong yet (good).  But it
means I was confused about what was happening above with regards to code
on changing encryption metadata.

> @@ -163,6 +173,11 @@ typedef struct QCowSnapshot {
>  struct Qcow2Cache;
>  typedef struct Qcow2Cache Qcow2Cache;
>  
> +typedef struct Qcow2CryptoHeaderExtension {
> +    uint64_t offset;
> +    uint64_t length;
> +} QEMU_PACKED Qcow2CryptoHeaderExtension;
> +
>  typedef struct Qcow2UnknownHeaderExtension {
>      uint32_t magic;
>      uint32_t len;
> @@ -256,7 +271,9 @@ typedef struct BDRVQcow2State {
>  
>      CoMutex lock;
>  
> -    QCryptoCipher *cipher; /* current cipher, NULL if no key yet */
> +    Qcow2CryptoHeaderExtension crypto_header; /* QCow2 header extension */
> +    QCryptoBlockOpenOptions *crypto_opts; /* Disk encryption runtime options */
> +    QCryptoBlock *crypto; /* Disk encryption format driver */
>      uint32_t crypt_method_header;

Seems reasonable.

>      uint64_t snapshots_offset;
>      int snapshots_size;
> diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> index 47630ee..2f4b983 100644
> --- a/crypto/block-luks.c
> +++ b/crypto/block-luks.c
> @@ -990,7 +990,6 @@ qcrypto_block_luks_create(QCryptoBlock *block,
>          cpu_to_be32s(&luks->header.key_slots[i].stripes);
>      }
>  
> -
>      /* Write out the partition header and key slot headers */
>      writefunc(block, 0,
>                (const uint8_t *)&luks->header,

Spurious hunk, probably from rebasing.

Continuing on at [3].

> diff --git a/docs/specs/qcow2.txt b/docs/specs/qcow2.txt
> index f236d8c..4d141a6 100644
> --- a/docs/specs/qcow2.txt
> +++ b/docs/specs/qcow2.txt

[1] Starting my review here.

> @@ -45,6 +45,7 @@ The first cluster of a qcow2 image contains the file header:
>           32 - 35:   crypt_method
>                      0 for no encryption
>                      1 for AES encryption
> +                    2 for LUKS encryption

May be worth an additional comment:

The choice of crypt_method affects whether the full disk encryption
header extension must be used; see details below.

>  
>           36 - 39:   l1_size
>                      Number of entries in the active L1 table
> @@ -123,6 +124,7 @@ be stored. Each extension has a structure like the following:
>                          0x00000000 - End of the header extension area
>                          0xE2792ACA - Backing file format name
>                          0x6803f857 - Feature name table
> +                        0x4c554b53 - Full disk encryption header pointer

We aren't very consistent on case within the hex constants. Not your
fault, but might be nice to fix.

>                          other      - Unknown header extension, can be safely
>                                       ignored
>  
> @@ -166,6 +168,78 @@ the header extension data. Each entry look like this:
>                      terminated if it has full length)
>  
>  
> +== Full disk encryption header pointer ==
> +
> +The full disk encryption header must be present if, and only if, the
> +'crypt_method' header requires metadata. Currently this is only true
> +of the 'LUKS' crypt method. The header extension must be absent for
> +other methods.
> +
> +This header provides the offset at which the crypt method can store
> +its additional data, as well as the length of such data.
> +
> +    Byte  0 -  7:   Offset into the image file at which the encryption
> +                    header starts. Must be aligned to a cluster boundary.
> +    Byte  8 - 16:   Length of the encryption header. Must be a multiple
> +                    of the cluster size.

I would add "in bytes" to both descriptions.

Should we explicitly document that the length occupied by the extension
header is NOT counted towards the guest-visible length; therefore, the
guest-visible size is the payload of the encrypted disk?

> +
> +While the header extension allocates the length as a multiple of the
> +cluster size, the encryption header may not consume the full permitted
> +allocation.

Should we require that any unused trailing space be all 0?

> +
> +For the LUKS crypt method, the encryption header works as follows.
> +
> +The first 592 bytes of the header clusters will contain the LUKS
> +partition header. This is then followed by the key material data areas.
> +The size of the key material data areas is determined by the number of
> +stripes in the key slot and key size. Refer to the LUKS format
> +specification ('docs/on-disk-format.pdf' in the cryptsetup source
> +package) for details of the LUKS partition header format.
> +
> +In the LUKS partition header, the "payload-offset" field does not refer
> +to the offset of the QCow2 payload. Instead it simply refers to the
> +total required length of the LUKS header plus key material regions.

I'm guessing that you are allowing the payload-offset to NOT be a
multiple of the cluster size.  It may be worth documenting that since
LUKS requires encryption to happen on 512-byte sector boundaries, where
the sector number feeds the IV used to encrypt that sector, that
guest-visible sector 0 is treated as LUKS sector payload-offset, even if
payload-offset is not qcow2-cluster-aligned.

> +
> +In the LUKS key slots header, the "key-material-offset" is relative
> +to the start of the LUKS header clusters in the qcow2 container,
> +not the start of the qcow2 file.
> +
> +Logically the layout looks like
> +

How does encryption interact with internal snapshots?  Is the encryption
header over all snapshots at once, or are we taking snapshots of the
current key slot usage?  That is, if we later add a command to allow key
slot manipulation (add a new user password on a vacant key slot, or
revoke a key slot), what happens if the user takes a snapshot, revokes a
key slot, then takes a second snapshot, then wants to revert to the
first snapshot?  Will the revoked password still be usable to unlock the
first snapshot contents, or did revoking it destroy that user's ability
to access the snapshot?

I'm leaning towards the latter - there is only a single LUKS header for
the entire qcow2 file; LUKS key management is global regardless of what
ever other content is stored, and the user passwords that unlock the
LUKS master key control whether a user can access all or none of the
rest of the qcow2 data.

I suspect that backing files are NOT encrypted by the LUKS header of the
active file.  That is, if we go to read a sector, and it is not present
in the current qcow2 image, then reading it from the backing file does
NOT need to decrypt data (unless the backing file itself independently
used a LUKS header).

Conversely, if we have a backing file that is encrypted, do we want to
place any requirements that the wrapper file can/must use encryption?  I
don't see any technical reasons that would require it, but from a data
safety standpoint, it seems awkward that a user that provides the LUKS
password to read the backing file can then write the same data
unencrypted into their wrapper file on copy-on-write operations.

> +++ b/qapi/block-core.json
> @@ -1789,6 +1789,8 @@
>  # @cache-clean-interval:  #optional clean unused entries in the L2 and refcount
>  #                         caches. The interval is in seconds. The default value
>  #                         is 0 and it disables this feature (since 2.5)
> +# @key-secret:            #optional the ID of a QCryptoSecret object providing
> +#                         the decryption key (since 2.6)

As in other patches in this series, may be worth mentioning that this is
mandatory for doing I/O on an encrypted disk, and optional when the disk
is not encrypted or when only metadata is being probed.

>  #
>  # Since: 1.7
>  ##
> @@ -1802,8 +1804,8 @@
>              '*cache-size': 'int',
>              '*l2-cache-size': 'int',
>              '*refcount-cache-size': 'int',
> -            '*cache-clean-interval': 'int' } }
> -
> +            '*cache-clean-interval': 'int',
> +            '*key-secret': 'str' } }
>  
>  ##
>  # @BlockdevOptionsArchipelago
> diff --git a/tests/qemu-iotests/049 b/tests/qemu-iotests/049
> index 93aa0ea..765b950 100755
> --- a/tests/qemu-iotests/049

Okay, back to [2], then this will be spot [3].

> +++ b/tests/qemu-iotests/049
> @@ -107,7 +107,7 @@ test_qemu_img create -f $IMGFMT -o preallocation=1234 "$TEST_IMG" 64M
>  echo "== Check encryption option =="
>  echo
>  test_qemu_img create -f $IMGFMT -o encryption=off "$TEST_IMG" 64M
> -test_qemu_img create -f $IMGFMT -o encryption=on "$TEST_IMG" 64M
> +test_qemu_img create -f $IMGFMT --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 "$TEST_IMG" 64M
>  
>  echo "== Check lazy_refcounts option (only with v3) =="
>  echo
> diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out
> index a2b6703..c9f0bc5 100644
> --- a/tests/qemu-iotests/049.out
> +++ b/tests/qemu-iotests/049.out
> @@ -186,14 +186,11 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_si
>  qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M
>  Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
>  
> -qemu-img create -f qcow2 -o encryption=on TEST_DIR/t.qcow2 64M
> +qemu-img create -f qcow2 --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 TEST_DIR/t.qcow2 64M
>  qemu-img: Encrypted images are deprecated
>  Support for them will be removed in a future release.
>  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> -qemu-img: Encrypted images are deprecated
> -Support for them will be removed in a future release.
> -You can use 'qemu-img convert' to convert your image to an unencrypted one.
> -Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16
> +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16 key-secret=sec0

So now that we support LUKS encryption by default, we no longer need the
deprecation warning.  Do we still forbid the creation of new images with
non-LUKS encryption?  That is, even though the new code will let us read
old images, I want to make sure we test the error message (or
deprecation warning) when trying to use the new options to request the
old format during image creation.

>  
>  == Check lazy_refcounts option (only with v3) ==
>  
> diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
> index a952330..b0572d4 100644
> --- a/tests/qemu-iotests/082.out
> +++ b/tests/qemu-iotests/082.out
> @@ -53,6 +53,13 @@ cluster_size     qcow2 cluster size
>  preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
>  lazy_refcounts   Postpone refcount updates
>  refcount_bits    Width of a reference count entry in bits
> +encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
> +key-secret       ID of the secret that provides the encryption key
> +cipher-alg       Name of encryption cipher algorithm
> +cipher-mode      Name of encryption cipher mode
> +ivgen-alg        Name of IV generator algorithm
> +ivgen-hash-alg   Name of IV generator hash algorithm
> +hash-alg         Name of encryption hash algorithm
>  nocow            Turn off copy-on-write (valid only on btrfs)

Should this help text mention defaults?

> +++ b/tests/qemu-iotests/087
> @@ -184,9 +184,18 @@ echo
>  echo === Encrypted image ===
>  echo
>  
> -_make_test_img -o encryption=on $size
> +_make_test_img --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 $size
>  run_qemu -S <<EOF
>  { "execute": "qmp_capabilities" }
> +{ "execute": "object-add",
> +  "arguments": {
> +      "qom-type": "secret",
> +      "id": "sec0",
> +      "props": {
> +          "data": "123456"
> +      }
> +  }
> +}
>  { "execute": "blockdev-add",
>    "arguments": {
>        "options": {
> @@ -195,7 +204,8 @@ run_qemu -S <<EOF
>          "file": {
>              "driver": "file",
>              "filename": "$TEST_IMG"
> -        }
> +        },
> +        "key-secret": "sec0"
>        }

Nice - proof that we can hot-plug a secret.  Which means libvirt will
have to be taught to _always_ prepopulate a master key for new enough
qemu, even if there is no encrypted disk on the command line, to cater
for hotplug down the road.

> +++ b/tests/qemu-iotests/134
> @@ -44,23 +44,31 @@ _supported_os Linux
>  

> -128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
> +can't open device /home/berrange/src/virt/qemu/tests/qemu-iotests/scratch/t.qcow2: Invalid password, cannot unlock any keyslot
> +no file open, try 'help open'

Umm, you'll want to fix this test to run on more than just your machine.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-02-08 16:28     ` Daniel P. Berrange
@ 2016-02-08 20:23       ` Eric Blake
  2016-02-09  9:55         ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-08 20:23 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

[-- Attachment #1: Type: text/plain, Size: 2440 bytes --]

On 02/08/2016 09:28 AM, Daniel P. Berrange wrote:

>> My vote: do the same as we do for qcow2 or any other format.  Make the
>> size requested by the user as the size visible to the guest, and a
>> fully-allocated image will take more space on the host than what the
>> guest is using (that is, a fully-allocated 10G LUKS disk would present
>> 10G payload to the guest but occupy 10G+1M on the host).
> 
> Yes, I had a todo item in the code asking which was better
> 
>     /* XXX Should we treat size as being total physical size
>      * of the image (ie payload + encryption header), or just
>      * the logical size of the image (ie payload). If the latter
>      * then we need to extend 'size' to include the header
>      * size */
>     qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> 
> And Fam suggested the same as you - show full 10 G to the guest.
> 
> The complication is that we need to create the backing file
> before we format the luks header, and we don't know the size
> of the luks header at that point. So either I could format
> the luks header into a temporary in-memory buffer, or I have
> to create the file and then resize it afterwards, or I have
> to provide some way to calculate the eventual header size
> prior to creating it. I didn't much like any of those options :-)

Well, if we hard-code stripes==4000, then we pretty much know that the
header is just under 1M for the maximum size key (aes-256); and rounding
up to 1M is nice for all cluster sizes except 2M.  I suppose we could
fit in 512k if we use a smaller key (aes-128), but would it hurt to just
hard-code a reservation of 1M?

>> Cool!  Would it be worth augmenting the commit message to show a
>> sequence of commands where you loopback mount a LUKS image file created
>> by qemu, then wire that up with cryptsetup, as a proof that the two are
>> compatible implementations?
> 
> My intent is to add something to tests/qemu-iotests that will check
> interoperability between qemu + cryptsetup. Slight complication is
> that those io tests all expect to run unprivileged. So it'll need
> a manual step run privileged to create the cryptsetup disk images
> for testing with.

We've checked in compressed binary images before; but 1M of key material
won't compress well.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
@ 2016-02-08 20:30   ` Eric Blake
  2016-02-09 12:33     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-08 20:30 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 2161 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Instead of requiring separate input/output buffers for
> encrypting data, change encrypt_sectors() to assume
> use of a single buffer, encrypting in place. One current
> caller all uses the same buffer for input/output already
> and the other two callers are easily converted todo so.

s/todo/to do/

> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow.c | 31 ++++++++++---------------------
>  1 file changed, 10 insertions(+), 21 deletions(-)
> 

> @@ -453,13 +447,12 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
>                      uint64_t start_sect;
>                      assert(s->cipher);
>                      start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
> -                    memset(s->cluster_data + 512, 0x00, 512);
> +                    memset(s->cluster_data, 0x00, 512);
>                      for(i = 0; i < s->cluster_sectors; i++) {

As long as you're touching near here, is it worth adding a space before '('?

> @@ -672,7 +665,6 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
>      BDRVQcowState *s = bs->opaque;
>      int index_in_cluster;
>      uint64_t cluster_offset;
> -    const uint8_t *src_buf;
>      int ret = 0, n;
>      uint8_t *cluster_data = NULL;
>      struct iovec hd_iov;
> @@ -715,18 +707,15 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
>              if (!cluster_data) {
>                  cluster_data = g_malloc0(s->cluster_size);
>              }
> -            if (encrypt_sectors(s, sector_num, cluster_data, buf,
> +            if (encrypt_sectors(s, sector_num, buf,
>                                  n, true, &err) < 0) {
>                  error_free(err);
>                  ret = -EIO;
>                  break;
>              }
> -            src_buf = cluster_data;

cluster_data is now unused; you've got a wasted g_malloc0() here and
g_free() later on.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
@ 2016-02-08 20:57   ` Eric Blake
  0 siblings, 0 replies; 69+ messages in thread
From: Eric Blake @ 2016-02-08 20:57 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 1596 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> This converts the qcow2 driver to make use of the QCryptoBlock

s/qcow2/qcow/

> APIs for encrypting image content. This is only wired up to
> permit use of the legacy QCow encryption format. Users who wish
> to have the strong LUKS format should switch to qcow2 instead.
> 
> With this change it is now required to use the QCryptoSecret
> object for providing passwords, instead of the current block
> password APIs / interactive prompting.
> 
>   $QEMU \
>     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
>     -drive file=/home/berrange/encrypted.qcow,key-secret=sec0
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow.c         | 173 +++++++++++++++++++++++----------------------------
>  qapi/block-core.json |  17 ++++-
>  2 files changed, 93 insertions(+), 97 deletions(-)
> 


> +++ b/qapi/block-core.json
> @@ -1756,6 +1756,21 @@
>              'mode':  'Qcow2OverlapCheckMode' } }
>  
>  ##
> +# @BlockdevOptionsQcow
> +#
> +# Driver specific block device options for qcow.
> +#
> +# @key-secret:   #optional ID of the "secret" object providing the
> +#                AES decryption key.

Maybe worth a mention that this is supported for decrypting old images,
but not for use in creating new images (but then again, who creates new
qcow images these days).

With the commit typo fixed,
Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs Daniel P. Berrange
@ 2016-02-08 21:23   ` Eric Blake
  2016-02-09 12:34     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-08 21:23 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 3998 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Now that all encryption keys must be provided upfront via
> the QCryptoSecret API and associated block driver properties
> there is no need for any explicit encryption handling APIs
> in the block layer. Encryption can be handled transparently
> within the block driver. We only retain an API for querying
> whether an image is encrypted or not, since that is a
> potentially useful piece of metadata to report to the user.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> @@ -2190,7 +2181,6 @@ void bdrv_close(BlockDriverState *bs)
>          bs->backing_format[0] = '\0';
>          bs->total_sectors = 0;
>          bs->encrypted = 0;
> -        bs->valid_key = 0;

It would be nice if this patch (or maybe a followup) switched
bs->encrypted to bool, rather than int.


> -void bdrv_add_key(BlockDriverState *bs, const char *key, Error **errp)
> -{
> -    if (key) {
> -        if (!bdrv_is_encrypted(bs)) {
> -            error_setg(errp, "Node '%s' is not encrypted",
> -                      bdrv_get_device_or_node_name(bs));
> -        } else if (bdrv_set_key(bs, key) < 0) {
> -            error_setg(errp, QERR_INVALID_PASSWORD);

If I'm not mistaken, this was the only use of QERR_INVALID_PASSWORD;
please also nuke it from include/qapi/qmp/qerror.h.

> -        }
> -    } else {
> -        if (bdrv_key_required(bs)) {
> -            error_set(errp, ERROR_CLASS_DEVICE_ENCRYPTED,
> -                      "'%s' (%s) is encrypted",

Likewise, this looks like the last use of ERROR_CLASS_DEVICE_ENCRYPTED
(since 15/17 nuked the only client in hmp.c that cared about the error
class); it would be nice to nuke the remains in include/qapi/error.h and
in qapi/common.json.

> +++ b/blockdev.c
> @@ -2261,24 +2257,8 @@ void qmp_block_passwd(bool has_device, const char *device,
>                        bool has_node_name, const char *node_name,
>                        const char *password, Error **errp)
>  {
> -    Error *local_err = NULL;
> -    BlockDriverState *bs;
> -    AioContext *aio_context;
> -
> -    bs = bdrv_lookup_bs(has_device ? device : NULL,
> -                        has_node_name ? node_name : NULL,
> -                        &local_err);
> -    if (local_err) {
> -        error_propagate(errp, local_err);
> -        return;
> -    }
> -
> -    aio_context = bdrv_get_aio_context(bs);
> -    aio_context_acquire(aio_context);
> -
> -    bdrv_add_key(bs, password, errp);
> -
> -    aio_context_release(aio_context);
> +    error_setg_errno(errp, -ENOSYS,
> +                     "Setting block passwords directly is no longer supported");

We should document in qapi/block-core.json that the QMP 'block_passwd'
command is deprecated, and will be removed in a future release (but I
suspect we don't want to completely remove it in 2.6, so that we at
least have time to find clients that were relying on it and should be
using the new methods).

> +++ b/include/block/block_int.h
> @@ -374,7 +374,6 @@ struct BlockDriverState {
>      int read_only; /* if true, the media is read only */
>      int open_flags; /* flags used to open the file, re-used for re-open */
>      int encrypted; /* if true, the media is encrypted */
> -    int valid_key; /* if true, a valid encryption key has been set */
>      int sg;        /* if true, the device is a /dev/sg* */
>      int copy_on_read; /* if true, copy read backing sectors into image
>                           note this is a reference count */

Hmm - several variables that are only 'true' or 'false' and should be
typed 'bool'.  Only 'encrypted' is a candidate for change in this patch;
'sg' is unrelated.  And 'copy_on_read' should be tweaked to document
'nonzero', not 'true', since it is used as a reference count rather than
bool.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption
  2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption Daniel P. Berrange
@ 2016-02-08 21:26   ` Eric Blake
  2016-02-09 12:35     ` Daniel P. Berrange
  0 siblings, 1 reply; 69+ messages in thread
From: Eric Blake @ 2016-02-08 21:26 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Kevin Wolf, Fam Zheng, qemu-block

[-- Attachment #1: Type: text/plain, Size: 1791 bytes --]

On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> Refuse to use images with the legacy AES-CBC encryption
> format in the system emulators. They are still fully
> supported in the qemu-img, qemu-io & qemu-nbd tools in
> order to allow data to be liberated and for compatibility
> with older QEMU versions. Continued support in these tools
> is not a notable burden with the new FDE framework.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> +++ b/block/qcow.c
> @@ -180,6 +180,14 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
>      s->crypt_method_header = header.crypt_method;
>      if (s->crypt_method_header) {
>          if (s->crypt_method_header == QCOW_CRYPT_AES) {
> +            if (bdrv_uses_whitelist()) {
> +                error_setg(errp,
> +                           "Use of AES-CBC encrypted qcow images is no longer "
> +                           "supported. Please use the qcow2 LUKS format instead.");

error_setg() should be a single phrase with no trailing punctuation, not
two sentences.  Use error_append_hint() for the suggestion of the
replacement.

> +++ b/block/qcow2.c
> @@ -1209,6 +1209,14 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
>  
>      s->crypt_method_header = header.crypt_method;
>      if (s->crypt_method_header) {
> +        if (bdrv_uses_whitelist() &&
> +            s->crypt_method_header == QCOW_CRYPT_AES) {
> +            error_setg(errp,
> +                       "Use of AES-CBC encrypted qcow2 images is no longer "
> +                       "supported. Please use the qcow2 LUKS format instead.");

Ditto

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver
  2016-02-08 20:23       ` Eric Blake
@ 2016-02-09  9:55         ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-09  9:55 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Mon, Feb 08, 2016 at 01:23:28PM -0700, Eric Blake wrote:
> On 02/08/2016 09:28 AM, Daniel P. Berrange wrote:
> 
> >> My vote: do the same as we do for qcow2 or any other format.  Make the
> >> size requested by the user as the size visible to the guest, and a
> >> fully-allocated image will take more space on the host than what the
> >> guest is using (that is, a fully-allocated 10G LUKS disk would present
> >> 10G payload to the guest but occupy 10G+1M on the host).
> > 
> > Yes, I had a todo item in the code asking which was better
> > 
> >     /* XXX Should we treat size as being total physical size
> >      * of the image (ie payload + encryption header), or just
> >      * the logical size of the image (ie payload). If the latter
> >      * then we need to extend 'size' to include the header
> >      * size */
> >     qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
> > 
> > And Fam suggested the same as you - show full 10 G to the guest.
> > 
> > The complication is that we need to create the backing file
> > before we format the luks header, and we don't know the size
> > of the luks header at that point. So either I could format
> > the luks header into a temporary in-memory buffer, or I have
> > to create the file and then resize it afterwards, or I have
> > to provide some way to calculate the eventual header size
> > prior to creating it. I didn't much like any of those options :-)
> 
> Well, if we hard-code stripes==4000, then we pretty much know that the
> header is just under 1M for the maximum size key (aes-256); and rounding
> up to 1M is nice for all cluster sizes except 2M.  I suppose we could
> fit in 512k if we use a smaller key (aes-128), but would it hurt to just
> hard-code a reservation of 1M?

I actually figured out a way to deal with this - i just have to delay
creation of the underling file, to be done in the callback which initilizes
the luks header region.

> >> Cool!  Would it be worth augmenting the commit message to show a
> >> sequence of commands where you loopback mount a LUKS image file created
> >> by qemu, then wire that up with cryptsetup, as a proof that the two are
> >> compatible implementations?
> > 
> > My intent is to add something to tests/qemu-iotests that will check
> > interoperability between qemu + cryptsetup. Slight complication is
> > that those io tests all expect to run unprivileged. So it'll need
> > a manual step run privileged to create the cryptsetup disk images
> > for testing with.
> 
> We've checked in compressed binary images before; but 1M of key material
> won't compress well.

And realistically it is going to be 10's of MB per image, since I want
the test to verify more than just the header, and there's probably about
10+ different cipher combinations, so it would be many 100's of MBs to
check in. So that won't fly sadly.

> 
> -- 
> Eric Blake   eblake redhat com    +1-919-301-3266
> Libvirt virtualization library http://libvirt.org
> 



Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2016-02-08 18:12   ` Eric Blake
@ 2016-02-09 12:32     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-09 12:32 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Mon, Feb 08, 2016 at 11:12:37AM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > This converts the qcow2 driver to make use of the QCryptoBlock
> > APIs for encrypting image content. As well as continued support
> > for the legacy QCow2 encryption format, the appealing benefit
> > is that it enables support for the LUKS format inside qcow2.
> 
> I know Fam had some interesting ideas on changing qcow2 to be able to
> auto-load a LUKS format driver rather than duplicating code, which may
> completely change this patch, but I'll go ahead and review this patch as-is.


> > (Full Disk Encryption) header extension that specifies
> > the offset of a set of clusters to hold the FDE headers,
> > as well as the length of that region. The LUKS header is
> > thus stored in these extra allocated clusters before the
> > main image payload.
> > 
> > With this change it is now required to use the QCryptoSecret
> > object for providing passwords, instead of the current block
> > password APIs / interactive prompting.
> > 
> >   $QEMU \
> >     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
> >     -drive file=/home/berrange/encrypted.qcow2,key-secret=sec0
> > 
> > The new LUKS format is set as the new default format when
> > creating encrypted images. ie
> > 
> >   # qemu-img create --object secret,data=123456,id=sec0 \
> >        -f qcow2 -o encryption,key-secret=sec0 \
> >        test.qcow2 10G
> 
> Did we add '-o encryption' earlier in the series, or is this line a bit
> stale in reference to your --image-opts series?

'-o encryption' is actually the pre-existing flag used to turn
on the existing qcow2 built-in AES encryption. I repurposed it
as a generic flag to indicate desire for encryption, and have
the new 'encryption-format' flag to say whether you want the
legacy AES or modern LUKS format. IOW people using '-o encryption'
today will automatically start getting LUKS format after this
change is applied.

> > diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
> > index f5bc4f2..0512256 100644
> > --- a/block/qcow2-cluster.c
> 
> > +++ b/block/qcow2-refcount.c
> > @@ -1847,6 +1847,16 @@ static int calculate_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
> >          return ret;
> >      }
> >  
> > +    /* encryption */
> > +    if (s->crypto_header.length) {
> > +        ret = inc_refcounts(bs, res, refcount_table, nb_clusters,
> > +                            s->crypto_header.offset,
> > +                            s->crypto_header.length);
> > +        if (ret < 0) {
> > +            return ret;
> > +        }
> > +    }
> > +
> 
> Do we ever allow turning off encryption, where we'd need to decrement
> the refcounts as the FDE header is removed?

No, once a fle has encryption it can't be removed. Likewise you have
to choose it at time of creation - you can't add it afterwards.

> > diff --git a/block/qcow2.c b/block/qcow2.c
> > index 2fae692..5249ca2 100644
> > --- a/block/qcow2.c
> > +++ b/block/qcow2.c
> > @@ -34,6 +34,8 @@
> >  #include "qapi-event.h"
> >  #include "trace.h"
> >  #include "qemu/option_int.h"
> > +#include "qapi/opts-visitor.h"
> > +#include "qapi-visit.h"
> >  
> >  /*
> >    Differences with QCOW:
> > @@ -60,6 +62,7 @@ typedef struct {
> >  #define  QCOW2_EXT_MAGIC_END 0
> >  #define  QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
> >  #define  QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
> > +#define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x4c554b53
> 
> Aha: ASCII "LUKS", even though we expect the header to be useful for
> more than just LUKS encryption schemes.  Would ASCII "CRYP" be any
> nicer?  Or a random number, or the next set of hex digits from pi, or...?
> 
> I guess we don't have any real rules or patterns describing how magic
> numbers are supposed to be chosen, so it works for me.

Hah, top marks for reverse engineering my magic constant. I had
completely forgotten that I used the ASCII values of 'LUKS' for
this constant :-)

Since this is not luks specific anymore I'll change this value
to 0x0537be77.  Bonus points to anyone who can explain the
significance of that number (hint it is not an ascii representation
of anything - its a special number in its own right :-)


> > +static ssize_t qcow2_crypto_hdr_read_func(QCryptoBlock *block, size_t offset,
> > +                                          uint8_t *buf, size_t buflen,
> > +                                          Error **errp, void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    BDRVQcow2State *s = bs->opaque;
> > +    ssize_t ret;
> > +
> > +    if ((offset + buflen) > s->crypto_header.length) {
> 
> The inner () are redundant, but I don't mind them.
> 
> > +        error_setg(errp, "Request for data outside of extension header");
> > +        return -1;
> 
> Nice that you are trying to prevent reads beyond the block of data
> reserved by the FDE header; but is s->crypto_header.length the
> rounded-up cluster boundary, or just the length of the used portion of
> the LUKS header?...

It is intended to record the allocated space (ie rounded to the cluster
boundary), but on reflection, I think I'll change it to exactly the
size of the LUKS header.


> > +    }
> > +
> > +    ret = bdrv_pread(bs->file->bs,
> > +                     s->crypto_header.offset + offset, buf, buflen);
> > +    if (ret < 0) {
> > +        error_setg_errno(errp, -ret, "Could not read encryption header");
> > +        return -1;
> > +    }
> > +    return ret;
> > +}
> > +
> > +
> > +static ssize_t qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
> > +                                          Error **errp, void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    BDRVQcow2State *s = bs->opaque;
> > +    int64_t ret;
> > +
> > +    s->crypto_header.length = headerlen + (headerlen % s->cluster_size);
> 
> ...Huh?  Are you trying to round up to a cluster boundary?  This doesn't
> do what you want.  And even if you had correctly rounded up to cluster
> boundary size, that means that qcow2_crypto_hdr_read_func() can now read
> the tail beyond the size recorded in the crypto header - which could be
> dangerous if it means that tail can overlap the same sector number used
> for the first guest payload sector which uses a sector number of
> payload_offset from the LUKS header for its IV.> 
> > +
> > +    ret = qcow2_alloc_clusters(bs, s->crypto_header.length);
> 
> Does qcow2_alloc_clusters(bs, headerlen) properly round up the
> allocation to a cluster boundary?  If so, I think you want
> s->crypto_header.length = headerlen, full stop.

Agreed, I will make it so.

> 
> > +    if (ret < 0) {
> > +        s->crypto_header.length = 0;
> 
> And it may be easier to not modify s->crypto_header.length except on
> successful allocation, rather than having to undo it on failure.
> 
> > +        error_setg_errno(errp, -ret,
> > +                         "Cannot allocate cluster for LUKS header size %zu",
> > +                         headerlen);
> > +        return -1;
> > +    }
> > +
> > +    s->crypto_header.offset = ret;
> > +    return ret;
> > +}
> > +
> > +
> > +static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
> > +                                           const uint8_t *buf, size_t buflen,
> > +                                           Error **errp, void *opaque)
> > +{
> > +    BlockDriverState *bs = opaque;
> > +    BDRVQcow2State *s = bs->opaque;
> > +    ssize_t ret;
> > +
> > +    if ((offset + buflen) > s->crypto_header.length) {
> > +        error_setg(errp, "Request for data outside of extension header");
> 
> Again, I think you want to be very careful and not allow writes beyond
> the initial header length reservation, even if that leaves the tail of
> the cluster-aligned FDE area unwritable.

Yes, this will be ok when the length refers to the luks header only

> >  {
> >      BDRVQcow2State *s = bs->opaque;
> > @@ -159,6 +232,39 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> >              }
> >              break;
> >  
> > +        case QCOW2_EXT_MAGIC_CRYPTO_HEADER: {
> > +            unsigned int cflags = 0;
> > +            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
> > +                error_setg(errp, "CRYPTO header extension only "
> > +                           "expected with LUKS encryption method");
> > +                return -EINVAL;
> > +            }
> > +            if (ext.len != sizeof(Qcow2CryptoHeaderExtension)) {
> > +                error_setg(errp, "CRYPTO header extension size %u, "
> > +                           "but expected size %zu", ext.len,
> > +                           sizeof(Qcow2CryptoHeaderExtension));
> > +                return -EINVAL;
> > +            }
> > +
> > +            ret = bdrv_pread(bs->file->bs, offset, &s->crypto_header, ext.len);
> > +            if (ret < 0) {
> > +                error_setg_errno(errp, -ret,
> > +                                 "Unable to read CRYPTO header extension");
> > +                return ret;
> > +            }
> > +            be64_to_cpu(s->crypto_header.offset);
> > +            be64_to_cpu(s->crypto_header.length);
> 
> Seeing this made me look back at patch 7.  It's convenient that the LUKS
> header also uses big-endian storage (otherwise, you'd have to tweak the
> portion of the qcow2 spec that requires big-endian everywhere to special
> case LUKS header content - although that is not the content being
> byte-swapped here).

Yes its fortunate everyone agrees that big-endian is best on disk :-)

> > +
> > +            if (flags & BDRV_O_NO_IO) {
> > +                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
> > +            }
> > +            s->crypto = qcrypto_block_open(s->crypto_opts,
> > +                                           qcow2_crypto_hdr_read_func,
> > +                                           bs, cflags, errp);
> > +            if (!s->crypto) {
> > +                return -EINVAL;
> > +            }
> > +        }   break;
> >          default:
> >              /* unknown magic - save it in case we need to rewrite the header */
> 
> Hmm. Based solely on the presence or absence of unknown headers, earlier
> versions of qemu-img that don't recognize the new FDE extension header
> would happily open an image without decrypting it, which may have
> disastrous results if it writes to guest data.  But I think we are safe
> in that the only time the FDE extension header is present is if the
> crypt_method is 2, but older versions of qemu should reject crypt_method
> 2 as unknown.  Phew - we don't have to burn an incompatible feature bit
> to protect newer images from being corrupted by an older qemu.

Yeah, the crypt_method value saves us, because all older versions are
careful to validate that.

> > +
> > +static QCryptoBlockOpenOptions *
> > +qcow2_crypto_open_opts_init(QCryptoBlockFormat format, QemuOpts *opts,
> 
> qemu style doesn't usually split return type from function name. I won't
> request a reformat, but others might be more picky.

The lines get rather long if I dont :-)

> > @@ -754,6 +971,28 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
> >      r->discard_passthrough[QCOW2_DISCARD_OTHER] =
> >          qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
> >  
> > +    switch (s->crypt_method_header) {
> > +    case QCOW_CRYPT_NONE:
> > +        break;
> 
> Are we allowing in-place updates to change the encryption method?  It
> may be safer to require that in-place updates can't change encryption,
> for now (that is, you can only set encryption at creation of a new file,
> and then copy data from one file to another to change how it is
> encrypted).  And even if we DO want to allow a user to convert an image
> from encrypted to plain, or from plain to encrypted, I would favor it as
> a separate patch, so that we make sure we properly handle the
> creation/removal of the FDE extension, as well as en/decrypting every
> byte of payload independently from simpler read/write of an encrypted image.

I don't see us ever allowing in-place changes to encryption. You'd have
to re-write the entire file contents with obvious disaster if you hit
an error 1/2 way through. Far better to use qemu-img convert instead.


> >  static void qcow2_update_options_abort(BlockDriverState *bs,
> > @@ -799,6 +1041,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
> >      if (r->refcount_block_cache) {
> >          qcow2_cache_destroy(bs, r->refcount_block_cache);
> >      }
> > +    qapi_free_QCryptoBlockOpenOptions(r->crypto_opts);
> 
> Again, I don't know that we should allow updating the use of crypto
> during an update options task, or if we do, it should be in a separate
> patch (let's get reading/writing to an encrypted image correct first,
> before we worry about in-place conversion).

Note despite the name the qcow2_update_options* methods are
actually used during initial image creation, so this code is
about updating in-place options.

> > +     * Using BDRV_O_NO_IO, since encryption is now setup we don't want to
> > +     * have to setup decryption context. We're not doing any I/O on the top
> > +     * level BlockDriverState, only lower layers, where BDRV_O_NO_IO does
> > +     * not have effect.
> > +     */
> >      options = qdict_new();
> >      qdict_put(options, "driver", qstring_from_str("qcow2"));
> >      ret = bdrv_open(&bs, filename, NULL, options,
> > -                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING,
> > +                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_BACKING |
> > +                    BDRV_O_NO_IO, /* Don't want to activate encryption */
> 
> Do we really need the second comment, after the first?

Not any more, no.

> > @@ -3046,9 +3366,9 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
> >              backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
> >          } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT)) {
> >              encrypt = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT,
> > -                                        !!s->cipher);
> > +                                        !!s->crypto);
> >  
> > -            if (encrypt != !!s->cipher) {
> > +            if (encrypt != !!s->crypto) {
> >                  error_report("Changing the encryption flag is not supported");
> 
> Oh, so we don't support changing the encryptiong yet (good).  But it
> means I was confused about what was happening above with regards to code
> on changing encryption metadata.

Yes, its a misleading method name earlier used for 2 different things :-)


> > @@ -166,6 +168,78 @@ the header extension data. Each entry look like this:
> >                      terminated if it has full length)
> >  
> >  
> > +== Full disk encryption header pointer ==
> > +
> > +The full disk encryption header must be present if, and only if, the
> > +'crypt_method' header requires metadata. Currently this is only true
> > +of the 'LUKS' crypt method. The header extension must be absent for
> > +other methods.
> > +
> > +This header provides the offset at which the crypt method can store
> > +its additional data, as well as the length of such data.
> > +
> > +    Byte  0 -  7:   Offset into the image file at which the encryption
> > +                    header starts. Must be aligned to a cluster boundary.
> > +    Byte  8 - 16:   Length of the encryption header. Must be a multiple
> > +                    of the cluster size.
> 
> I would add "in bytes" to both descriptions.

Ok.

> Should we explicitly document that the length occupied by the extension
> header is NOT counted towards the guest-visible length; therefore, the
> guest-visible size is the payload of the encrypted disk?

Sure, can do.

> > +While the header extension allocates the length as a multiple of the
> > +cluster size, the encryption header may not consume the full permitted
> > +allocation.
> 
> Should we require that any unused trailing space be all 0?

I guess it might be a nice idea to have it zeroed in case it becomes
important later.

> > +For the LUKS crypt method, the encryption header works as follows.
> > +
> > +The first 592 bytes of the header clusters will contain the LUKS
> > +partition header. This is then followed by the key material data areas.
> > +The size of the key material data areas is determined by the number of
> > +stripes in the key slot and key size. Refer to the LUKS format
> > +specification ('docs/on-disk-format.pdf' in the cryptsetup source
> > +package) for details of the LUKS partition header format.
> > +
> > +In the LUKS partition header, the "payload-offset" field does not refer
> > +to the offset of the QCow2 payload. Instead it simply refers to the
> > +total required length of the LUKS header plus key material regions.
> 
> I'm guessing that you are allowing the payload-offset to NOT be a
> multiple of the cluster size.  It may be worth documenting that since
> LUKS requires encryption to happen on 512-byte sector boundaries, where
> the sector number feeds the IV used to encrypt that sector, that
> guest-visible sector 0 is treated as LUKS sector payload-offset, even if
> payload-offset is not qcow2-cluster-aligned.

Yeah, there's no restriction on payload-offset at all. Essentially
the payload-offset is completely redundant when used in the context
of qcow2. We could easily have said it should be 0, since qcow2
determines the actual payload offset. I felt it was worth leving
it to record the value LUKS would ordinarily have used, even though
we don't need it.

> > +In the LUKS key slots header, the "key-material-offset" is relative
> > +to the start of the LUKS header clusters in the qcow2 container,
> > +not the start of the qcow2 file.
> > +
> > +Logically the layout looks like
> > +
> 
> How does encryption interact with internal snapshots?  Is the encryption
> header over all snapshots at once, or are we taking snapshots of the
> current key slot usage?  That is, if we later add a command to allow key
> slot manipulation (add a new user password on a vacant key slot, or
> revoke a key slot), what happens if the user takes a snapshot, revokes a
> key slot, then takes a second snapshot, then wants to revert to the
> first snapshot?  Will the revoked password still be usable to unlock the
> first snapshot contents, or did revoking it destroy that user's ability
> to access the snapshot?
> 
> I'm leaning towards the latter - there is only a single LUKS header for
> the entire qcow2 file; LUKS key management is global regardless of what
> ever other content is stored, and the user passwords that unlock the
> LUKS master key control whether a user can access all or none of the
> rest of the qcow2 data.

NB passwords have no direct relationship to the data encryption key.
The passwords are just used to encrypt the master key. So you can
change the passwords at any time, without loosing ability to
decrypt old data. IOW, we can completely ignore the issue of snapshots.
All snapshot data is using the same master key.

> I suspect that backing files are NOT encrypted by the LUKS header of the
> active file.  That is, if we go to read a sector, and it is not present
> in the current qcow2 image, then reading it from the backing file does
> NOT need to decrypt data (unless the backing file itself independently
> used a LUKS header).

Yep, whether any backing file uses encryption is independant of
the decision wrt encryption for this file.

> Conversely, if we have a backing file that is encrypted, do we want to
> place any requirements that the wrapper file can/must use encryption?  I
> don't see any technical reasons that would require it, but from a data
> safety standpoint, it seems awkward that a user that provides the LUKS
> password to read the backing file can then write the same data
> unencrypted into their wrapper file on copy-on-write operations.

This feels like a policy decision that belongs in the mgmt app - indeed
that situation already exists today. ie you could have created a qcow2
file that points to a /dev/mapper/BLAH that is in fact backed by a LUKS
volume.

> > +++ b/qapi/block-core.json
> > @@ -1789,6 +1789,8 @@
> >  # @cache-clean-interval:  #optional clean unused entries in the L2 and refcount
> >  #                         caches. The interval is in seconds. The default value
> >  #                         is 0 and it disables this feature (since 2.5)
> > +# @key-secret:            #optional the ID of a QCryptoSecret object providing
> > +#                         the decryption key (since 2.6)
> 
> As in other patches in this series, may be worth mentioning that this is
> mandatory for doing I/O on an encrypted disk, and optional when the disk
> is not encrypted or when only metadata is being probed.

Yes


> > diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out
> > index a2b6703..c9f0bc5 100644
> > --- a/tests/qemu-iotests/049.out
> > +++ b/tests/qemu-iotests/049.out
> > @@ -186,14 +186,11 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_si
> >  qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M
> >  Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
> >  
> > -qemu-img create -f qcow2 -o encryption=on TEST_DIR/t.qcow2 64M
> > +qemu-img create -f qcow2 --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 TEST_DIR/t.qcow2 64M
> >  qemu-img: Encrypted images are deprecated
> >  Support for them will be removed in a future release.
> >  You can use 'qemu-img convert' to convert your image to an unencrypted one.
> > -qemu-img: Encrypted images are deprecated
> > -Support for them will be removed in a future release.
> > -You can use 'qemu-img convert' to convert your image to an unencrypted one.
> > -Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16
> > +Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16 key-secret=sec0
> 
> So now that we support LUKS encryption by default, we no longer need the
> deprecation warning.  Do we still forbid the creation of new images with
> non-LUKS encryption?  That is, even though the new code will let us read
> old images, I want to make sure we test the error message (or
> deprecation warning) when trying to use the new options to request the
> old format during image creation.

Forbidding creation of legacy encryption is dealt with in a later patch.

> >  == Check lazy_refcounts option (only with v3) ==
> >  
> > diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
> > index a952330..b0572d4 100644
> > --- a/tests/qemu-iotests/082.out
> > +++ b/tests/qemu-iotests/082.out
> > @@ -53,6 +53,13 @@ cluster_size     qcow2 cluster size
> >  preallocation    Preallocation mode (allowed values: off, metadata, falloc, full)
> >  lazy_refcounts   Postpone refcount updates
> >  refcount_bits    Width of a reference count entry in bits
> > +encryption-format Encryption format, 'luks' (default), 'qcow' (deprecated)
> > +key-secret       ID of the secret that provides the encryption key
> > +cipher-alg       Name of encryption cipher algorithm
> > +cipher-mode      Name of encryption cipher mode
> > +ivgen-alg        Name of IV generator algorithm
> > +ivgen-hash-alg   Name of IV generator hash algorithm
> > +hash-alg         Name of encryption hash algorithm
> >  nocow            Turn off copy-on-write (valid only on btrfs)
> 
> Should this help text mention defaults?

Yeah, good idea.

> > +++ b/tests/qemu-iotests/087
> > @@ -184,9 +184,18 @@ echo
> >  echo === Encrypted image ===
> >  echo
> >  
> > -_make_test_img -o encryption=on $size
> > +_make_test_img --object secret,id=sec0,data=123456 -o encryption=on,key-secret=sec0 $size
> >  run_qemu -S <<EOF
> >  { "execute": "qmp_capabilities" }
> > +{ "execute": "object-add",
> > +  "arguments": {
> > +      "qom-type": "secret",
> > +      "id": "sec0",
> > +      "props": {
> > +          "data": "123456"
> > +      }
> > +  }
> > +}
> >  { "execute": "blockdev-add",
> >    "arguments": {
> >        "options": {
> > @@ -195,7 +204,8 @@ run_qemu -S <<EOF
> >          "file": {
> >              "driver": "file",
> >              "filename": "$TEST_IMG"
> > -        }
> > +        },
> > +        "key-secret": "sec0"
> >        }
> 
> Nice - proof that we can hot-plug a secret.  Which means libvirt will
> have to be taught to _always_ prepopulate a master key for new enough
> qemu, even if there is no encrypted disk on the command line, to cater
> for hotplug down the road.

You can actually hotplug the master key too, but for simplicity I
expect that libvirt will just generate a AES master key for all
QEMU versions >= 2.6. We need it for encrypting RBD, iSCSI, Curl
passwords too.

> > +++ b/tests/qemu-iotests/134
> > @@ -44,23 +44,31 @@ _supported_os Linux
> >  
> 
> > -128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
> > +can't open device /home/berrange/src/virt/qemu/tests/qemu-iotests/scratch/t.qcow2: Invalid password, cannot unlock any keyslot
> > +no file open, try 'help open'
> 
> Umm, you'll want to fix this test to run on more than just your machine.

Opps :-)


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place
  2016-02-08 20:30   ` Eric Blake
@ 2016-02-09 12:33     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-09 12:33 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Mon, Feb 08, 2016 at 01:30:10PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Instead of requiring separate input/output buffers for
> > encrypting data, change encrypt_sectors() to assume
> > use of a single buffer, encrypting in place. One current
> > caller all uses the same buffer for input/output already
> > and the other two callers are easily converted todo so.
> 
> s/todo/to do/
> 
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  block/qcow.c | 31 ++++++++++---------------------
> >  1 file changed, 10 insertions(+), 21 deletions(-)
> > 
> 
> > @@ -453,13 +447,12 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
> >                      uint64_t start_sect;
> >                      assert(s->cipher);
> >                      start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
> > -                    memset(s->cluster_data + 512, 0x00, 512);
> > +                    memset(s->cluster_data, 0x00, 512);
> >                      for(i = 0; i < s->cluster_sectors; i++) {
> 
> As long as you're touching near here, is it worth adding a space before '('?
> 
> > @@ -672,7 +665,6 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
> >      BDRVQcowState *s = bs->opaque;
> >      int index_in_cluster;
> >      uint64_t cluster_offset;
> > -    const uint8_t *src_buf;
> >      int ret = 0, n;
> >      uint8_t *cluster_data = NULL;
> >      struct iovec hd_iov;
> > @@ -715,18 +707,15 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
> >              if (!cluster_data) {
> >                  cluster_data = g_malloc0(s->cluster_size);
> >              }
> > -            if (encrypt_sectors(s, sector_num, cluster_data, buf,
> > +            if (encrypt_sectors(s, sector_num, buf,
> >                                  n, true, &err) < 0) {
> >                  error_free(err);
> >                  ret = -EIO;
> >                  break;
> >              }
> > -            src_buf = cluster_data;
> 
> cluster_data is now unused; you've got a wasted g_malloc0() here and
> g_free() later on.

Oh, good point.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs
  2016-02-08 21:23   ` Eric Blake
@ 2016-02-09 12:34     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-09 12:34 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Mon, Feb 08, 2016 at 02:23:40PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Now that all encryption keys must be provided upfront via
> > the QCryptoSecret API and associated block driver properties
> > there is no need for any explicit encryption handling APIs
> > in the block layer. Encryption can be handled transparently
> > within the block driver. We only retain an API for querying
> > whether an image is encrypted or not, since that is a
> > potentially useful piece of metadata to report to the user.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> 
> > @@ -2190,7 +2181,6 @@ void bdrv_close(BlockDriverState *bs)
> >          bs->backing_format[0] = '\0';
> >          bs->total_sectors = 0;
> >          bs->encrypted = 0;
> > -        bs->valid_key = 0;
> 
> It would be nice if this patch (or maybe a followup) switched
> bs->encrypted to bool, rather than int.

I'll prefer todo it later to avoid mixing unrelated changes.

> > -void bdrv_add_key(BlockDriverState *bs, const char *key, Error **errp)
> > -{
> > -    if (key) {
> > -        if (!bdrv_is_encrypted(bs)) {
> > -            error_setg(errp, "Node '%s' is not encrypted",
> > -                      bdrv_get_device_or_node_name(bs));
> > -        } else if (bdrv_set_key(bs, key) < 0) {
> > -            error_setg(errp, QERR_INVALID_PASSWORD);
> 
> If I'm not mistaken, this was the only use of QERR_INVALID_PASSWORD;
> please also nuke it from include/qapi/qmp/qerror.h.
> 
> > -        }
> > -    } else {
> > -        if (bdrv_key_required(bs)) {
> > -            error_set(errp, ERROR_CLASS_DEVICE_ENCRYPTED,
> > -                      "'%s' (%s) is encrypted",
> 
> Likewise, this looks like the last use of ERROR_CLASS_DEVICE_ENCRYPTED
> (since 15/17 nuked the only client in hmp.c that cared about the error
> class); it would be nice to nuke the remains in include/qapi/error.h and
> in qapi/common.json.

Oh yes, goo points.


> > +++ b/blockdev.c
> > @@ -2261,24 +2257,8 @@ void qmp_block_passwd(bool has_device, const char *device,
> >                        bool has_node_name, const char *node_name,
> >                        const char *password, Error **errp)
> >  {
> > -    Error *local_err = NULL;
> > -    BlockDriverState *bs;
> > -    AioContext *aio_context;
> > -
> > -    bs = bdrv_lookup_bs(has_device ? device : NULL,
> > -                        has_node_name ? node_name : NULL,
> > -                        &local_err);
> > -    if (local_err) {
> > -        error_propagate(errp, local_err);
> > -        return;
> > -    }
> > -
> > -    aio_context = bdrv_get_aio_context(bs);
> > -    aio_context_acquire(aio_context);
> > -
> > -    bdrv_add_key(bs, password, errp);
> > -
> > -    aio_context_release(aio_context);
> > +    error_setg_errno(errp, -ENOSYS,
> > +                     "Setting block passwords directly is no longer supported");
> 
> We should document in qapi/block-core.json that the QMP 'block_passwd'
> command is deprecated, and will be removed in a future release (but I
> suspect we don't want to completely remove it in 2.6, so that we at
> least have time to find clients that were relying on it and should be
> using the new methods).

I thought that I had mentioned that already, but will do so
if its not already there.

> > +++ b/include/block/block_int.h
> > @@ -374,7 +374,6 @@ struct BlockDriverState {
> >      int read_only; /* if true, the media is read only */
> >      int open_flags; /* flags used to open the file, re-used for re-open */
> >      int encrypted; /* if true, the media is encrypted */
> > -    int valid_key; /* if true, a valid encryption key has been set */
> >      int sg;        /* if true, the device is a /dev/sg* */
> >      int copy_on_read; /* if true, copy read backing sectors into image
> >                           note this is a reference count */
> 
> Hmm - several variables that are only 'true' or 'false' and should be
> typed 'bool'.  Only 'encrypted' is a candidate for change in this patch;
> 'sg' is unrelated.  And 'copy_on_read' should be tweaked to document
> 'nonzero', not 'true', since it is used as a reference count rather than
> bool.

Yeah, these should all be changed separately really.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

* Re: [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption
  2016-02-08 21:26   ` Eric Blake
@ 2016-02-09 12:35     ` Daniel P. Berrange
  0 siblings, 0 replies; 69+ messages in thread
From: Daniel P. Berrange @ 2016-02-09 12:35 UTC (permalink / raw)
  To: Eric Blake; +Cc: Kevin Wolf, Fam Zheng, qemu-devel, qemu-block

On Mon, Feb 08, 2016 at 02:26:54PM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Refuse to use images with the legacy AES-CBC encryption
> > format in the system emulators. They are still fully
> > supported in the qemu-img, qemu-io & qemu-nbd tools in
> > order to allow data to be liberated and for compatibility
> > with older QEMU versions. Continued support in these tools
> > is not a notable burden with the new FDE framework.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> 
> > +++ b/block/qcow.c
> > @@ -180,6 +180,14 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
> >      s->crypt_method_header = header.crypt_method;
> >      if (s->crypt_method_header) {
> >          if (s->crypt_method_header == QCOW_CRYPT_AES) {
> > +            if (bdrv_uses_whitelist()) {
> > +                error_setg(errp,
> > +                           "Use of AES-CBC encrypted qcow images is no longer "
> > +                           "supported. Please use the qcow2 LUKS format instead.");
> 
> error_setg() should be a single phrase with no trailing punctuation, not
> two sentences.  Use error_append_hint() for the suggestion of the
> replacement.

Ah, I didn't know that method existed.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 69+ messages in thread

end of thread, other threads:[~2016-02-09 12:35 UTC | newest]

Thread overview: 69+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-20 17:38 [Qemu-devel] [PATCH v2 00/17] Support LUKS encryption in block devices Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 01/17] crypto: ensure qcrypto_hash_digest_len is always defined Daniel P. Berrange
2016-01-21  6:12   ` Fam Zheng
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 02/17] crypto: add cryptographic random byte source Daniel P. Berrange
2016-01-21  6:12   ` Fam Zheng
2016-01-21  8:59     ` Daniel P. Berrange
2016-02-04 17:44   ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 03/17] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
2016-01-21  6:59   ` Fam Zheng
2016-01-21 10:59     ` Daniel P. Berrange
2016-02-04 22:14   ` Eric Blake
2016-02-05  9:23     ` Daniel P. Berrange
2016-02-05 10:13     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 04/17] crypto: add support for generating initialization vectors Daniel P. Berrange
2016-01-21  7:51   ` Fam Zheng
2016-01-21 11:00     ` Daniel P. Berrange
2016-02-04 22:57   ` Eric Blake
2016-02-05 10:23     ` Daniel P. Berrange
2016-02-05 13:23       ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 05/17] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
2016-01-21  8:37   ` Fam Zheng
2016-01-21 11:01     ` Daniel P. Berrange
2016-02-04 23:26   ` Eric Blake
2016-02-05 12:37     ` Daniel P. Berrange
2016-02-05 12:39     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 06/17] crypto: add block encryption framework Daniel P. Berrange
2016-02-05  0:23   ` Eric Blake
2016-02-05 12:43     ` Daniel P. Berrange
2016-02-05 18:48       ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 07/17] crypto: implement the LUKS block encryption format Daniel P. Berrange
2016-02-05 17:38   ` Eric Blake
2016-02-08 16:03     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 08/17] block: add flag to indicate that no I/O will be performed Daniel P. Berrange
2016-02-05 19:08   ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 09/17] qemu-img/qemu-io: don't prompt for passwords if not required Daniel P. Berrange
2016-02-05 19:52   ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 10/17] block: add generic full disk encryption driver Daniel P. Berrange
2016-01-21  9:12   ` Fam Zheng
2016-01-21 11:02     ` Daniel P. Berrange
2016-01-21 13:01       ` Fam Zheng
2016-01-21 13:12         ` Daniel P. Berrange
2016-02-05 22:20   ` Eric Blake
2016-02-08 16:28     ` Daniel P. Berrange
2016-02-08 20:23       ` Eric Blake
2016-02-09  9:55         ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 11/17] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
2016-01-21  9:13   ` Fam Zheng
2016-02-05 23:22   ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 12/17] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
2016-01-21  9:54   ` Fam Zheng
2016-01-21 10:50     ` Daniel P. Berrange
2016-01-21 13:56       ` Fam Zheng
2016-01-21 14:03         ` Daniel P. Berrange
2016-02-08 18:12   ` Eric Blake
2016-02-09 12:32     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 13/17] qcow: make encrypt_sectors encrypt in place Daniel P. Berrange
2016-02-08 20:30   ` Eric Blake
2016-02-09 12:33     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 14/17] qcow: convert QCow to use QCryptoBlock for encryption Daniel P. Berrange
2016-02-08 20:57   ` Eric Blake
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 15/17] block: rip out all traces of password prompting Daniel P. Berrange
2016-01-21 13:02   ` Fam Zheng
2016-01-21 13:11     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 16/17] block: remove all encryption handling APIs Daniel P. Berrange
2016-02-08 21:23   ` Eric Blake
2016-02-09 12:34     ` Daniel P. Berrange
2016-01-20 17:38 ` [Qemu-devel] [PATCH v2 17/17] block: remove support for legecy AES qcow/qcow2 encryption Daniel P. Berrange
2016-02-08 21:26   ` Eric Blake
2016-02-09 12:35     ` Daniel P. Berrange

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).