All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption
@ 2015-11-20 18:04 Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling Daniel P. Berrange
                   ` (29 more replies)
  0 siblings, 30 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

This Work-In-Progress series introduces a framework for full disk
encryption, and implements the LUKS encryption format using it.
It provides a new block driver that can use this directly, but
also aims to integrate it natively into the qcow2 file format
(more on that later).

For convenience, this series includes of the patches I've
previously sent wrt introducing a secure framework for passing
secrets (passwords, keys, etc) to QEMU:

  https://lists.gnu.org/archive/html/qemu-devel/2015-10/msg04365.html

Patches 1-> 15 are the basic secret handling related patches

Patches 16-25 are preparatory work in the crypto codebase.
This is some bug fixing, some QAPI-ification, and some new
support modules for generating initialization vectors,
the PBKDF2 algorithm and LUKS anti-forensic split algorithm.

Patches 26 & 27 provide the full disk encryption framework and
LUKS implementation respectively.

Finally, patch 28 provides a general purpose encrypted block
driver, that can be used in any place you'd currently use dm-crypt
with QEMU), and 29/30 start to extend QCow2 to support LUKS as
an embedded encryption method.  Patch 28 quickly illustrates
the qemu-img commands for dealing with encrypted volumes.

The block/fde.c driver is a fairly clean example of how to
make use of the full disk encryption framework to implement
a block driver. So a good place to look. The crypto/block-luks.c
file is the actual LUKS implementation. Its creation code will
generate volumes with pretty much the same parameters as the
reference cryptsetup impl will.

The QCow2 support is non-functional, because I hit a small
problem. The QCow2 header and all header extensions appear to
need to fit into the first cluster. The LUKS header is typically
set to be 2 MB in size, which is clearly not going to fit. The
actual QCow2 header appears to include offsets for all the
various data tables, so it /appears/ that the first cluster
limit for headers is just an implementation limit, not a
specification limit. Assuming that's correct, I'd aim to
extend the permitted size of the header to be much larger.

The main limitation of the LUKS impl is that it currently
lacks support for the XTS cipher mode, so only CBC can be
used. This is because I'm yet to find any attractive impl
of XTS that QEMU can reuse, as either gcrypt/nettle provide
it yet. TBD...

Daniel P. Berrange (30):
  crypto: add QCryptoSecret object class for password/key handling
  crypto: add support for loading encrypted x509 keys
  qcow: add a 'keyid' parameter to qcow options
  qcow2: add a 'keyid' parameter to qcow2 options
  qom: add user_creatable_add & user_creatable_del methods
  qemu-img: add support for --object command line arg
  qemu-nbd: add support for --object command line arg
  qemu-io: add support for --object command line arg
  qemu-io: allow specifying image as a set of options args
  qemu-nbd: allow specifying image as a set of options args
  qemu-img: allow specifying image as a set of options args
  block: rip out all traces of password prompting
  block: remove all encryption handling APIs
  block: remove support for writing to qcow/qcow2 encrypted images
  qcow2: make qcow2_encrypt_sectors encrypt in place
  crypto: add ability to query the cipher key, block & IV lens
  crypto: add method for querying hash digest size
  crypto: move QCryptoHashAlgorithm enum definition into QAPI
  crypto: move QCryptoCipherAlgorithm/Mode enum definitions into QAPI
  crypto: ensure qapi/crypto.json is listed in qapi-modules
  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: fix transposed arguments in cipher error message
  crypto: add block encryption framework
  crypto: implement the LUKS block encryption format
  block: add generic full disk encryption driver
  qcow2: convert QCow2 to use QCryptoBlock for encryption
  qcow2: add LUKS full disk encryption support

 Makefile                        |    3 +-
 block.c                         |   88 +---
 block/Makefile.objs             |    2 +
 block/fde.c                     |  522 +++++++++++++++++++
 block/qapi.c                    |    2 +-
 block/qcow.c                    |  122 +++--
 block/qcow2-cluster.c           |   55 +-
 block/qcow2.c                   |  403 ++++++++++++---
 block/qcow2.h                   |   18 +-
 blockdev.c                      |   40 +-
 crypto/Makefile.objs            |   11 +
 crypto/afsplit.c                |  194 +++++++
 crypto/block-luks.c             | 1056 +++++++++++++++++++++++++++++++++++++++
 crypto/block-luks.h             |   28 ++
 crypto/block-qcowaes.c          |  150 ++++++
 crypto/block-qcowaes.h          |   28 ++
 crypto/block.c                  |  253 ++++++++++
 crypto/blockpriv.h              |   87 ++++
 crypto/cipher.c                 |   54 +-
 crypto/hash.c                   |   17 +-
 crypto/ivgen-essiv.c            |  116 +++++
 crypto/ivgen-essiv.h            |   28 ++
 crypto/ivgen-plain.c            |   60 +++
 crypto/ivgen-plain.h            |   28 ++
 crypto/ivgen-plain64.c          |   60 +++
 crypto/ivgen-plain64.h          |   28 ++
 crypto/ivgen.c                  |  117 +++++
 crypto/ivgenpriv.h              |   47 ++
 crypto/pbkdf-gcrypt.c           |   64 +++
 crypto/pbkdf-nettle.c           |   63 +++
 crypto/pbkdf-stub.c             |   39 ++
 crypto/pbkdf.c                  |   76 +++
 crypto/random.c                 |   50 ++
 crypto/secret.c                 |  567 +++++++++++++++++++++
 crypto/tlscredsx509.c           |   47 ++
 docs/specs/qcow2.txt            |   53 ++
 hmp.c                           |   42 +-
 hw/usb/dev-storage.c            |   34 --
 include/block/block.h           |    5 +-
 include/crypto/afsplit.h        |  133 +++++
 include/crypto/block.h          |  199 ++++++++
 include/crypto/cipher.h         |   54 +-
 include/crypto/hash.h           |   20 +-
 include/crypto/ivgen.h          |  167 +++++++
 include/crypto/pbkdf.h          |  152 ++++++
 include/crypto/random.h         |   43 ++
 include/crypto/secret.h         |  148 ++++++
 include/crypto/tlscredsx509.h   |    1 +
 include/monitor/monitor.h       |   10 -
 include/qemu/option.h           |    1 +
 include/qemu/osdep.h            |    2 -
 include/qom/object_interfaces.h |   31 ++
 monitor.c                       |   69 ---
 qapi/block-core.json            |   23 +-
 qapi/crypto.json                |  173 +++++++
 qemu-img-cmds.hx                |   44 +-
 qemu-img.c                      |  788 +++++++++++++++++++++++++----
 qemu-img.texi                   |    8 +
 qemu-io.c                       |  145 +++++-
 qemu-nbd.c                      |  142 +++++-
 qemu-nbd.texi                   |    7 +
 qemu-options.hx                 |   86 +++-
 qmp.c                           |   84 +---
 qom/object_interfaces.c         |   76 +++
 tests/.gitignore                |    4 +
 tests/Makefile                  |    8 +
 tests/qemu-iotests/087          |   20 +
 tests/qemu-iotests/087.out      |   26 +-
 tests/qemu-iotests/134          |   17 +-
 tests/qemu-iotests/134.out      |   44 +-
 tests/qemu-iotests/common.rc    |    4 +-
 tests/test-crypto-afsplit.c     |  176 +++++++
 tests/test-crypto-cipher.c      |   10 +
 tests/test-crypto-hash.c        |    5 +
 tests/test-crypto-ivgen.c       |  168 +++++++
 tests/test-crypto-pbkdf.c       |  378 ++++++++++++++
 tests/test-crypto-secret.c      |  446 +++++++++++++++++
 util/oslib-posix.c              |   66 ---
 util/oslib-win32.c              |   24 -
 util/qemu-option.c              |    6 +
 vl.c                            |    8 +-
 81 files changed, 7829 insertions(+), 844 deletions(-)
 create mode 100644 block/fde.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-qcowaes.c
 create mode 100644 crypto/block-qcowaes.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.c
 create mode 100644 crypto/secret.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 include/crypto/secret.h
 create mode 100644 tests/test-crypto-afsplit.c
 create mode 100644 tests/test-crypto-ivgen.c
 create mode 100644 tests/test-crypto-pbkdf.c
 create mode 100644 tests/test-crypto-secret.c

-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 22:09   ` Eric Blake
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 02/30] crypto: add support for loading encrypted x509 keys Daniel P. Berrange
                   ` (28 subsequent siblings)
  29 siblings, 1 reply; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Introduce a new QCryptoSecret object class which will be used
for providing passwords and keys to other objects which need
sensitive credentials.

The new object can provide secret values directly as properties,
or indirectly via a file. The latter includes support for file
descriptor passing syntax on UNIX platforms. Ordinarily passing
secret values directly as properties is insecure, since they
are visible in process listings, or in log files showing the
CLI args / QMP commands. It is possible to use AES-256-CBC to
encrypt the secret values though, in which case all that is
visible is the ciphertext.  For adhoc developer testing though,
it is fine to provide the secrets directly without encryption
so this is not explicitly forbidden.

The anticipated scenario is that libvirtd will create a random
master key per QEMU instance (eg /var/run/libvirt/qemu/$VMNAME.key)
and will use that key to encrypt all passwords it provides to
QEMU via '-object secret,....'.  This avoids the need for libvirt
(or other mgmt apps) to worry about file descriptor passing.

It also makes life easier for people who are scripting the
management of QEMU, for whom FD passing is significantly more
complex.

Providing data inline (insecure, only for adhoc dev tetsing)

  $QEMU -object secret,id=sec0,data=letmein

Providing data indirectly in raw format

  echo -n "letmein" > mypasswd.txt
  $QEMU -object secret,id=sec0,file=mypasswd.txt

Providing data indirectly in base64 format

  $QEMU -object secret,id=sec0,file=mykey.b64,format=base64

Providing data with encyption

  $QEMU -object secret,id=master0,file=mykey.b64,format=base64 \
        -object secret,id=sec0,data=[base64 ciphertext],\
	           keyid=master0,iv=[base64 IV],format=base64

Note that 'format' here refers to the format of the ciphertext
data. The decrypted data must always be in raw byte format.

More examples are shown in the updated docs.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs       |   1 +
 crypto/secret.c            | 567 +++++++++++++++++++++++++++++++++++++++++++++
 include/crypto/secret.h    | 148 ++++++++++++
 qapi/crypto.json           |  14 ++
 qemu-options.hx            |  78 +++++++
 tests/.gitignore           |   1 +
 tests/Makefile             |   2 +
 tests/test-crypto-secret.c | 446 +++++++++++++++++++++++++++++++++++
 8 files changed, 1257 insertions(+)
 create mode 100644 crypto/secret.c
 create mode 100644 include/crypto/secret.h
 create mode 100644 tests/test-crypto-secret.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index b2a0e0b..a3135f1 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -7,6 +7,7 @@ crypto-obj-y += tlscreds.o
 crypto-obj-y += tlscredsanon.o
 crypto-obj-y += tlscredsx509.o
 crypto-obj-y += tlssession.o
+crypto-obj-y += secret.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/secret.c b/crypto/secret.c
new file mode 100644
index 0000000..c34d46d
--- /dev/null
+++ b/crypto/secret.c
@@ -0,0 +1,567 @@
+/*
+ * QEMU crypto secret support
+ *
+ * Copyright (c) 2015 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/secret.h"
+#include "crypto/cipher.h"
+#include "qom/object_interfaces.h"
+#include "trace.h"
+
+static const char *base64_valid_chars =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+static int
+qcrypto_secret_validate_base64(const uint8_t *input,
+                               size_t inputlen,
+                               Error **errp)
+{
+    if (input[inputlen] != '\0') {
+        error_setg(errp, "Base64 data is not NULL terminated");
+        return -1;
+    }
+    /* Check there's no NULs embedded since we expect
+     * this to be base64 data */
+    if (memchr(input, '\0', inputlen) != NULL) {
+        error_setg(errp, "Base64 data contains embedded NUL characters");
+        return -1;
+    }
+
+    if (strspn((const char *)input, base64_valid_chars) != inputlen) {
+        error_setg(errp, "Base64 data contains unexpected characters '%s'",
+                   input);
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+qcrypto_secret_load_data(QCryptoSecret *secret,
+                         uint8_t **output,
+                         size_t *outputlen,
+                         Error **errp)
+{
+    int fd;
+    char *data = NULL;
+    size_t offset = 0;
+    size_t length = 0;
+
+    *output = NULL;
+    *outputlen = 0;
+
+    if (secret->file) {
+        if (secret->data) {
+            error_setg(errp,
+                       "'file' and 'data' are mutually exclusive");
+            return;
+        }
+        fd = qemu_open(secret->file, O_RDONLY);
+        if (fd < 0) {
+            error_setg_errno(errp, errno,
+                             "Unable to open %s", secret->file);
+            return;
+        }
+        while (length < (1024 * 1024)) { /* Limit secrets to 1 MB */
+            if ((length - offset) < 1024) {
+                length += 1024;
+                data = g_renew(char, data, length);
+            }
+            ssize_t ret = read(fd, data + offset, length - offset);
+            if (ret == 0) {
+                break;
+            }
+            if (ret < 0) {
+                error_setg_errno(errp, errno,
+                                 "Unable to read from %s", secret->file);
+                close(fd);
+                g_free(data);
+                return;
+            }
+            offset += ret;
+        }
+        close(fd);
+        if (offset) {
+            /* Even though data is raw 8-bit, so may contain
+             * arbitrary NULs, ensure it is explicitly NUL
+             * terminated */
+            *output = g_renew(uint8_t, data, offset + 1);
+            (*output)[offset] = '\0';
+            *outputlen = offset;
+        } else {
+            error_setg(errp, "Secret file %s is empty",
+                       secret->file);
+            g_free(data);
+        }
+    } else if (secret->data) {
+        *outputlen = strlen(secret->data);
+        *output = g_new(uint8_t, *outputlen + 1);
+        memcpy(*output, secret->data, *outputlen + 1);
+    } else {
+        error_setg(errp, "Either 'file' or 'data' must be provided");
+    }
+}
+
+
+static void qcrypto_secret_decrypt(QCryptoSecret *secret,
+                                   const uint8_t *input,
+                                   size_t inputlen,
+                                   uint8_t **output,
+                                   size_t *outputlen,
+                                   Error **errp)
+{
+    uint8_t *key = NULL, *ciphertext = NULL, *iv = NULL;
+    size_t keylen, ciphertextlen, ivlen;
+    QCryptoCipher *aes = NULL;
+    uint8_t *plaintext = NULL;
+
+    *output = NULL;
+    *outputlen = 0;
+
+    if (qcrypto_secret_lookup(secret->keyid,
+                              &key, &keylen,
+                              errp) < 0) {
+        goto cleanup;
+    }
+
+    if (keylen != 32) {
+        error_setg(errp, "Key should be 32 bytes in length");
+        goto cleanup;
+    }
+
+    if (!secret->iv) {
+        error_setg(errp, "IV is required to decrypt secret");
+        goto cleanup;
+    }
+
+    iv = (uint8_t *)g_base64_decode(secret->iv, &ivlen);
+    if (ivlen != 16) {
+        error_setg(errp, "IV should be 16 bytes in length not %zu",
+                   ivlen);
+        goto cleanup;
+    }
+
+    aes = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES_256,
+                             QCRYPTO_CIPHER_MODE_CBC,
+                             key, keylen,
+                             errp);
+    if (!aes) {
+        goto cleanup;
+    }
+
+    if (qcrypto_cipher_setiv(aes, iv, ivlen, errp) < 0) {
+        goto cleanup;
+    }
+
+    if (secret->format == QCRYPTO_SECRET_FORMAT_BASE64) {
+        if (qcrypto_secret_validate_base64(input, inputlen, errp) < 0) {
+            goto cleanup;
+        }
+
+        ciphertext = g_base64_decode((const gchar*)input, &ciphertextlen);
+        plaintext = g_new0(uint8_t, ciphertextlen + 1);
+    } else {
+        ciphertextlen = inputlen;
+        plaintext = g_new0(uint8_t, inputlen + 1);
+    }
+    if (qcrypto_cipher_decrypt(aes,
+                               ciphertext ? ciphertext : input,
+                               plaintext,
+                               ciphertextlen,
+                               errp) < 0) {
+        plaintext = NULL;
+        goto cleanup;
+    }
+
+    if (plaintext[ciphertextlen - 1] > 16 ||
+        plaintext[ciphertextlen - 1] > ciphertextlen) {
+        error_setg(errp, "Incorrect number of padding bytes (%d) "
+                   "found on decrypted data",
+                   (int)plaintext[ciphertextlen - 1]);
+        g_free(plaintext);
+        plaintext = NULL;
+        goto cleanup;
+    }
+
+    /* Even though plaintext may contain arbitrary NUL
+     * ensure it is explicitly NUL terminated.
+     */
+    ciphertextlen -= plaintext[ciphertextlen - 1];
+    plaintext[ciphertextlen] = '\0';
+
+    *output = plaintext;
+    *outputlen = ciphertextlen;
+
+ cleanup:
+    g_free(ciphertext);
+    g_free(iv);
+    g_free(key);
+    qcrypto_cipher_free(aes);
+}
+
+
+static void qcrypto_secret_decode(const uint8_t *input,
+                                  size_t inputlen,
+                                  uint8_t **output,
+                                  size_t *outputlen,
+                                  Error **errp)
+{
+    /* Check there's no NULs embedded since we expect
+     * this to be base64 data */
+    if (qcrypto_secret_validate_base64(input, inputlen, errp) < 0) {
+        return;
+    }
+
+    *output = (uint8_t *)g_base64_decode((const gchar*)input, outputlen);
+}
+
+
+static void
+qcrypto_secret_prop_set_loaded(Object *obj,
+                               bool value,
+                               Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    if (value) {
+        Error *local_err = NULL;
+        uint8_t *input = NULL;
+        size_t inputlen = 0;
+        uint8_t *output = NULL;
+        size_t outputlen = 0;
+
+        qcrypto_secret_load_data(secret, &input, &inputlen, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            return;
+        }
+
+        if (secret->keyid) {
+            qcrypto_secret_decrypt(secret, input, inputlen,
+                                   &output, &outputlen, &local_err);
+            g_free(input);
+            if (local_err) {
+                error_propagate(errp, local_err);
+                return;
+            }
+            input = output;
+            inputlen = outputlen;
+        } else {
+            if (secret->format != QCRYPTO_SECRET_FORMAT_RAW) {
+                qcrypto_secret_decode(input, inputlen,
+                                      &output, &outputlen, &local_err);
+                g_free(input);
+                if (local_err) {
+                    error_propagate(errp, local_err);
+                    return;
+                }
+                input = output;
+                inputlen = outputlen;
+            }
+        }
+
+        secret->rawdata = input;
+        secret->rawlen = inputlen;
+    } else {
+        g_free(secret->rawdata);
+        secret->rawlen = 0;
+    }
+}
+
+
+static bool
+qcrypto_secret_prop_get_loaded(Object *obj,
+                               Error **errp G_GNUC_UNUSED)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+    return secret->data != NULL;
+}
+
+
+static void
+qcrypto_secret_prop_set_format(Object *obj,
+                               int value,
+                               Error **errp G_GNUC_UNUSED)
+{
+    QCryptoSecret *creds = QCRYPTO_SECRET(obj);
+
+    creds->format = value;
+}
+
+
+static int
+qcrypto_secret_prop_get_format(Object *obj,
+                               Error **errp G_GNUC_UNUSED)
+{
+    QCryptoSecret *creds = QCRYPTO_SECRET(obj);
+
+    return creds->format;
+}
+
+
+static void
+qcrypto_secret_prop_set_data(Object *obj,
+                             const char *value,
+                             Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    g_free(secret->data);
+    secret->data = g_strdup(value);
+}
+
+
+static char *
+qcrypto_secret_prop_get_data(Object *obj,
+                             Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+    return g_strdup(secret->data);
+}
+
+
+static void
+qcrypto_secret_prop_set_file(Object *obj,
+                             const char *value,
+                             Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    g_free(secret->file);
+    secret->file = g_strdup(value);
+}
+
+
+static char *
+qcrypto_secret_prop_get_file(Object *obj,
+                             Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+    return g_strdup(secret->file);
+}
+
+
+static void
+qcrypto_secret_prop_set_iv(Object *obj,
+                           const char *value,
+                           Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    g_free(secret->iv);
+    secret->iv = g_strdup(value);
+}
+
+
+static char *
+qcrypto_secret_prop_get_iv(Object *obj,
+                           Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+    return g_strdup(secret->iv);
+}
+
+
+static void
+qcrypto_secret_prop_set_keyid(Object *obj,
+                              const char *value,
+                              Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    g_free(secret->keyid);
+    secret->keyid = g_strdup(value);
+}
+
+
+static char *
+qcrypto_secret_prop_get_keyid(Object *obj,
+                              Error **errp)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+    return g_strdup(secret->keyid);
+}
+
+
+static void
+qcrypto_secret_complete(UserCreatable *uc, Error **errp)
+{
+    object_property_set_bool(OBJECT(uc), true, "loaded", errp);
+}
+
+
+static void
+qcrypto_secret_init(Object *obj)
+{
+    object_property_add_bool(obj, "loaded",
+                             qcrypto_secret_prop_get_loaded,
+                             qcrypto_secret_prop_set_loaded,
+                             NULL);
+    object_property_add_enum(obj, "format",
+                             "QCryptoSecretFormat",
+                             QCryptoSecretFormat_lookup,
+                             qcrypto_secret_prop_get_format,
+                             qcrypto_secret_prop_set_format,
+                             NULL);
+    object_property_add_str(obj, "data",
+                            qcrypto_secret_prop_get_data,
+                            qcrypto_secret_prop_set_data,
+                            NULL);
+    object_property_add_str(obj, "file",
+                            qcrypto_secret_prop_get_file,
+                            qcrypto_secret_prop_set_file,
+                            NULL);
+    object_property_add_str(obj, "keyid",
+                            qcrypto_secret_prop_get_keyid,
+                            qcrypto_secret_prop_set_keyid,
+                            NULL);
+    object_property_add_str(obj, "iv",
+                            qcrypto_secret_prop_get_iv,
+                            qcrypto_secret_prop_set_iv,
+                            NULL);
+}
+
+
+static void
+qcrypto_secret_finalize(Object *obj)
+{
+    QCryptoSecret *secret = QCRYPTO_SECRET(obj);
+
+    g_free(secret->iv);
+    g_free(secret->file);
+    g_free(secret->keyid);
+    g_free(secret->rawdata);
+    g_free(secret->data);
+}
+
+static void
+qcrypto_secret_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+    ucc->complete = qcrypto_secret_complete;
+}
+
+
+int qcrypto_secret_lookup(const char *secretid,
+                          uint8_t **data,
+                          size_t *datalen,
+                          Error **errp)
+{
+    Object *obj;
+    QCryptoSecret *secret;
+
+    obj = object_resolve_path_component(
+        object_get_objects_root(), secretid);
+    if (!obj) {
+        error_setg(errp, "No secret with id '%s'", secretid);
+        return -1;
+    }
+
+    secret = (QCryptoSecret *)
+        object_dynamic_cast(obj,
+                            TYPE_QCRYPTO_SECRET);
+    if (!secret) {
+        error_setg(errp, "Object with id '%s' is not a secret",
+                   secretid);
+        return -1;
+    }
+
+    if (!secret->rawdata) {
+        error_setg(errp, "Secret with id '%s' has no data",
+                   secretid);
+        return -1;
+    }
+
+    *data = g_new0(uint8, secret->rawlen + 1);
+    memcpy(*data, secret->rawdata, secret->rawlen);
+    (*data)[secret->rawlen] = '\0';
+    *datalen = secret->rawlen;
+
+    return 0;
+}
+
+
+char *qcrypto_secret_lookup_as_utf8(const char *secretid,
+                                    Error **errp)
+{
+    uint8_t *data;
+    size_t datalen;
+
+    if (qcrypto_secret_lookup(secretid,
+                              &data,
+                              &datalen,
+                              errp) < 0) {
+        return NULL;
+    }
+
+    if (!g_utf8_validate((const gchar*)data, datalen, NULL)) {
+        error_setg(errp,
+                   "Data from secret %s is not valid UTF-8",
+                   secretid);
+        g_free(data);
+        return NULL;
+    }
+
+    return (char *)data;
+}
+
+
+char *qcrypto_secret_lookup_as_base64(const char *secretid,
+                                      Error **errp)
+{
+    uint8_t *data;
+    size_t datalen;
+    char *ret;
+
+    if (qcrypto_secret_lookup(secretid,
+                              &data,
+                              &datalen,
+                              errp) < 0) {
+        return NULL;
+    }
+
+    ret = g_base64_encode(data, datalen);
+    g_free(data);
+    return ret;
+}
+
+
+static const TypeInfo qcrypto_secret_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_QCRYPTO_SECRET,
+    .instance_size = sizeof(QCryptoSecret),
+    .instance_init = qcrypto_secret_init,
+    .instance_finalize = qcrypto_secret_finalize,
+    .class_size = sizeof(QCryptoSecretClass),
+    .class_init = qcrypto_secret_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+
+static void
+qcrypto_secret_register_types(void)
+{
+    type_register_static(&qcrypto_secret_info);
+}
+
+
+type_init(qcrypto_secret_register_types);
diff --git a/include/crypto/secret.h b/include/crypto/secret.h
new file mode 100644
index 0000000..55c7d88
--- /dev/null
+++ b/include/crypto/secret.h
@@ -0,0 +1,148 @@
+/*
+ * QEMU crypto secret support
+ *
+ * Copyright (c) 2015 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_SECRET_H__
+#define QCRYPTO_SECRET_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+#define TYPE_QCRYPTO_SECRET "secret"
+#define QCRYPTO_SECRET(obj)                  \
+    OBJECT_CHECK(QCryptoSecret, (obj), TYPE_QCRYPTO_SECRET)
+
+typedef struct QCryptoSecret QCryptoSecret;
+typedef struct QCryptoSecretClass QCryptoSecretClass;
+
+/**
+ * QCryptoSecret:
+ *
+ * The QCryptoSecret object provides storage of secrets,
+ * which may be user passwords, encryption keys or any
+ * other kind of sensitive data that is represented as
+ * a sequence of bytes.
+ *
+ * The sensitive data associated with the secret can
+ * be provided directly via the 'data' property, or
+ * indirectly via the 'file' property. In the latter
+ * case there is support for file descriptor passing
+ * via the usual /dev/fdset/NN syntax that QEMU uses.
+ *
+ * The data for a secret can be provided in two formats,
+ * either as a UTF-8 string (the default), or as base64
+ * encoded 8-bit binary data. The latter is appropriate
+ * for  raw encrypton keys, while the former is appropriate
+ * for user entered passwords.
+ *
+ * The data may be optionally encrypted with AES-256-CBC,
+ * and the decryption key provided by another
+ * QCryptoSecret instance identified by the 'keyid'
+ * property. When passing sensitive data directly
+ * via the 'data' property it is strongly recommended
+ * to use the AES encryption facility to prevent the
+ * sensitive data being exposed in the process listing
+ * or system log files.
+ *
+ * Providing data directly, insecurely (suitable for
+ * adhoc developer testing only)
+ *
+ *  $QEMU -object secret,id=sec0,data=letmein
+ *
+ * Providing data indirectly:
+ *
+ *  # echo -n "letmein" > password.txt
+ *  # $QEMU \
+ *      -object secret,id=sec0,file=password.txt
+ *
+ * Using a master encryption key with data.
+ *
+ * The master key needs to be created as 32 secure
+ * random bytes (optionally base64 encoded)
+ *
+ *  # openssl rand -base64 32 > key.b64
+ *  # KEY=$(base64 -d key.b64 | hexdump  -v -e '/1 "%02X"')
+ *
+ * Each secret to be encrypted needs to have a random
+ * initialization vector generated. These do not need
+ * to be kept secret
+ *
+ *  # openssl rand -base64 16 > iv.b64
+ *  # IV=$(base64 -d iv.b64 | hexdump  -v -e '/1 "%02X"')
+ *
+ * A secret to be defined can now be encrypted
+ *
+ *  # SECRET=$(echo -n "letmein" |
+ *             openssl enc -aes-256-cbc -a -K $KEY -iv $IV)
+ *
+ * When launching QEMU, create a master secret pointing
+ * to key.b64 and specify that to be used to decrypt
+ * the user password
+ *
+ *  # $QEMU \
+ *      -object secret,id=secmaster0,format=base64,file=key.b64 \
+ *      -object secret,id=sec0,keyid=secmaster0,format=base64,\
+ *          data=$SECRET,iv=$(<iv.b64)
+ *
+ * When encrypting, the data can still be provided via an
+ * external file, in which case it is possible to use either
+ * raw binary data, or base64 encoded. This example uses
+ * raw format
+ *
+ *  # echo -n "letmein" |
+ *       openssl enc -aes-256-cbc -K $KEY -iv $IV -o pw.aes
+ *  # $QEMU \
+ *      -object secret,id=secmaster0,format=base64,file=key.b64 \
+ *      -object secret,id=sec0,keyid=secmaster0,\
+ *          file=pw.aes,iv=$(<iv.b64)
+ *
+ * Note that the ciphertext can be in either raw or base64
+ * format, as indicated by the 'format' parameter, but the
+ * plaintext resulting from decryption is expected to always
+ * be in raw format.
+ */
+
+struct QCryptoSecret {
+    Object parent_obj;
+    uint8_t *rawdata;
+    size_t rawlen;
+    QCryptoSecretFormat format;
+    char *data;
+    char *file;
+    char *keyid;
+    char *iv;
+};
+
+
+struct QCryptoSecretClass {
+    ObjectClass parent_class;
+};
+
+
+extern int qcrypto_secret_lookup(const char *secretid,
+                                 uint8_t **data,
+                                 size_t *datalen,
+                                 Error **errp);
+extern char *qcrypto_secret_lookup_as_utf8(const char *secretid,
+                                           Error **errp);
+extern char *qcrypto_secret_lookup_as_base64(const char *secretid,
+                                             Error **errp);
+
+#endif /* QCRYPTO_SECRET_H__ */
diff --git a/qapi/crypto.json b/qapi/crypto.json
index b058b14..88d9ead 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -19,3 +19,17 @@
 { 'enum': 'QCryptoTLSCredsEndpoint',
   'prefix': 'QCRYPTO_TLS_CREDS_ENDPOINT',
   'data': ['client', 'server']}
+
+
+##
+# QCryptoSecretFormat:
+#
+# The data format that the secret is provided in
+#
+# @raw: raw bytes. When encoded in JSON only valid UTF-8 sequences can be used
+# @base64: arbitrary base64 encoded binary data
+# Since: 2.5
+##
+{ 'enum': 'QCryptoSecretFormat',
+  'prefix': 'QCRYPTO_SECRET_FORMAT',
+  'data': ['raw', 'base64']}
diff --git a/qemu-options.hx b/qemu-options.hx
index 0eea4ee..dd3f7f8 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3670,6 +3670,7 @@ queue @var{all|rx|tx} is an option that can be applied to any netfilter.
 @option{tx}: the filter is attached to the transmit queue of the netdev,
              where it will receive packets sent by the netdev.
 
+
 @item -object filter-dump,id=@var{id},netdev=@var{dev},file=@var{filename}][,maxlen=@var{len}]
 
 Dump the network traffic on netdev @var{dev} to the file specified by
@@ -3677,6 +3678,83 @@ Dump the network traffic on netdev @var{dev} to the file specified by
 The file format is libpcap, so it can be analyzed with tools such as tcpdump
 or Wireshark.
 
+@item -object secret,id=@var{id},data=@var{string},format=@var{raw|base64}[,keyid=@var{secretid},iv=@var{string}]
+@item -object secret,id=@var{id},file=@var{filename},format=@var{raw|base64}[,keyid=@var{secretid},iv=@var{string}]
+
+Defines a secret to store a password, encryption key, or some other sensitive
+data. The senstive data can either be passed directly via the @var{data}
+parameter, or indirectly via the @var{file} parameter. Using the @var{data}
+parameter is insecure unless the sensitive data is encrypted.
+
+The sensitive data can be provided in raw format (the default), or base64.
+When encoded as JSON, the raw format only supports valid UTF-8 characters,
+so base64 is recommended for sending binary data. QEMU will convert from
+which ever format is provided to the format it needs internally. eg, an
+RBD password can be provided in raw format, even though it will be base64
+encoded when passed onto the RBD sever.
+
+For added protection, it is possible to encrypt the data associated with
+a secret using the AES-256-CBC cipher. Use of encryption is indicated
+by providing the @var{keyid} and @var{iv} parameters. The @var{keyid}
+parameter provides the ID of a previously defined secret that contains
+the AES-256 decryption key. This key should be 32-bytes long and be
+base64 encoded. The @var{iv} parameter provides the random initialization
+vector used for encryption of this particular secret and should be a
+base64 encrypted string of the 32-byte IV.
+
+The simplest (insecure) usage is to provide the secret inline
+
+@example
+
+ # $QEMU -object secret,id=sec0,data=letmein,format=raw
+
+@end example
+
+The simplest secure usage is to provide the secret via a file
+
+ # echo -n "letmein" > mypasswd.txt
+ # $QEMU -object secret,id=sec0,file=mypasswd.txt,format=raw
+
+For greater security, AES-256-CBC should be used. To illustrate usage,
+consider the openssl command line tool which can encrypt the data. Note
+that when encrypting, the plaintext must be padded to the cipher block
+size (32 bytes) using the standard PKCS#5/6 compatible padding algorithm.
+
+First a master key needs to be created in base64 encoding:
+
+@example
+ # openssl rand -base64 32 > key.b64
+ # KEY=$(base64 -d key.b64 | hexdump  -v -e '/1 "%02X"')
+@end example
+
+Each secret to be encrypted needs to have a random initialization vector
+generated. These do not need to be kept secret
+
+@example
+ # openssl rand -base64 16 > iv.b64
+ # IV=$(base64 -d iv.b64 | hexdump  -v -e '/1 "%02X"')
+@end example
+
+The secret to be defined can now be encrypted, in this case we're
+telling openssl to base64 encode the result, but it could be left
+as raw bytes if desired.
+
+@example
+ # SECRET=$(echo -n "letmein" |
+            openssl enc -aes-256-cbc -a -K $KEY -iv $IV)
+@end example
+
+When launching QEMU, create a master secret pointing to @code{key.b64}
+and specify that to be used to decrypt the user password. Pass the
+contents of @code{iv.b64} to the second secret
+
+@example
+ # $QEMU \
+     -object secret,id=secmaster0,format=base64,file=key.b64 \
+     -object secret,id=sec0,keyid=secmaster0,format=base64,\
+         data=$SECRET,iv=$(<iv.b64)
+@end example
+
 @end table
 
 ETEXI
diff --git a/tests/.gitignore b/tests/.gitignore
index 1e55722..6cc8efe 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,6 +13,7 @@ test-blockjob-txn
 test-coroutine
 test-crypto-cipher
 test-crypto-hash
+test-crypto-secret
 test-crypto-tlscredsx509
 test-crypto-tlscredsx509-work/
 test-crypto-tlscredsx509-certs/
diff --git a/tests/Makefile b/tests/Makefile
index 90c4141..72265e8 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -80,6 +80,7 @@ check-unit-y += tests/test-write-threshold$(EXESUF)
 gcov-files-test-write-threshold-y = block/write-threshold.c
 check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
 check-unit-y += tests/test-crypto-cipher$(EXESUF)
+check-unit-y += tests/test-crypto-secret$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
@@ -457,6 +458,7 @@ tests/test-mul64$(EXESUF): tests/test-mul64.o $(test-util-obj-y)
 tests/test-bitops$(EXESUF): tests/test-bitops.o $(test-util-obj-y)
 tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(test-crypto-obj-y)
 tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(test-crypto-obj-y)
+tests/test-crypto-secret$(EXESUF): tests/test-crypto-secret.o $(test-crypto-obj-y)
 
 tests/crypto-tls-x509-helpers.o-cflags := $(TASN1_CFLAGS)
 tests/crypto-tls-x509-helpers.o-libs := $(TASN1_LIBS)
diff --git a/tests/test-crypto-secret.c b/tests/test-crypto-secret.c
new file mode 100644
index 0000000..6743608
--- /dev/null
+++ b/tests/test-crypto-secret.c
@@ -0,0 +1,446 @@
+/*
+ * QEMU Crypto secret handling
+ *
+ * Copyright (c) 2015 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/secret.h"
+
+static void test_secret_direct(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "123456",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    g_free(pw);
+}
+
+
+static void test_secret_indirect_good(void)
+{
+    Object *sec;
+    char *fname = NULL;
+    int fd = g_file_open_tmp("secretXXXXXX",
+                             &fname,
+                             NULL);
+
+    g_assert(fd >= 0);
+    g_assert_nonnull(fname);
+
+    g_assert(write(fd, "123456", 6) == 6);
+
+    sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "file", fname,
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    g_free(pw);
+    close(fd);
+    g_free(fname);
+}
+
+
+static void test_secret_indirect_badfile(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "file", "does-not-exist",
+        NULL);
+
+    g_assert(sec == NULL);
+}
+
+
+static void test_secret_indirect_emptyfile(void)
+{
+    Object *sec;
+    char *fname = NULL;
+    int fd = g_file_open_tmp("secretXXXXXX",
+                             &fname,
+                             NULL);
+
+    g_assert(fd >= 0);
+    g_assert_nonnull(fname);
+
+    sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "file", fname,
+        NULL);
+
+    close(fd);
+    g_free(fname);
+    g_assert(sec == NULL);
+}
+
+
+static void test_secret_noconv_base64_good(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "MTIzNDU2",
+        "format", "base64",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_base64("sec0",
+                                               &error_abort);
+
+    g_assert_cmpstr(pw, ==, "MTIzNDU2");
+
+    object_unparent(sec);
+    g_free(pw);
+}
+
+
+static void test_secret_noconv_base64_bad(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "data", "MTI$NDU2",
+        "format", "base64",
+        NULL);
+
+    g_assert(sec == NULL);
+}
+
+
+static void test_secret_noconv_utf8(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "123456",
+        "format", "raw",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    g_free(pw);
+}
+
+
+static void test_secret_conv_base64_utf8valid(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "MTIzNDU2",
+        "format", "base64",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    g_free(pw);
+}
+
+
+static void test_secret_conv_base64_utf8invalid(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "f0VMRgIBAQAAAA==",
+        "format", "base64",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             NULL);
+    g_assert(pw == NULL);
+
+    object_unparent(sec);
+}
+
+
+static void test_secret_conv_utf8_base64(void)
+{
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "123456",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_base64("sec0",
+                                               &error_abort);
+
+    g_assert_cmpstr(pw, ==, "MTIzNDU2");
+
+    object_unparent(sec);
+    g_free(pw);
+}
+
+
+static void test_secret_crypt_raw(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data",
+        "\xCC\xBF\xF7\x09\x46\x19\x0B\x52\x2A\x3A\xB4\x6B\xCD\x7A\xB0\xB0",
+        "format", "raw",
+        "keyid", "master",
+        "iv", "0I7Gw/TKuA+Old2W2apQ3g==",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    object_unparent(master);
+    g_free(pw);
+}
+
+
+static void test_secret_crypt_base64(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        &error_abort,
+        "data", "zL/3CUYZC1IqOrRrzXqwsA==",
+        "format", "base64",
+        "keyid", "master",
+        "iv", "0I7Gw/TKuA+Old2W2apQ3g==",
+        NULL);
+
+    char *pw = qcrypto_secret_lookup_as_utf8("sec0",
+                                             &error_abort);
+
+    g_assert_cmpstr(pw, ==, "123456");
+
+    object_unparent(sec);
+    object_unparent(master);
+    g_free(pw);
+}
+
+
+static void test_secret_crypt_short_key(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVc",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "data", "zL/3CUYZC1IqOrRrzXqwsA==",
+        "format", "raw",
+        "keyid", "master",
+        "iv", "0I7Gw/TKuA+Old2W2apQ3g==",
+        NULL);
+
+    g_assert(sec == NULL);
+    object_unparent(master);
+}
+
+
+static void test_secret_crypt_short_iv(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "data", "zL/3CUYZC1IqOrRrzXqwsA==",
+        "format", "raw",
+        "keyid", "master",
+        "iv", "0I7Gw/TKuA+Old2W2a",
+        NULL);
+
+    g_assert(sec == NULL);
+    object_unparent(master);
+}
+
+
+static void test_secret_crypt_missing_iv(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "data", "zL/3CUYZC1IqOrRrzXqwsA==",
+        "format", "raw",
+        "keyid", "master",
+        NULL);
+
+    g_assert(sec == NULL);
+    object_unparent(master);
+}
+
+
+static void test_secret_crypt_bad_iv(void)
+{
+    Object *master = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "master",
+        &error_abort,
+        "data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
+        "format", "base64",
+        NULL);
+    Object *sec = object_new_with_props(
+        TYPE_QCRYPTO_SECRET,
+        object_get_objects_root(),
+        "sec0",
+        NULL,
+        "data", "zL/3CUYZC1IqOrRrzXqwsA==",
+        "format", "raw",
+        "keyid", "master",
+        "iv", "0I7Gw/TK$$uA+Old2W2a",
+        NULL);
+
+    g_assert(sec == NULL);
+    object_unparent(master);
+}
+
+
+int main(int argc, char **argv)
+{
+    module_call_init(MODULE_INIT_QOM);
+    g_test_init(&argc, &argv, NULL);
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    g_test_add_func("/crypto/secret/direct",
+                    test_secret_direct);
+    g_test_add_func("/crypto/secret/indirect/good",
+                    test_secret_indirect_good);
+    g_test_add_func("/crypto/secret/indirect/badfile",
+                    test_secret_indirect_badfile);
+    g_test_add_func("/crypto/secret/indirect/emptyfile",
+                    test_secret_indirect_emptyfile);
+
+    g_test_add_func("/crypto/secret/noconv/base64/good",
+                    test_secret_noconv_base64_good);
+    g_test_add_func("/crypto/secret/noconv/base64/bad",
+                    test_secret_noconv_base64_bad);
+    g_test_add_func("/crypto/secret/noconv/utf8",
+                    test_secret_noconv_utf8);
+    g_test_add_func("/crypto/secret/conv/base64/utf8valid",
+                    test_secret_conv_base64_utf8valid);
+    g_test_add_func("/crypto/secret/conv/base64/utf8invalid",
+                    test_secret_conv_base64_utf8invalid);
+    g_test_add_func("/crypto/secret/conv/utf8/base64",
+                    test_secret_conv_utf8_base64);
+
+    g_test_add_func("/crypto/secret/crypt/raw",
+                    test_secret_crypt_raw);
+    g_test_add_func("/crypto/secret/crypt/base64",
+                    test_secret_crypt_base64);
+    g_test_add_func("/crypto/secret/crypt/shortkey",
+                    test_secret_crypt_short_key);
+    g_test_add_func("/crypto/secret/crypt/shortiv",
+                    test_secret_crypt_short_iv);
+    g_test_add_func("/crypto/secret/crypt/missingiv",
+                    test_secret_crypt_missing_iv);
+    g_test_add_func("/crypto/secret/crypt/badiv",
+                    test_secret_crypt_bad_iv);
+
+    return g_test_run();
+}
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 02/30] crypto: add support for loading encrypted x509 keys
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 03/30] qcow: add a 'keyid' parameter to qcow options Daniel P. Berrange
                   ` (27 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Make use of the QCryptoSecret object to support loading of
encrypted x509 keys. The optional 'passwordid' parameter
to the tls-creds-x509 object type, provides the ID of a
secret object instance that holds the decryption password
for the PEM file.

 # echo "123456" > mypasswd.txt
 # $QEMU \
    -object secret,id=sec0,filename=mypasswd.txt \
    -object tls-creds-x509,passwordid=sec0,id=creds0,\
            dir=/home/berrange/.pki/qemu,endpoint=server \
    -vnc :1,tls-creds=creds0

This requires QEMU to be linked to GNUTLS >= 3.1.11. If
GNUTLS is too old an error will be reported if an attempt
is made to pass a decryption password.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/tlscredsx509.c         | 47 +++++++++++++++++++++++++++++++++++++++++++
 include/crypto/tlscredsx509.h |  1 +
 qemu-options.hx               |  8 +++++++-
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c
index d080deb..00bb299 100644
--- a/crypto/tlscredsx509.c
+++ b/crypto/tlscredsx509.c
@@ -20,6 +20,7 @@
 
 #include "crypto/tlscredsx509.h"
 #include "crypto/tlscredspriv.h"
+#include "crypto/secret.h"
 #include "qom/object_interfaces.h"
 #include "trace.h"
 
@@ -606,9 +607,29 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds,
     }
 
     if (cert != NULL && key != NULL) {
+#if GNUTLS_VERSION_NUMBER >= 0x030111
+        char *password = NULL;
+        if (creds->passwordid) {
+            password = qcrypto_secret_lookup_as_utf8(creds->passwordid,
+                                                     errp);
+            if (!password) {
+                goto cleanup;
+            }
+        }
+        ret = gnutls_certificate_set_x509_key_file2(creds->data,
+                                                    cert, key,
+                                                    GNUTLS_X509_FMT_PEM,
+                                                    password,
+                                                    0);
+#else /* GNUTLS_VERSION_NUMBER < 0x030111 */
+        if (creds->passwordid) {
+            error_setg(errp, "PKCS8 decryption requires GNUTLS >= 3.1.11");
+            goto cleanup;
+        }
         ret = gnutls_certificate_set_x509_key_file(creds->data,
                                                    cert, key,
                                                    GNUTLS_X509_FMT_PEM);
+#endif /* GNUTLS_VERSION_NUMBER < 0x030111 */
         if (ret < 0) {
             error_setg(errp, "Cannot load certificate '%s' & key '%s': %s",
                        cert, key, gnutls_strerror(ret));
@@ -736,6 +757,27 @@ qcrypto_tls_creds_x509_prop_set_sanity(Object *obj,
 }
 
 
+static void
+qcrypto_tls_creds_x509_prop_set_passwordid(Object *obj,
+                                           const char *value,
+                                           Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
+
+    creds->passwordid = g_strdup(value);
+}
+
+
+static char *
+qcrypto_tls_creds_x509_prop_get_passwordid(Object *obj,
+                                           Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
+
+    return g_strdup(creds->passwordid);
+}
+
+
 static bool
 qcrypto_tls_creds_x509_prop_get_sanity(Object *obj,
                                        Error **errp G_GNUC_UNUSED)
@@ -768,6 +810,10 @@ qcrypto_tls_creds_x509_init(Object *obj)
                              qcrypto_tls_creds_x509_prop_get_sanity,
                              qcrypto_tls_creds_x509_prop_set_sanity,
                              NULL);
+    object_property_add_str(obj, "passwordid",
+                            qcrypto_tls_creds_x509_prop_get_passwordid,
+                            qcrypto_tls_creds_x509_prop_set_passwordid,
+                            NULL);
 }
 
 
@@ -776,6 +822,7 @@ qcrypto_tls_creds_x509_finalize(Object *obj)
 {
     QCryptoTLSCredsX509 *creds = QCRYPTO_TLS_CREDS_X509(obj);
 
+    g_free(creds->passwordid);
     qcrypto_tls_creds_x509_unload(creds);
 }
 
diff --git a/include/crypto/tlscredsx509.h b/include/crypto/tlscredsx509.h
index b9785fd..25796d7 100644
--- a/include/crypto/tlscredsx509.h
+++ b/include/crypto/tlscredsx509.h
@@ -101,6 +101,7 @@ struct QCryptoTLSCredsX509 {
     gnutls_certificate_credentials_t data;
 #endif
     bool sanityCheck;
+    char *passwordid;
 };
 
 
diff --git a/qemu-options.hx b/qemu-options.hx
index dd3f7f8..bbf0c13 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3626,7 +3626,7 @@ expensive operation that consumes random pool entropy, so it is
 recommended that a persistent set of parameters be generated
 upfront and saved.
 
-@item -object tls-creds-x509,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off}
+@item -object tls-creds-x509,id=@var{id},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off},passwordid=@var{id}
 
 Creates a TLS anonymous credentials object, which can be used to provide
 TLS support on network backends. The @option{id} parameter is a unique
@@ -3653,6 +3653,12 @@ in PEM format, in filenames @var{ca-cert.pem}, @var{ca-crl.pem} (optional),
 @var{server-cert.pem} (only servers), @var{server-key.pem} (only servers),
 @var{client-cert.pem} (only clients), and @var{client-key.pem} (only clients).
 
+For the @var{server-key.pem} and @var{client-key.pem} files which
+contain sensitive private keys, it is possible to use an encrypted
+version by providing the @var{passwordid} parameter. This provides
+the ID of a previously created @code{secret} object containing the
+password for decryption.
+
 @item -object filter-buffer,id=@var{id},netdev=@var{netdevid},interval=@var{t}[,queue=@var{all|rx|tx}]
 
 Interval @var{t} can't be 0, this filter batches the packet delivery: all
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 03/30] qcow: add a 'keyid' parameter to qcow options
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 02/30] crypto: add support for loading encrypted x509 keys Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options Daniel P. Berrange
                   ` (26 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Add a 'keyid' parameter that refers to the ID of a
QCryptoSecret instance that provides the encryption key.
eg

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

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

diff --git a/block/qcow.c b/block/qcow.c
index 635085e..719ed7c 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -27,6 +27,7 @@
 #include <zlib.h>
 #include "qapi/qmp/qerror.h"
 #include "crypto/cipher.h"
+#include "crypto/secret.h"
 #include "migration/migration.h"
 
 /**************************************************************/
@@ -40,6 +41,8 @@
 
 #define QCOW_OFLAG_COMPRESSED (1LL << 63)
 
+#define QCOW_OPT_KEY_ID "keyid"
+
 typedef struct QCowHeader {
     uint32_t magic;
     uint32_t version;
@@ -92,6 +95,43 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)
         return 0;
 }
 
+static QCryptoCipher *qcow_get_cipher_from_key(const char *key,
+                                               Error **errp)
+{
+    uint8_t keybuf[16];
+    int len, i;
+
+    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];
+    }
+
+    return qcrypto_cipher_new(
+        QCRYPTO_CIPHER_ALG_AES_128,
+        QCRYPTO_CIPHER_MODE_CBC,
+        keybuf, G_N_ELEMENTS(keybuf),
+        errp);
+}
+
+static QemuOptsList qcow_runtime_opts = {
+    .name = "qcow",
+    .head = QTAILQ_HEAD_INITIALIZER(qcow_runtime_opts.head),
+    .desc = {
+        {
+            .name = QCOW_OPT_KEY_ID,
+            .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 +139,10 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     unsigned int len, i, shift;
     int ret;
     QCowHeader header;
+    QemuOpts *opts = NULL;
+    const char *keyid;
+    char *key;
+    Error *local_err = NULL;
 
     ret = bdrv_pread(bs->file->bs, 0, &header, sizeof(header));
     if (ret < 0) {
@@ -147,6 +191,32 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
+    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;
+    }
+
+    keyid = qemu_opt_get(opts, QCOW_OPT_KEY_ID);
+    if (keyid) {
+        key = qcrypto_secret_lookup_as_utf8(keyid,
+                                            errp);
+        if (!key) {
+            ret = -ENOENT;
+            goto fail;
+        }
+
+        s->cipher = qcow_get_cipher_from_key(key,
+                                             errp);
+        g_free(key);
+        if (!s->cipher) {
+            ret = -ENOSYS;
+            goto fail;
+        }
+    }
+
     if (header.crypt_method > QCOW_CRYPT_AES) {
         error_setg(errp, "invalid encryption method in qcow header");
         ret = -EINVAL;
@@ -261,33 +331,11 @@ static int qcow_reopen_prepare(BDRVReopenState *state,
 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);
-
+    s->cipher = qcow_get_cipher_from_key(key, NULL);
     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;
diff --git a/qapi/block-core.json b/qapi/block-core.json
index a07b13f..d3cc129 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1693,6 +1693,21 @@
             'mode':  'Qcow2OverlapCheckMode' } }
 
 ##
+# @BlockdevOptionsQcow
+#
+# Driver specific block device options for qcow.
+#
+# @keyid:                 #optional ID of the "secret" object providing the
+#                         AES decryption key.
+#
+# Since: 2.5
+##
+{ 'struct': 'BlockdevOptionsQcow',
+  'base': 'BlockdevOptionsGenericCOWFormat',
+  'data': { '*keyid': 'str' } }
+
+
+##
 # @BlockdevOptionsQcow2
 #
 # Driver specific block device options for qcow2.
@@ -1956,7 +1971,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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (2 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 03/30] qcow: add a 'keyid' parameter to qcow options Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 22:15   ` Eric Blake
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 05/30] qom: add user_creatable_add & user_creatable_del methods Daniel P. Berrange
                   ` (25 subsequent siblings)
  29 siblings, 1 reply; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Add a 'keyid' parameter that refers to the ID of a
QCryptoSecret instance that provides the encryption key.

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

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow2.c        | 80 +++++++++++++++++++++++++++++++++++++---------------
 block/qcow2.h        |  1 +
 qapi/block-core.json |  8 ++++--
 3 files changed, 64 insertions(+), 25 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 88f56c8..1e6e975 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -34,6 +34,7 @@
 #include "qapi-event.h"
 #include "trace.h"
 #include "qemu/option_int.h"
+#include "crypto/secret.h"
 
 /*
   Differences with QCOW:
@@ -472,6 +473,11 @@ static QemuOptsList qcow2_runtime_opts = {
             .type = QEMU_OPT_NUMBER,
             .help = "Clean unused cache entries after this time (in seconds)",
         },
+        {
+            .name = QCOW2_OPT_KEY_ID,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
         { /* end of list */ }
     },
 };
@@ -589,6 +595,31 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
     }
 }
 
+static QCryptoCipher *qcow2_get_cipher_from_key(const char *key,
+                                                Error **errp)
+{
+    uint8_t keybuf[16];
+    int len, i;
+
+    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];
+    }
+
+    return qcrypto_cipher_new(
+        QCRYPTO_CIPHER_ALG_AES_128,
+        QCRYPTO_CIPHER_MODE_CBC,
+        keybuf, G_N_ELEMENTS(keybuf),
+        errp);
+}
+
+
 typedef struct Qcow2ReopenState {
     Qcow2Cache *l2_table_cache;
     Qcow2Cache *refcount_block_cache;
@@ -596,6 +627,7 @@ typedef struct Qcow2ReopenState {
     int overlap_check;
     bool discard_passthrough[QCOW2_DISCARD_MAX];
     uint64_t cache_clean_interval;
+    QCryptoCipher *cipher;
 } Qcow2ReopenState;
 
 static int qcow2_update_options_prepare(BlockDriverState *bs,
@@ -611,6 +643,8 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     int i;
     Error *local_err = NULL;
     int ret;
+    const char *keyid;
+    char *key;
 
     opts = qemu_opts_create(&qcow2_runtime_opts, NULL, 0, &error_abort);
     qemu_opts_absorb_qdict(opts, options, &local_err);
@@ -754,6 +788,24 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     r->discard_passthrough[QCOW2_DISCARD_OTHER] =
         qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
 
+    keyid = qemu_opt_get(opts, QCOW2_OPT_KEY_ID);
+    if (keyid) {
+        key = qcrypto_secret_lookup_as_utf8(keyid,
+                                            errp);
+        if (!key) {
+            ret = -ENOENT;
+            goto fail;
+        }
+
+        r->cipher = qcow2_get_cipher_from_key(key,
+                                              errp);
+        g_free(key);
+        if (!r->cipher) {
+            ret = -ENOSYS;
+            goto fail;
+        }
+    }
+
     ret = 0;
 fail:
     qemu_opts_del(opts);
@@ -788,6 +840,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));
     }
+
+    qcrypto_cipher_free(s->cipher);
+    s->cipher = r->cipher;
 }
 
 static void qcow2_update_options_abort(BlockDriverState *bs,
@@ -799,6 +854,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
     if (r->refcount_block_cache) {
         qcow2_cache_destroy(bs, r->refcount_block_cache);
     }
+    qcrypto_cipher_free(r->cipher);
 }
 
 static int qcow2_update_options(BlockDriverState *bs, QDict *options,
@@ -1202,33 +1258,11 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
 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);
-
+    s->cipher = qcow2_get_cipher_from_key(key, NULL);
     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;
diff --git a/block/qcow2.h b/block/qcow2.h
index b8c500b..376f83f 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -97,6 +97,7 @@
 #define QCOW2_OPT_L2_CACHE_SIZE "l2-cache-size"
 #define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
 #define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
+#define QCOW2_OPT_KEY_ID "keyid"
 
 typedef struct QCowHeader {
     uint32_t magic;
diff --git a/qapi/block-core.json b/qapi/block-core.json
index d3cc129..6967554 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1698,7 +1698,7 @@
 # Driver specific block device options for qcow.
 #
 # @keyid:                 #optional ID of the "secret" object providing the
-#                         AES decryption key.
+#                         AES decryption key (since 2.5)
 #
 # Since: 2.5
 ##
@@ -1742,6 +1742,9 @@
 #                         caches. The interval is in seconds. The default value
 #                         is 0 and it disables this feature (since 2.5)
 #
+# @keyid:                 #optional ID of the "secret" object providing the
+#                         AES decryption key.
+#
 # Since: 1.7
 ##
 { 'struct': 'BlockdevOptionsQcow2',
@@ -1754,7 +1757,8 @@
             '*cache-size': 'int',
             '*l2-cache-size': 'int',
             '*refcount-cache-size': 'int',
-            '*cache-clean-interval': 'int' } }
+            '*cache-clean-interval': 'int',
+            '*keyid': 'str' } }
 
 
 ##
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 05/30] qom: add user_creatable_add & user_creatable_del methods
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (3 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 06/30] qemu-img: add support for --object command line arg Daniel P. Berrange
                   ` (24 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The QMP monitor code has two helper methods object_add
and qmp_object_del that are called from several places
in the code (QMP, HMP and main emulator startup).

We soon need to use this code from qemu-img, qemu-io
and qemu-nbd too, but don't want those to depend on
the monitor.

To avoid this, move object_add to user_creatable_add
an qmp_object_del to user_creatable_del, in the
object_interfaces.c file

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 hmp.c                           | 11 ++++--
 include/monitor/monitor.h       |  3 --
 include/qom/object_interfaces.h | 31 +++++++++++++++++
 qmp.c                           | 75 ++++------------------------------------
 qom/object_interfaces.c         | 76 +++++++++++++++++++++++++++++++++++++++++
 vl.c                            |  8 +++--
 6 files changed, 127 insertions(+), 77 deletions(-)

diff --git a/hmp.c b/hmp.c
index 2140605..6044db3 100644
--- a/hmp.c
+++ b/hmp.c
@@ -29,6 +29,7 @@
 #include "qapi/string-output-visitor.h"
 #include "qapi/util.h"
 #include "qapi-visit.h"
+#include "qom/object_interfaces.h"
 #include "ui/console.h"
 #include "block/qapi.h"
 #include "qemu-io.h"
@@ -1662,6 +1663,7 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)
     void *dummy = NULL;
     OptsVisitor *ov;
     QDict *pdict;
+    Object *obj = NULL;
 
     opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err);
     if (err) {
@@ -1688,12 +1690,12 @@ void hmp_object_add(Monitor *mon, const QDict *qdict)
         goto out_end;
     }
 
-    object_add(type, id, pdict, opts_get_visitor(ov), &err);
+    obj = user_creatable_add(type, id, pdict, opts_get_visitor(ov), &err);
 
 out_end:
     visit_end_struct(opts_get_visitor(ov), &err_end);
     if (!err && err_end) {
-        qmp_object_del(id, NULL);
+        user_creatable_del(id, NULL);
     }
     error_propagate(&err, err_end);
 out_clean:
@@ -1704,6 +1706,9 @@ out_clean:
     g_free(id);
     g_free(type);
     g_free(dummy);
+    if (obj) {
+        object_unref(obj);
+    }
 
 out:
     hmp_handle_error(mon, &err);
@@ -1936,7 +1941,7 @@ void hmp_object_del(Monitor *mon, const QDict *qdict)
     const char *id = qdict_get_str(qdict, "id");
     Error *err = NULL;
 
-    qmp_object_del(id, &err);
+    user_creatable_del(id, &err);
     hmp_handle_error(mon, &err);
 }
 
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 91b95ae..aa0f373 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -43,9 +43,6 @@ void monitor_read_command(Monitor *mon, int show_prompt);
 int monitor_read_password(Monitor *mon, ReadLineFunc *readline_func,
                           void *opaque);
 
-void object_add(const char *type, const char *id, const QDict *qdict,
-                Visitor *v, Error **errp);
-
 AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id,
                                 bool has_opaque, const char *opaque,
                                 Error **errp);
diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
index 283ae0d..3e2afeb 100644
--- a/include/qom/object_interfaces.h
+++ b/include/qom/object_interfaces.h
@@ -2,6 +2,8 @@
 #define OBJECT_INTERFACES_H
 
 #include "qom/object.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/visitor.h"
 
 #define TYPE_USER_CREATABLE "user-creatable"
 
@@ -72,4 +74,33 @@ void user_creatable_complete(Object *obj, Error **errp);
  * from implements USER_CREATABLE interface.
  */
 bool user_creatable_can_be_deleted(UserCreatable *uc, Error **errp);
+
+/**
+ * user_creatable_add:
+ * @type: the object type name
+ * @id: the unique ID for the object
+ * @qdict: the object parameters
+ * @v: the visitor
+ * @errp: if an error occurs, a pointer to an area to store the error
+ *
+ * Create an instance of the user creatable object @type, placing
+ * it in the object composition tree with name @id, initializing
+ * it with properties from @qdict
+ *
+ * Returns: the newly created object or NULL on error
+ */
+Object *user_creatable_add(const char *type, const char *id,
+                           const QDict *qdict,
+                           Visitor *v, Error **errp);
+
+/**
+ * user_creatable_del:
+ * @id: the unique ID for the object
+ * @errp: if an error occurs, a pointer to an area to store the error
+ *
+ * Delete an instance of the user creatable object identified
+ * by @id.
+ */
+void user_creatable_del(const char *id, Error **errp);
+
 #endif
diff --git a/qmp.c b/qmp.c
index ddc63ea..f38d597 100644
--- a/qmp.c
+++ b/qmp.c
@@ -616,65 +616,13 @@ void qmp_add_client(const char *protocol, const char *fdname,
     close(fd);
 }
 
-void object_add(const char *type, const char *id, const QDict *qdict,
-                Visitor *v, Error **errp)
-{
-    Object *obj;
-    ObjectClass *klass;
-    const QDictEntry *e;
-    Error *local_err = NULL;
-
-    klass = object_class_by_name(type);
-    if (!klass) {
-        error_setg(errp, "invalid object type: %s", type);
-        return;
-    }
-
-    if (!object_class_dynamic_cast(klass, TYPE_USER_CREATABLE)) {
-        error_setg(errp, "object type '%s' isn't supported by object-add",
-                   type);
-        return;
-    }
-
-    if (object_class_is_abstract(klass)) {
-        error_setg(errp, "object type '%s' is abstract", type);
-        return;
-    }
-
-    obj = object_new(type);
-    if (qdict) {
-        for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
-            object_property_set(obj, v, e->key, &local_err);
-            if (local_err) {
-                goto out;
-            }
-        }
-    }
-
-    object_property_add_child(object_get_objects_root(),
-                              id, obj, &local_err);
-    if (local_err) {
-        goto out;
-    }
-
-    user_creatable_complete(obj, &local_err);
-    if (local_err) {
-        object_property_del(object_get_objects_root(),
-                            id, &error_abort);
-        goto out;
-    }
-out:
-    if (local_err) {
-        error_propagate(errp, local_err);
-    }
-    object_unref(obj);
-}
 
 void qmp_object_add(const char *type, const char *id,
                     bool has_props, QObject *props, Error **errp)
 {
     const QDict *pdict = NULL;
     QmpInputVisitor *qiv;
+    Object *obj;
 
     if (props) {
         pdict = qobject_to_qdict(props);
@@ -685,27 +633,16 @@ void qmp_object_add(const char *type, const char *id,
     }
 
     qiv = qmp_input_visitor_new(props);
-    object_add(type, id, pdict, qmp_input_get_visitor(qiv), errp);
+    obj = user_creatable_add(type, id, pdict, qmp_input_get_visitor(qiv), errp);
     qmp_input_visitor_cleanup(qiv);
+    if (obj) {
+        object_unref(obj);
+    }
 }
 
 void qmp_object_del(const char *id, Error **errp)
 {
-    Object *container;
-    Object *obj;
-
-    container = object_get_objects_root();
-    obj = object_resolve_path_component(container, id);
-    if (!obj) {
-        error_setg(errp, "object id not found");
-        return;
-    }
-
-    if (!user_creatable_can_be_deleted(USER_CREATABLE(obj), errp)) {
-        error_setg(errp, "%s is in use, can not be deleted", id);
-        return;
-    }
-    object_unparent(obj);
+    user_creatable_del(id, errp);
 }
 
 MemoryDeviceInfoList *qmp_query_memory_devices(Error **errp)
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index a66cd60..d94995f 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -30,6 +30,82 @@ bool user_creatable_can_be_deleted(UserCreatable *uc, Error **errp)
     }
 }
 
+Object *user_creatable_add(const char *type, const char *id,
+                           const QDict *qdict,
+                           Visitor *v, Error **errp)
+{
+    Object *obj;
+    ObjectClass *klass;
+    const QDictEntry *e;
+    Error *local_err = NULL;
+
+    klass = object_class_by_name(type);
+    if (!klass) {
+        error_setg(errp, "invalid object type: %s", type);
+        return NULL;
+    }
+
+    if (!object_class_dynamic_cast(klass, TYPE_USER_CREATABLE)) {
+        error_setg(errp, "object type '%s' isn't supported by object-add",
+                   type);
+        return NULL;
+    }
+
+    if (object_class_is_abstract(klass)) {
+        error_setg(errp, "object type '%s' is abstract", type);
+        return NULL;
+    }
+
+    obj = object_new(type);
+    if (qdict) {
+        for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
+            object_property_set(obj, v, e->key, &local_err);
+            if (local_err) {
+                goto out;
+            }
+        }
+    }
+
+    object_property_add_child(object_get_objects_root(),
+                              id, obj, &local_err);
+    if (local_err) {
+        goto out;
+    }
+
+    user_creatable_complete(obj, &local_err);
+    if (local_err) {
+        object_property_del(object_get_objects_root(),
+                            id, &error_abort);
+        goto out;
+    }
+out:
+    if (local_err) {
+        error_propagate(errp, local_err);
+        object_unref(obj);
+        return NULL;
+    }
+    return obj;
+}
+
+void user_creatable_del(const char *id, Error **errp)
+{
+    Object *container;
+    Object *obj;
+
+    container = object_get_objects_root();
+    obj = object_resolve_path_component(container, id);
+    if (!obj) {
+        error_setg(errp, "object id not found");
+        return;
+    }
+
+    if (!user_creatable_can_be_deleted(USER_CREATABLE(obj), errp)) {
+        error_setg(errp, "%s is in use, can not be deleted", id);
+        return;
+    }
+    object_unparent(obj);
+}
+
 static void register_types(void)
 {
     static const TypeInfo uc_interface_info = {
diff --git a/vl.c b/vl.c
index 7d993a5..0be9a3a 100644
--- a/vl.c
+++ b/vl.c
@@ -2831,6 +2831,7 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
     OptsVisitor *ov;
     QDict *pdict;
     bool (*type_predicate)(const char *) = opaque;
+    Object *obj = NULL;
 
     ov = opts_visitor_new(opts);
     pdict = qemu_opts_to_qdict(opts, NULL);
@@ -2855,13 +2856,13 @@ static int object_create(void *opaque, QemuOpts *opts, Error **errp)
         goto out;
     }
 
-    object_add(type, id, pdict, opts_get_visitor(ov), &err);
+    obj = user_creatable_add(type, id, pdict, opts_get_visitor(ov), &err);
     if (err) {
         goto out;
     }
     visit_end_struct(opts_get_visitor(ov), &err);
     if (err) {
-        qmp_object_del(id, NULL);
+        user_creatable_del(id, NULL);
     }
 
 out:
@@ -2871,6 +2872,9 @@ out:
     g_free(id);
     g_free(type);
     g_free(dummy);
+    if (obj) {
+        object_unref(obj);
+    }
     if (err) {
         error_report_err(err);
         return -1;
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 06/30] qemu-img: add support for --object command line arg
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (4 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 05/30] qom: add user_creatable_add & user_creatable_del methods Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 07/30] qemu-nbd: " Daniel P. Berrange
                   ` (23 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Allow creation of user creatable object types with qemu-img
via a --object command line arg. This will be used to supply
passwords and/or encryption keys to the various block driver
backends via the recently added 'secret' object type.

 # echo -n letmein > mypasswd.txt
 # qemu-img info --object secret,id=sec0,file=mypasswd.txt \
      ...other info args...

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-img-cmds.hx |  44 ++++----
 qemu-img.c       | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 qemu-img.texi    |   8 ++
 3 files changed, 322 insertions(+), 30 deletions(-)

diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index 9567774..5bb1de7 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -10,68 +10,68 @@ STEXI
 ETEXI
 
 DEF("check", img_check,
-    "check [-q] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename")
+    "check [-q] [--object objectdef] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] filename")
 STEXI
-@item check [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename}
+@item check [--object objectdef] [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] @var{filename}
 ETEXI
 
 DEF("create", img_create,
-    "create [-q] [-f fmt] [-o options] filename [size]")
+    "create [-q] [--object objectdef] [-f fmt] [-o options] filename [size]")
 STEXI
-@item create [-q] [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}]
+@item create [--object objectdef] [-q] [-f @var{fmt}] [-o @var{options}] @var{filename} [@var{size}]
 ETEXI
 
 DEF("commit", img_commit,
-    "commit [-q] [-f fmt] [-t cache] [-b base] [-d] [-p] filename")
+    "commit [-q] [--object objectdef] [-f fmt] [-t cache] [-b base] [-d] [-p] filename")
 STEXI
-@item commit [-q] [-f @var{fmt}] [-t @var{cache}] [-b @var{base}] [-d] [-p] @var{filename}
+@item commit [--object objectdef] [-q] [-f @var{fmt}] [-t @var{cache}] [-b @var{base}] [-d] [-p] @var{filename}
 ETEXI
 
 DEF("compare", img_compare,
-    "compare [-f fmt] [-F fmt] [-T src_cache] [-p] [-q] [-s] filename1 filename2")
+    "compare [--object objectdef] [-f fmt] [-F fmt] [-T src_cache] [-p] [-q] [-s] filename1 filename2")
 STEXI
-@item compare [-f @var{fmt}] [-F @var{fmt}] [-T @var{src_cache}] [-p] [-q] [-s] @var{filename1} @var{filename2}
+@item compare [--object objectdef] [-f @var{fmt}] [-F @var{fmt}] [-T @var{src_cache}] [-p] [-q] [-s] @var{filename1} @var{filename2}
 ETEXI
 
 DEF("convert", img_convert,
-    "convert [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-o options] [-s snapshot_id_or_name] [-l snapshot_param] [-S sparse_size] filename [filename2 [...]] output_filename")
+    "convert [--object objectdef] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-o options] [-s snapshot_id_or_name] [-l snapshot_param] [-S sparse_size] filename [filename2 [...]] output_filename")
 STEXI
-@item convert [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
+@item convert [--object objectdef] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_id_or_name}] [-l @var{snapshot_param}] [-S @var{sparse_size}] @var{filename} [@var{filename2} [...]] @var{output_filename}
 ETEXI
 
 DEF("info", img_info,
-    "info [-f fmt] [--output=ofmt] [--backing-chain] filename")
+    "info [--object objectdef] [-f fmt] [--output=ofmt] [--backing-chain] filename")
 STEXI
-@item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
+@item info [--object objectdef] [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
 ETEXI
 
 DEF("map", img_map,
-    "map [-f fmt] [--output=ofmt] filename")
+    "map [--object objectdef] [-f fmt] [--output=ofmt] filename")
 STEXI
-@item map [-f @var{fmt}] [--output=@var{ofmt}] @var{filename}
+@item map [--object objectdef] [-f @var{fmt}] [--output=@var{ofmt}] @var{filename}
 ETEXI
 
 DEF("snapshot", img_snapshot,
-    "snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
+    "snapshot [--object objectdef] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
 STEXI
-@item snapshot [-q] [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename}
+@item snapshot [--object objectdef] [-q] [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename}
 ETEXI
 
 DEF("rebase", img_rebase,
-    "rebase [-q] [-f fmt] [-t cache] [-T src_cache] [-p] [-u] -b backing_file [-F backing_fmt] filename")
+    "rebase [--object objectdef] [-q] [-f fmt] [-t cache] [-T src_cache] [-p] [-u] -b backing_file [-F backing_fmt] filename")
 STEXI
-@item rebase [-q] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename}
+@item rebase [--object objectdef] [-q] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename}
 ETEXI
 
 DEF("resize", img_resize,
-    "resize [-q] filename [+ | -]size")
+    "resize [--object objectdef] [-q] filename [+ | -]size")
 STEXI
-@item resize [-q] @var{filename} [+ | -]@var{size}
+@item resize [--object objectdef] [-q] @var{filename} [+ | -]@var{size}
 ETEXI
 
 DEF("amend", img_amend,
-    "amend [-p] [-q] [-f fmt] [-t cache] -o options filename")
+    "amend [--object objectdef] [-p] [-q] [-f fmt] [-t cache] -o options filename")
 STEXI
-@item amend [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
+@item amend [--object objectdef] [-p] [-q] [-f @var{fmt}] [-t @var{cache}] -o @var{options} @var{filename}
 @end table
 ETEXI
diff --git a/qemu-img.c b/qemu-img.c
index 033011c..822f193 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -23,12 +23,15 @@
  */
 #include "qapi-visit.h"
 #include "qapi/qmp-output-visitor.h"
+#include "qapi/opts-visitor.h"
 #include "qapi/qmp/qerror.h"
 #include "qapi/qmp/qjson.h"
 #include "qemu-common.h"
+#include "qemu/config-file.h"
 #include "qemu/option.h"
 #include "qemu/error-report.h"
 #include "qemu/osdep.h"
+#include "qom/object_interfaces.h"
 #include "sysemu/sysemu.h"
 #include "sysemu/block-backend.h"
 #include "block/block_int.h"
@@ -47,6 +50,7 @@ typedef struct img_cmd_t {
 enum {
     OPTION_OUTPUT = 256,
     OPTION_BACKING_CHAIN = 257,
+    OPTION_OBJECT = 258,
 };
 
 typedef enum OutputFormat {
@@ -94,6 +98,11 @@ static void QEMU_NORETURN help(void)
            "\n"
            "Command parameters:\n"
            "  'filename' is a disk image filename\n"
+           "  'objectdef' is a QEMU user creatable object definition. See \n"
+           "    the @code{qemu(1)} manual page for a description of the object\n"
+           "    properties. The only object type that it makes sense to define\n"
+           "    is the @code{secret} object, which is used to supply passwords\n"
+           "    and/or encryption keys.\n"
            "  'fmt' is the disk image format. It is guessed automatically in most cases\n"
            "  'cache' is the cache mode used to write the output disk image, the valid\n"
            "    options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n"
@@ -154,6 +163,67 @@ static void QEMU_NORETURN help(void)
     exit(EXIT_SUCCESS);
 }
 
+static QemuOptsList qemu_object_opts = {
+    .name = "object",
+    .implied_opt_name = "qom-type",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
+    .desc = {
+        { }
+    },
+};
+
+static int object_create(void *opaque, QemuOpts *opts, Error **errp)
+{
+    Error *err = NULL;
+    char *type = NULL;
+    char *id = NULL;
+    void *dummy = NULL;
+    OptsVisitor *ov;
+    QDict *pdict;
+
+    ov = opts_visitor_new(opts);
+    pdict = qemu_opts_to_qdict(opts, NULL);
+
+    visit_start_struct(opts_get_visitor(ov), &dummy, NULL, NULL, 0, &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "qom-type");
+    visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "id");
+    visit_type_str(opts_get_visitor(ov), &id, "id", &err);
+    if (err) {
+        goto out;
+    }
+
+    user_creatable_add(type, id, pdict, opts_get_visitor(ov), &err);
+    if (err) {
+        goto out;
+    }
+    visit_end_struct(opts_get_visitor(ov), &err);
+    if (err) {
+        user_creatable_del(id, NULL);
+    }
+
+out:
+    opts_visitor_cleanup(ov);
+
+    QDECREF(pdict);
+    g_free(id);
+    g_free(type);
+    g_free(dummy);
+    if (err) {
+        error_report_err(err);
+        return -1;
+    }
+    return 0;
+}
+
 static int GCC_FMT_ATTR(2, 3) qprintf(bool quiet, const char *fmt, ...)
 {
     int ret = 0;
@@ -275,9 +345,17 @@ static int img_create(int argc, char **argv)
     char *options = NULL;
     Error *local_err = NULL;
     bool quiet = false;
+    QemuOpts *opts;
 
     for(;;) {
-        c = getopt(argc, argv, "F:b:f:he6o:q");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "F:b:f:he6o:q",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -319,6 +397,13 @@ static int img_create(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -334,6 +419,12 @@ static int img_create(int argc, char **argv)
     }
     optind++;
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* Get image size, if specified */
     if (optind < argc) {
         int64_t sval;
@@ -492,6 +583,7 @@ static int img_check(int argc, char **argv)
     int flags = BDRV_O_FLAGS | BDRV_O_CHECK;
     ImageCheck *check;
     bool quiet = false;
+    QemuOpts *opts;
 
     fmt = NULL;
     output = NULL;
@@ -503,6 +595,7 @@ static int img_check(int argc, char **argv)
             {"format", required_argument, 0, 'f'},
             {"repair", required_argument, 0, 'r'},
             {"output", required_argument, 0, OPTION_OUTPUT},
+            {"object", required_argument, 0, OPTION_OBJECT},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "hf:r:T:q",
@@ -539,6 +632,13 @@ static int img_check(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
     if (optind != argc - 1) {
@@ -555,6 +655,12 @@ static int img_check(int argc, char **argv)
         return 1;
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     ret = bdrv_parse_cache_flags(cache, &flags);
     if (ret < 0) {
         error_report("Invalid source cache option: %s", cache);
@@ -673,12 +779,20 @@ static int img_commit(int argc, char **argv)
     bool progress = false, quiet = false, drop = false;
     Error *local_err = NULL;
     CommonBlockJobCBInfo cbi;
+    QemuOpts *opts;
 
     fmt = NULL;
     cache = BDRV_DEFAULT_CACHE;
     base = NULL;
     for(;;) {
-        c = getopt(argc, argv, "f:ht:b:dpq");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "f:ht:b:dpq",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -707,6 +821,13 @@ static int img_commit(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -720,6 +841,12 @@ static int img_commit(int argc, char **argv)
     }
     filename = argv[optind++];
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     flags = BDRV_O_RDWR | BDRV_O_UNMAP;
     ret = bdrv_parse_cache_flags(cache, &flags);
     if (ret < 0) {
@@ -976,10 +1103,18 @@ static int img_compare(int argc, char **argv)
     int64_t nb_sectors;
     int c, pnum;
     uint64_t progress_base;
+    QemuOpts *opts;
 
     cache = BDRV_DEFAULT_CACHE;
     for (;;) {
-        c = getopt(argc, argv, "hf:F:T:pqs");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "hf:F:T:pqs",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -1006,6 +1141,13 @@ static int img_compare(int argc, char **argv)
         case 's':
             strict = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -1021,6 +1163,12 @@ static int img_compare(int argc, char **argv)
     filename1 = argv[optind++];
     filename2 = argv[optind++];
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* Initialize before goto out */
     qemu_progress_init(progress, 2.0);
 
@@ -1540,7 +1688,14 @@ static int img_convert(int argc, char **argv)
     compress = 0;
     skip_create = 0;
     for(;;) {
-        c = getopt(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -1631,9 +1786,22 @@ static int img_convert(int argc, char **argv)
         case 'n':
             skip_create = 1;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* Initialize before goto out */
     if (quiet) {
         progress = 0;
@@ -2063,6 +2231,7 @@ static int img_info(int argc, char **argv)
     bool chain = false;
     const char *filename, *fmt, *output;
     ImageInfoList *list;
+    QemuOpts *opts;
 
     fmt = NULL;
     output = NULL;
@@ -2073,6 +2242,7 @@ static int img_info(int argc, char **argv)
             {"format", required_argument, 0, 'f'},
             {"output", required_argument, 0, OPTION_OUTPUT},
             {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN},
+            {"object", required_argument, 0, OPTION_OBJECT},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:h",
@@ -2094,6 +2264,13 @@ static int img_info(int argc, char **argv)
         case OPTION_BACKING_CHAIN:
             chain = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
     if (optind != argc - 1) {
@@ -2110,6 +2287,12 @@ static int img_info(int argc, char **argv)
         return 1;
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     list = collect_image_info_list(filename, fmt, chain);
     if (!list) {
         return 1;
@@ -2233,6 +2416,7 @@ static int img_map(int argc, char **argv)
     int64_t length;
     MapEntry curr = { .length = 0 }, next;
     int ret = 0;
+    QemuOpts *opts;
 
     fmt = NULL;
     output = NULL;
@@ -2242,6 +2426,7 @@ static int img_map(int argc, char **argv)
             {"help", no_argument, 0, 'h'},
             {"format", required_argument, 0, 'f'},
             {"output", required_argument, 0, OPTION_OUTPUT},
+            {"object", required_argument, 0, OPTION_OBJECT},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:h",
@@ -2260,6 +2445,13 @@ static int img_map(int argc, char **argv)
         case OPTION_OUTPUT:
             output = optarg;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
     if (optind != argc - 1) {
@@ -2276,6 +2468,12 @@ static int img_map(int argc, char **argv)
         return 1;
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     blk = img_open("image", filename, fmt, BDRV_O_FLAGS, true, false);
     if (!blk) {
         return 1;
@@ -2341,11 +2539,19 @@ static int img_snapshot(int argc, char **argv)
     qemu_timeval tv;
     bool quiet = false;
     Error *err = NULL;
+    QemuOpts *opts;
 
     bdrv_oflags = BDRV_O_FLAGS | BDRV_O_RDWR;
     /* Parse commandline parameters */
     for(;;) {
-        c = getopt(argc, argv, "la:c:d:hq");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "la:c:d:hq",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -2389,6 +2595,13 @@ static int img_snapshot(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -2397,6 +2610,12 @@ static int img_snapshot(int argc, char **argv)
     }
     filename = argv[optind++];
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* Open the image */
     blk = img_open("image", filename, NULL, bdrv_oflags, true, quiet);
     if (!blk) {
@@ -2463,6 +2682,7 @@ static int img_rebase(int argc, char **argv)
     int progress = 0;
     bool quiet = false;
     Error *local_err = NULL;
+    QemuOpts *opts;
 
     /* Parse commandline parameters */
     fmt = NULL;
@@ -2471,7 +2691,14 @@ static int img_rebase(int argc, char **argv)
     out_baseimg = NULL;
     out_basefmt = NULL;
     for(;;) {
-        c = getopt(argc, argv, "hf:F:b:upt:T:q");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "hf:F:b:upt:T:q",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -2504,6 +2731,13 @@ static int img_rebase(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -2519,6 +2753,12 @@ static int img_rebase(int argc, char **argv)
     }
     filename = argv[optind++];
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     qemu_progress_init(progress, 2.0);
     qemu_progress_print(0, 100);
 
@@ -2779,6 +3019,7 @@ static int img_resize(int argc, char **argv)
     bool quiet = false;
     BlockBackend *blk = NULL;
     QemuOpts *param;
+    QemuOpts *opts;
     static QemuOptsList resize_options = {
         .name = "resize_options",
         .head = QTAILQ_HEAD_INITIALIZER(resize_options.head),
@@ -2805,7 +3046,14 @@ static int img_resize(int argc, char **argv)
     /* Parse getopt arguments */
     fmt = NULL;
     for(;;) {
-        c = getopt(argc, argv, "f:hq");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "f:hq",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -2820,6 +3068,13 @@ static int img_resize(int argc, char **argv)
         case 'q':
             quiet = true;
             break;
+        case OPTION_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
     if (optind != argc - 1) {
@@ -2827,6 +3082,12 @@ static int img_resize(int argc, char **argv)
     }
     filename = argv[optind++];
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* Choose grow, shrink, or absolute resize mode */
     switch (size[0]) {
     case '+':
@@ -2916,7 +3177,14 @@ static int img_amend(int argc, char **argv)
 
     cache = BDRV_DEFAULT_CACHE;
     for (;;) {
-        c = getopt(argc, argv, "ho:f:t:pq");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"object", required_argument, 0, OPTION_OBJECT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "ho:f:t:pq",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -2952,6 +3220,13 @@ static int img_amend(int argc, char **argv)
             case 'q':
                 quiet = true;
                 break;
+            case OPTION_OBJECT:
+                opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                               optarg, true);
+                if (!opts) {
+                    exit(1);
+                }
+                break;
         }
     }
 
@@ -2959,6 +3234,12 @@ static int img_amend(int argc, char **argv)
         error_exit("Must specify options (-o)");
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     if (quiet) {
         progress = false;
     }
@@ -3081,6 +3362,9 @@ int main(int argc, char **argv)
     }
     cmdname = argv[1];
 
+    module_call_init(MODULE_INIT_QOM);
+    qemu_add_opts(&qemu_object_opts);
+
     /* find the command */
     for (cmd = img_cmds; cmd->name != NULL; cmd++) {
         if (!strcmp(cmdname, cmd->name)) {
diff --git a/qemu-img.texi b/qemu-img.texi
index 55c6be3..8e7c17c 100644
--- a/qemu-img.texi
+++ b/qemu-img.texi
@@ -24,6 +24,14 @@ Command parameters:
 @table @var
 @item filename
  is a disk image filename
+
+@item objectdef
+
+is a QEMU user creatable object definition. See the @code{qemu(1)} manual
+page for a description of the object properties. The only object type that
+it makes sense to define is the @code{secret} object, which is used to
+supply passwords and/or encryption keys.
+
 @item fmt
 is the disk image format. It is guessed automatically in most cases. See below
 for a description of the supported disk formats.
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 07/30] qemu-nbd: add support for --object command line arg
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (5 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 06/30] qemu-img: add support for --object command line arg Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 08/30] qemu-io: " Daniel P. Berrange
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Allow creation of user creatable object types with qemu-nbd
via a --object command line arg. This will be used to supply
passwords and/or encryption keys to the various block driver
backends via the recently added 'secret' object type.

 # echo -n letmein > mypasswd.txt
 # qemu-nbd --object secret,id=sec0,file=mypasswd.txt \
      ...other nbd args...

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-nbd.c    | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qemu-nbd.texi |  7 +++++
 2 files changed, 92 insertions(+)

diff --git a/qemu-nbd.c b/qemu-nbd.c
index 3afec76..41f4285 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -23,9 +23,12 @@
 #include "qemu/main-loop.h"
 #include "qemu/sockets.h"
 #include "qemu/error-report.h"
+#include "qemu/config-file.h"
 #include "block/snapshot.h"
 #include "qapi/util.h"
 #include "qapi/qmp/qstring.h"
+#include "qapi/opts-visitor.h"
+#include "qom/object_interfaces.h"
 
 #include <stdarg.h>
 #include <stdio.h>
@@ -45,6 +48,7 @@
 #define QEMU_NBD_OPT_AIO           2
 #define QEMU_NBD_OPT_DISCARD       3
 #define QEMU_NBD_OPT_DETECT_ZEROES 4
+#define QEMU_NBD_OPT_OBJECT        5
 
 static NBDExport *exp;
 static int verbose;
@@ -78,6 +82,9 @@ static void usage(const char *name)
 "  -o, --offset=OFFSET       offset into the image\n"
 "  -P, --partition=NUM       only expose partition NUM\n"
 "\n"
+"General purpose options:\n"
+"  --object type,id=ID,...   define an object such as 'secret' for providing\n"
+"                            passwords and/or encryption keys\n"
 #ifdef __linux__
 "Kernel NBD client support:\n"
 "  -c, --connect=DEV         connect FILE to the local NBD device DEV\n"
@@ -380,6 +387,67 @@ static SocketAddress *nbd_build_socket_address(const char *sockpath,
 }
 
 
+static QemuOptsList qemu_object_opts = {
+    .name = "object",
+    .implied_opt_name = "qom-type",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
+    .desc = {
+        { }
+    },
+};
+
+static int object_create(void *opaque, QemuOpts *opts, Error **errp)
+{
+    Error *err = NULL;
+    char *type = NULL;
+    char *id = NULL;
+    void *dummy = NULL;
+    OptsVisitor *ov;
+    QDict *pdict;
+
+    ov = opts_visitor_new(opts);
+    pdict = qemu_opts_to_qdict(opts, NULL);
+
+    visit_start_struct(opts_get_visitor(ov), &dummy, NULL, NULL, 0, &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "qom-type");
+    visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "id");
+    visit_type_str(opts_get_visitor(ov), &id, "id", &err);
+    if (err) {
+        goto out;
+    }
+
+    user_creatable_add(type, id, pdict, opts_get_visitor(ov), &err);
+    if (err) {
+        goto out;
+    }
+    visit_end_struct(opts_get_visitor(ov), &err);
+    if (err) {
+        user_creatable_del(id, NULL);
+    }
+
+out:
+    opts_visitor_cleanup(ov);
+
+    QDECREF(pdict);
+    g_free(id);
+    g_free(type);
+    g_free(dummy);
+    if (err) {
+        error_report_err(err);
+        return -1;
+    }
+    return 0;
+}
+
 int main(int argc, char **argv)
 {
     BlockBackend *blk;
@@ -417,6 +485,7 @@ int main(int argc, char **argv)
         { "format", 1, NULL, 'f' },
         { "persistent", 0, NULL, 't' },
         { "verbose", 0, NULL, 'v' },
+        { "object", 1, NULL, QEMU_NBD_OPT_OBJECT },
         { NULL, 0, NULL, 0 }
     };
     int ch;
@@ -434,6 +503,7 @@ int main(int argc, char **argv)
     Error *local_err = NULL;
     BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
     QDict *options = NULL;
+    QemuOpts *opts;
 
     /* The client thread uses SIGTERM to interrupt the server.  A signal
      * handler ensures that "qemu-nbd -v -c" exits with a nice status code.
@@ -442,6 +512,8 @@ int main(int argc, char **argv)
     memset(&sa_sigterm, 0, sizeof(sa_sigterm));
     sa_sigterm.sa_handler = termsig_handler;
     sigaction(SIGTERM, &sa_sigterm, NULL);
+    module_call_init(MODULE_INIT_QOM);
+    qemu_add_opts(&qemu_object_opts);
     qemu_init_exec_dir(argv[0]);
 
     while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
@@ -578,6 +650,13 @@ int main(int argc, char **argv)
             usage(argv[0]);
             exit(0);
             break;
+        case QEMU_NBD_OPT_OBJECT:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         case '?':
             errx(EXIT_FAILURE, "Try `%s --help' for more information.",
                  argv[0]);
@@ -590,6 +669,12 @@ int main(int argc, char **argv)
              argv[0]);
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     if (disconnect) {
         fd = open(argv[optind], O_RDWR);
         if (fd < 0) {
diff --git a/qemu-nbd.texi b/qemu-nbd.texi
index 46fd483..3b2004e 100644
--- a/qemu-nbd.texi
+++ b/qemu-nbd.texi
@@ -14,6 +14,13 @@ Export QEMU disk image using NBD protocol.
 @table @option
 @item @var{filename}
  is a disk image filename
+@item --object type,id=@var{id},...props...
+  define a new instance of the @var{type} object class
+  identified by @var{id}. See the @code{qemu(1)} manual
+  page for full details of the properties supported.
+  The only object type that it makes sense to define
+  is the @code{secret} object, which is used to supply
+  passwords and/or encryption keys.
 @item -p, --port=@var{port}
   port to listen on (default @samp{10809})
 @item -o, --offset=@var{offset}
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 08/30] qemu-io: add support for --object command line arg
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (6 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 07/30] qemu-nbd: " Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 09/30] qemu-io: allow specifying image as a set of options args Daniel P. Berrange
                   ` (21 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Allow creation of user creatable object types with qemu-io
via a --object command line arg. This will be used to supply
passwords and/or encryption keys to the various block driver
backends via the recently added 'secret' object type.

 # echo -n letmein > mypasswd.txt
 # qemu-io --object secret,id=sec0,file=mypasswd.txt \
      ...other args...

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-io.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/qemu-io.c b/qemu-io.c
index 269f17c..cf1dac6 100644
--- a/qemu-io.c
+++ b/qemu-io.c
@@ -21,6 +21,8 @@
 #include "qemu/config-file.h"
 #include "qemu/readline.h"
 #include "qapi/qmp/qstring.h"
+#include "qapi/opts-visitor.h"
+#include "qom/object_interfaces.h"
 #include "sysemu/block-backend.h"
 #include "block/block_int.h"
 #include "trace/control.h"
@@ -205,6 +207,9 @@ static void usage(const char *name)
 "Usage: %s [-h] [-V] [-rsnm] [-f FMT] [-c STRING] ... [file]\n"
 "QEMU Disk exerciser\n"
 "\n"
+"  --object OBJECTDEF   define a object such as 'secret' for\n"
+"                       providing passwords and/or encryption\n"
+"                       keys\n"
 "  -c, --cmd STRING     execute command with its arguments\n"
 "                       from the given string\n"
 "  -f, --format FMT     specifies the block driver to use\n"
@@ -366,6 +371,71 @@ static void reenable_tty_echo(void)
     qemu_set_tty_echo(STDIN_FILENO, true);
 }
 
+enum {
+    OPTION_OBJECT = 258,
+};
+
+static QemuOptsList qemu_object_opts = {
+    .name = "object",
+    .implied_opt_name = "qom-type",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
+    .desc = {
+        { }
+    },
+};
+
+static int object_create(void *opaque, QemuOpts *opts, Error **errp)
+{
+    Error *err = NULL;
+    char *type = NULL;
+    char *id = NULL;
+    void *dummy = NULL;
+    OptsVisitor *ov;
+    QDict *pdict;
+
+    ov = opts_visitor_new(opts);
+    pdict = qemu_opts_to_qdict(opts, NULL);
+
+    visit_start_struct(opts_get_visitor(ov), &dummy, NULL, NULL, 0, &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "qom-type");
+    visit_type_str(opts_get_visitor(ov), &type, "qom-type", &err);
+    if (err) {
+        goto out;
+    }
+
+    qdict_del(pdict, "id");
+    visit_type_str(opts_get_visitor(ov), &id, "id", &err);
+    if (err) {
+        goto out;
+    }
+
+    user_creatable_add(type, id, pdict, opts_get_visitor(ov), &err);
+    if (err) {
+        goto out;
+    }
+    visit_end_struct(opts_get_visitor(ov), &err);
+    if (err) {
+        user_creatable_del(id, NULL);
+    }
+
+out:
+    opts_visitor_cleanup(ov);
+
+    QDECREF(pdict);
+    g_free(id);
+    g_free(type);
+    g_free(dummy);
+    if (err) {
+        error_report_err(err);
+        return -1;
+    }
+    return 0;
+}
+
 int main(int argc, char **argv)
 {
     int readonly = 0;
@@ -384,6 +454,7 @@ int main(int argc, char **argv)
         { "discard", 1, NULL, 'd' },
         { "cache", 1, NULL, 't' },
         { "trace", 1, NULL, 'T' },
+        { "object", 1, NULL, OPTION_OBJECT },
         { NULL, 0, NULL, 0 }
     };
     int c;
@@ -391,6 +462,7 @@ int main(int argc, char **argv)
     int flags = BDRV_O_UNMAP;
     Error *local_error = NULL;
     QDict *opts = NULL;
+    QemuOpts *qopts = NULL;
 
 #ifdef CONFIG_POSIX
     signal(SIGPIPE, SIG_IGN);
@@ -399,6 +471,8 @@ int main(int argc, char **argv)
     progname = basename(argv[0]);
     qemu_init_exec_dir(argv[0]);
 
+    module_call_init(MODULE_INIT_QOM);
+    qemu_add_opts(&qemu_object_opts);
     bdrv_init();
 
     while ((c = getopt_long(argc, argv, sopt, lopt, &opt_index)) != -1) {
@@ -450,6 +524,13 @@ int main(int argc, char **argv)
         case 'h':
             usage(progname);
             exit(0);
+        case OPTION_OBJECT:
+            qopts = qemu_opts_parse_noisily(qemu_find_opts("object"),
+                                            optarg, true);
+            if (!qopts) {
+                exit(1);
+            }
+            break;
         default:
             usage(progname);
             exit(1);
@@ -466,6 +547,12 @@ int main(int argc, char **argv)
         exit(1);
     }
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          NULL, NULL)) {
+        exit(1);
+    }
+
     /* initialize commands */
     qemuio_add_command(&quit_cmd);
     qemuio_add_command(&open_cmd);
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 09/30] qemu-io: allow specifying image as a set of options args
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (7 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 08/30] qemu-io: " Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 10/30] qemu-nbd: " Daniel P. Berrange
                   ` (20 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Currently qemu-io allows an image filename to be passed on the
command line, but does not have a way to set any options except
the format eg

 qemu-io https://127.0.0.1/images/centos7.iso
 qemu-io /home/berrange/demo.qcow2

This adds a --source arg (that is mutually exclusive with a
positional filename arg and -f arg) that accepts a full option
string, as well as the original syntax eg

 qemu-io --source driver=http,url=https://127.0.0.1/images,sslverify=off
 qemu-io --source https://127.0.0.1/images/centos7.iso
 qemu-io --source file=/home/berrange/demo.qcow2
 qemu-io --source /home/berrange/demo.qcow2

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-io.c | 37 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/qemu-io.c b/qemu-io.c
index cf1dac6..fc7f81b 100644
--- a/qemu-io.c
+++ b/qemu-io.c
@@ -373,6 +373,7 @@ static void reenable_tty_echo(void)
 
 enum {
     OPTION_OBJECT = 258,
+    OPTION_SOURCE = 259,
 };
 
 static QemuOptsList qemu_object_opts = {
@@ -436,6 +437,16 @@ out:
     return 0;
 }
 
+static QemuOptsList file_opts = {
+    .name = "file",
+    .implied_opt_name = "file",
+    .head = QTAILQ_HEAD_INITIALIZER(file_opts.head),
+    .desc = {
+        /* no elements => accept any params */
+        { /* end of list */ }
+    },
+};
+
 int main(int argc, char **argv)
 {
     int readonly = 0;
@@ -455,6 +466,7 @@ int main(int argc, char **argv)
         { "cache", 1, NULL, 't' },
         { "trace", 1, NULL, 'T' },
         { "object", 1, NULL, OPTION_OBJECT },
+        { "source", 1, NULL, OPTION_SOURCE },
         { NULL, 0, NULL, 0 }
     };
     int c;
@@ -531,6 +543,12 @@ int main(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (!qemu_opts_parse_noisily(&file_opts, optarg, false)) {
+                qemu_opts_reset(&file_opts);
+                return 0;
+            }
+            break;
         default:
             usage(progname);
             exit(1);
@@ -572,7 +590,24 @@ int main(int argc, char **argv)
         flags |= BDRV_O_RDWR;
     }
 
-    if ((argc - optind) == 1) {
+    qopts = qemu_opts_find(&file_opts, NULL);
+    if (qopts) {
+        char *file;
+        if (opts) {
+            error_report("--source and -f are mutually exclusive");
+            exit(1);
+        }
+        if ((argc - optind) == 1) {
+            error_report("--source and filename are mutually exclusive");
+            exit(1);
+        }
+        file = g_strdup(qemu_opt_get(qopts, "file"));
+        qemu_opt_unset(qopts, "file");
+        opts = qemu_opts_to_qdict(qopts, NULL);
+        qemu_opts_reset(&file_opts);
+        openfile(file, flags, opts);
+        g_free(file);
+    } else if ((argc - optind) == 1) {
         openfile(argv[optind], flags, opts);
     }
     command_loop();
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 10/30] qemu-nbd: allow specifying image as a set of options args
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (8 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 09/30] qemu-io: allow specifying image as a set of options args Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 11/30] qemu-img: " Daniel P. Berrange
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Currently qemu-nbd allows an image filename to be passed on the
command line, but does not have a way to set any options except
the format eg

   qemu-nbd https://127.0.0.1/images/centos7.iso
   qemu-nbd /home/berrange/demo.qcow2

This adds a --source arg (that is mutually exclusive with a
positional filename arg and -f arg) that accepts a full option
string, as well as the original syntax eg

   qemu-nbd --source driver=http,url=https://127.0.0.1/images,sslverify=off
   qemu-nbd --source https://127.0.0.1/images/centos7.iso
   qemu-nbd --source file=/home/berrange/demo.qcow2
   qemu-nbd --source /home/berrange/demo.qcow2

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-nbd.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 50 insertions(+), 7 deletions(-)

diff --git a/qemu-nbd.c b/qemu-nbd.c
index 41f4285..980355e 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -49,6 +49,7 @@
 #define QEMU_NBD_OPT_DISCARD       3
 #define QEMU_NBD_OPT_DETECT_ZEROES 4
 #define QEMU_NBD_OPT_OBJECT        5
+#define QEMU_NBD_OPT_SOURCE        6
 
 static NBDExport *exp;
 static int verbose;
@@ -387,6 +388,16 @@ static SocketAddress *nbd_build_socket_address(const char *sockpath,
 }
 
 
+static QemuOptsList file_opts = {
+    .name = "file",
+    .implied_opt_name = "file",
+    .head = QTAILQ_HEAD_INITIALIZER(file_opts.head),
+    .desc = {
+        /* no elements => accept any params */
+        { /* end of list */ }
+    },
+};
+
 static QemuOptsList qemu_object_opts = {
     .name = "object",
     .implied_opt_name = "qom-type",
@@ -486,6 +497,7 @@ int main(int argc, char **argv)
         { "persistent", 0, NULL, 't' },
         { "verbose", 0, NULL, 'v' },
         { "object", 1, NULL, QEMU_NBD_OPT_OBJECT },
+        { "source", 1, NULL, QEMU_NBD_OPT_SOURCE },
         { NULL, 0, NULL, 0 }
     };
     int ch;
@@ -657,13 +669,23 @@ int main(int argc, char **argv)
                 exit(1);
             }
             break;
+        case QEMU_NBD_OPT_SOURCE:
+            if (srcpath) {
+                errx(EXIT_FAILURE, "--source can only be used once");
+            }
+            if (!qemu_opts_parse_noisily(&file_opts, optarg, true)) {
+                qemu_opts_reset(&file_opts);
+                exit(EXIT_FAILURE);
+            }
+            srcpath = optarg;
+            break;
         case '?':
             errx(EXIT_FAILURE, "Try `%s --help' for more information.",
                  argv[0]);
         }
     }
 
-    if ((argc - optind) != 1) {
+    if ((argc - optind) > 1) {
         errx(EXIT_FAILURE, "Invalid number of argument.\n"
              "Try `%s --help' for more information.",
              argv[0]);
@@ -757,15 +779,36 @@ int main(int argc, char **argv)
     bdrv_init();
     atexit(bdrv_close_all);
 
-    if (fmt) {
-        options = qdict_new();
-        qdict_put(options, "driver", qstring_from_str(fmt));
+    if (srcpath) {
+        char *file = NULL;
+        opts = qemu_opts_find(&file_opts, NULL);
+        if (fmt) {
+            errx(EXIT_FAILURE, "--source and -f are mutually exclusive");
+        }
+        if ((argc - optind) > 1) {
+            errx(EXIT_FAILURE, "--source and filename are mutually exclusive");
+        }
+        file = g_strdup(qemu_opt_get(opts, "file"));
+        qemu_opt_unset(opts, "file");
+        options = qemu_opts_to_qdict(opts, NULL);
+        qemu_opts_reset(&file_opts);
+        blk = blk_new_open("hda", file, NULL, options, flags, &local_err);
+        g_free(file);
+    } else {
+        if (fmt) {
+            options = qdict_new();
+            qdict_put(options, "driver", qstring_from_str(fmt));
+        }
+        if ((argc - optind) != 1) {
+            errx(EXIT_FAILURE, "one of --source or filename are expected");
+        }
+
+        srcpath = argv[optind];
+        blk = blk_new_open("hda", srcpath, NULL, options, flags, &local_err);
     }
 
-    srcpath = argv[optind];
-    blk = blk_new_open("hda", srcpath, NULL, options, flags, &local_err);
     if (!blk) {
-        errx(EXIT_FAILURE, "Failed to blk_new_open '%s': %s", argv[optind],
+        errx(EXIT_FAILURE, "Failed to blk_new_open '%s': %s", srcpath,
              error_get_pretty(local_err));
     }
     bs = blk_bs(blk);
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 11/30] qemu-img: allow specifying image as a set of options args
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (9 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 10/30] qemu-nbd: " Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 12/30] block: rip out all traces of password prompting Daniel P. Berrange
                   ` (18 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Currently qemu-img allows an image filename to be passed on the
command line, but does not have a way to set any options except
the format eg

   qemu-img info https://127.0.0.1/images/centos7.iso

This adds a --source arg (that is mutually exclusive with a
positional filename arg and -f arg) that accepts a full option
string, as well as the original syntax eg

   qemu-img info driver=http,url=https://127.0.0.1/images,sslverify=off

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/qemu/option.h |   1 +
 qemu-img.c            | 474 ++++++++++++++++++++++++++++++++++++++++++--------
 util/qemu-option.c    |   6 +
 3 files changed, 407 insertions(+), 74 deletions(-)

diff --git a/include/qemu/option.h b/include/qemu/option.h
index 71f5f27..caf5a3b 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -104,6 +104,7 @@ int qemu_opt_foreach(QemuOpts *opts, qemu_opt_loopfunc func, void *opaque,
                      Error **errp);
 
 QemuOpts *qemu_opts_find(QemuOptsList *list, const char *id);
+QemuOpts *qemu_opts_next(QemuOpts *opts);
 QemuOpts *qemu_opts_create(QemuOptsList *list, const char *id,
                            int fail_if_exists, Error **errp);
 void qemu_opts_reset(QemuOptsList *list);
diff --git a/qemu-img.c b/qemu-img.c
index 822f193..6f8b6ab 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -38,6 +38,7 @@
 #include "block/blockjob.h"
 #include "block/qapi.h"
 #include <getopt.h>
+#include <err.h>
 
 #define QEMU_IMG_VERSION "qemu-img version " QEMU_VERSION QEMU_PKGVERSION \
                           ", Copyright (c) 2004-2008 Fabrice Bellard\n"
@@ -51,6 +52,8 @@ enum {
     OPTION_OUTPUT = 256,
     OPTION_BACKING_CHAIN = 257,
     OPTION_OBJECT = 258,
+    OPTION_SOURCE = 259,
+    OPTION_TARGET = 260,
 };
 
 typedef enum OutputFormat {
@@ -172,6 +175,16 @@ static QemuOptsList qemu_object_opts = {
     },
 };
 
+static QemuOptsList qemu_source_opts = {
+    .name = "source",
+    .implied_opt_name = "file",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_source_opts.head),
+    .desc = {
+        { }
+    },
+};
+
+
 static int object_create(void *opaque, QemuOpts *opts, Error **errp)
 {
     Error *err = NULL;
@@ -266,9 +279,31 @@ static int print_block_option_help(const char *filename, const char *fmt)
     return 0;
 }
 
-static BlockBackend *img_open(const char *id, const char *filename,
-                              const char *fmt, int flags,
-                              bool require_io, bool quiet)
+static BlockBackend *img_open_opts(const char *id,
+                                   QemuOpts *opts, int flags)
+{
+    QDict *options;
+    Error *local_err = NULL;
+    char *file = NULL;
+    BlockBackend *blk;
+    file = g_strdup(qemu_opt_get(opts, "file"));
+    qemu_opt_unset(opts, "file");
+    options = qemu_opts_to_qdict(opts, NULL);
+    blk = blk_new_open(id, file, NULL, options, flags, &local_err);
+    if (!blk) {
+        error_report("Could not open '%s': %s", file ? file : "",
+                     error_get_pretty(local_err));
+        g_free(file);
+        error_free(local_err);
+        return NULL;
+    }
+    g_free(file);
+    return blk;
+}
+
+static BlockBackend *img_open_file(const char *id, const char *filename,
+                                   const char *fmt, int flags,
+                                   bool require_io, bool quiet)
 {
     BlockBackend *blk;
     BlockDriverState *bs;
@@ -576,7 +611,7 @@ static int img_check(int argc, char **argv)
 {
     int c, ret;
     OutputFormat output_format = OFORMAT_HUMAN;
-    const char *filename, *fmt, *output, *cache;
+    const char *filename = NULL, *fmt, *output, *cache;
     BlockBackend *blk;
     BlockDriverState *bs;
     int fix = 0;
@@ -596,6 +631,7 @@ static int img_check(int argc, char **argv)
             {"repair", required_argument, 0, 'r'},
             {"output", required_argument, 0, OPTION_OUTPUT},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "hf:r:T:q",
@@ -639,12 +675,29 @@ static int img_check(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename) {
+                errx(EXIT_FAILURE, "--source can only be specified once");
+            }
+            filename = optarg;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
-    filename = argv[optind++];
 
     if (output && !strcmp(output, "json")) {
         output_format = OFORMAT_JSON;
@@ -667,7 +720,15 @@ static int img_check(int argc, char **argv)
         return 1;
     }
 
-    blk = img_open("image", filename, fmt, flags, true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            errx(EXIT_FAILURE, "--source and --format are mutually exclusive");
+        }
+        blk = img_open_opts("image", opts, flags);
+    } else {
+        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+    }
     if (!blk) {
         return 1;
     }
@@ -773,7 +834,7 @@ static void run_block_job(BlockJob *job, Error **errp)
 static int img_commit(int argc, char **argv)
 {
     int c, ret, flags;
-    const char *filename, *fmt, *cache, *base;
+    const char *filename = NULL, *fmt, *cache, *base;
     BlockBackend *blk;
     BlockDriverState *bs, *base_bs;
     bool progress = false, quiet = false, drop = false;
@@ -789,6 +850,7 @@ static int img_commit(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:ht:b:dpq",
@@ -828,6 +890,17 @@ static int img_commit(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename) {
+                errx(EXIT_FAILURE, "--source can only be specified once");
+            }
+            filename = optarg;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -836,10 +909,16 @@ static int img_commit(int argc, char **argv)
         progress = false;
     }
 
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
-    filename = argv[optind++];
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
@@ -854,7 +933,15 @@ static int img_commit(int argc, char **argv)
         return 1;
     }
 
-    blk = img_open("image", filename, fmt, flags, true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            errx(EXIT_FAILURE, "--source and --format are mutually exclusive");
+        }
+        blk = img_open_opts("image", opts, flags);
+    } else {
+        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+    }
     if (!blk) {
         return 1;
     }
@@ -1088,7 +1175,8 @@ static int check_empty_sectors(BlockBackend *blk, int64_t sect_num,
  */
 static int img_compare(int argc, char **argv)
 {
-    const char *fmt1 = NULL, *fmt2 = NULL, *cache, *filename1, *filename2;
+    const char *fmt1 = NULL, *fmt2 = NULL, *cache,
+        *filename1 = NULL, *filename2 = NULL;
     BlockBackend *blk1, *blk2;
     BlockDriverState *bs1, *bs2;
     int64_t total_sectors1, total_sectors2;
@@ -1111,6 +1199,7 @@ static int img_compare(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "hf:F:T:pqs",
@@ -1148,6 +1237,20 @@ static int img_compare(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename2) {
+                errx(EXIT_FAILURE, "--source can only be specified twice");
+            } else if (filename1) {
+                filename2 = optarg;
+            } else {
+                filename1 = optarg;
+            }
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -1156,12 +1259,20 @@ static int img_compare(int argc, char **argv)
         progress = false;
     }
 
-
-    if (optind != argc - 2) {
-        error_exit("Expecting two image file names");
+    if (filename1) {
+        if (optind != argc) {
+            error_exit("--source and filenames are mutually exclusive");
+        }
+        if (!filename2) {
+            error_exit("Expecting two --source arguments");
+        }
+    } else {
+        if (optind != argc - 2) {
+            error_exit("Expecting two image file names");
+        }
+        filename1 = argv[optind++];
+        filename2 = argv[optind++];
     }
-    filename1 = argv[optind++];
-    filename2 = argv[optind++];
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
@@ -1180,18 +1291,38 @@ static int img_compare(int argc, char **argv)
         goto out3;
     }
 
-    blk1 = img_open("image_1", filename1, fmt1, flags, true, quiet);
-    if (!blk1) {
-        ret = 2;
-        goto out3;
-    }
-    bs1 = blk_bs(blk1);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt1 || fmt2) {
+            error_report("--source and -f or -F are mutuall exclusive");
+            goto out3;
+        }
 
-    blk2 = img_open("image_2", filename2, fmt2, flags, true, quiet);
-    if (!blk2) {
-        ret = 2;
-        goto out2;
+        blk1 = img_open_opts("image_1", opts, flags);
+        if (!blk1) {
+            ret = 2;
+            goto out3;
+        }
+
+        blk2 = img_open_opts("image_2", qemu_opts_next(opts), flags);
+        if (!blk2) {
+            ret = 2;
+            goto out3;
+        }
+    } else {
+        blk1 = img_open_file("image_1", filename1, fmt1, flags, true, quiet);
+        if (!blk1) {
+            ret = 2;
+            goto out3;
+        }
+
+        blk2 = img_open_file("image_2", filename2, fmt2, flags, true, quiet);
+        if (!blk2) {
+            ret = 2;
+            goto out2;
+        }
     }
+    bs1 = blk_bs(blk1);
     bs2 = blk_bs(blk2);
 
     buf1 = blk_blockalign(blk1, IO_BUF_SIZE);
@@ -1658,10 +1789,11 @@ fail:
 
 static int img_convert(int argc, char **argv)
 {
-    int c, bs_n, bs_i, compress, cluster_sectors, skip_create;
+    int c, bs_n = 0, bs_i, compress, cluster_sectors, skip_create;
     int64_t ret = 0;
     int progress = 0, flags, src_flags;
-    const char *fmt, *out_fmt, *cache, *src_cache, *out_baseimg, *out_filename;
+    const char *fmt, *out_fmt, *cache, *src_cache,
+        *out_baseimg, *out_filename = NULL;
     BlockDriver *drv, *proto_drv;
     BlockBackend **blk = NULL, *out_blk = NULL;
     BlockDriverState **bs = NULL, *out_bs = NULL;
@@ -1692,6 +1824,7 @@ static int img_convert(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "hf:O:B:ce6o:s:l:S:pt:T:qn",
@@ -1793,6 +1926,14 @@ static int img_convert(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            bs_n++;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -1808,20 +1949,33 @@ static int img_convert(int argc, char **argv)
     }
     qemu_progress_init(progress, 1.0);
 
-
-    bs_n = argc - optind - 1;
-    out_filename = bs_n >= 1 ? argv[argc - 1] : NULL;
+    if (!bs_n) {
+        out_filename = (argc - optind - 1) >= 1 ? argv[argc - 1] : NULL;
+    }
 
     if (options && has_help_option(options)) {
         ret = print_block_option_help(out_filename, out_fmt);
         goto out;
     }
 
-    if (bs_n < 1) {
-        error_exit("Must specify image file name");
+    if (bs_n) {
+        if (argc > (optind + 1)) {
+            error_exit("--source and filenames are mutually exclusive");
+        }
+        if (argc != (optind + 1)) {
+            error_exit("Must specify target image file name");
+        }
+        if (!bs_n) {
+            error_exit("At least one --source arg is expected with --target");
+        }
+        out_filename = argv[argc - 1];
+    } else {
+        bs_n = argc - optind - 1;
+        if (bs_n < 1) {
+            error_exit("Must specify image file name");
+        }
     }
 
-
     if (bs_n > 1 && out_baseimg) {
         error_report("-B makes no sense when concatenating multiple input "
                      "images");
@@ -1843,11 +1997,21 @@ static int img_convert(int argc, char **argv)
     bs_sectors = g_new(int64_t, bs_n);
 
     total_sectors = 0;
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
     for (bs_i = 0; bs_i < bs_n; bs_i++) {
         char *id = bs_n > 1 ? g_strdup_printf("source_%d", bs_i)
                             : g_strdup("source");
-        blk[bs_i] = img_open(id, argv[optind + bs_i], fmt, src_flags,
-                             true, quiet);
+        if (opts) {
+            if (fmt) {
+                error_report("--source and -f are mutually exclusive");
+                goto out;
+            }
+            blk[bs_i] = img_open_opts(id, opts, src_flags);
+            opts = qemu_opts_next(opts);
+        } else {
+            blk[bs_i] = img_open_file(id, argv[optind + bs_i], fmt, src_flags,
+                                      true, quiet);
+        }
         g_free(id);
         if (!blk[bs_i]) {
             ret = -1;
@@ -1991,7 +2155,12 @@ static int img_convert(int argc, char **argv)
         goto out;
     }
 
-    out_blk = img_open("target", out_filename, out_fmt, flags, true, quiet);
+    /* XXX we could allow a --target OPTSRING and then use
+     * img_open_opts here, but then we have trouble with
+     * the bdrv_create() call which takes different params
+     */
+    out_blk = img_open_file("target", out_filename,
+                            out_fmt, flags, true, quiet);
     if (!out_blk) {
         ret = -1;
         goto out;
@@ -2158,7 +2327,8 @@ static gboolean str_equal_func(gconstpointer a, gconstpointer b)
  * image file.  If there was an error a message will have been printed to
  * stderr.
  */
-static ImageInfoList *collect_image_info_list(const char *filename,
+static ImageInfoList *collect_image_info_list(QemuOpts *opts,
+                                              const char *filename,
                                               const char *fmt,
                                               bool chain)
 {
@@ -2182,8 +2352,19 @@ static ImageInfoList *collect_image_info_list(const char *filename,
         }
         g_hash_table_insert(filenames, (gpointer)filename, NULL);
 
-        blk = img_open("image", filename, fmt,
-                       BDRV_O_FLAGS | BDRV_O_NO_BACKING, false, false);
+        if (opts) {
+            if (fmt) {
+                error_report("--source and -f are mutually exclusive");
+                goto err;
+            }
+            blk = img_open_opts("image", opts,
+                                BDRV_O_FLAGS | BDRV_O_NO_BACKING);
+            opts = NULL;
+        } else {
+            blk = img_open_file("image", filename, fmt,
+                                BDRV_O_FLAGS | BDRV_O_NO_BACKING,
+                                false, false);
+        }
         if (!blk) {
             goto err;
         }
@@ -2229,7 +2410,7 @@ static int img_info(int argc, char **argv)
     int c;
     OutputFormat output_format = OFORMAT_HUMAN;
     bool chain = false;
-    const char *filename, *fmt, *output;
+    const char *filename = NULL, *fmt, *output;
     ImageInfoList *list;
     QemuOpts *opts;
 
@@ -2243,6 +2424,7 @@ static int img_info(int argc, char **argv)
             {"output", required_argument, 0, OPTION_OUTPUT},
             {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:h",
@@ -2271,12 +2453,29 @@ static int img_info(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename) {
+                error_exit("--source can only be specified once");
+            }
+            filename = optarg;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
-    filename = argv[optind++];
 
     if (output && !strcmp(output, "json")) {
         output_format = OFORMAT_JSON;
@@ -2293,7 +2492,8 @@ static int img_info(int argc, char **argv)
         exit(1);
     }
 
-    list = collect_image_info_list(filename, fmt, chain);
+    list = collect_image_info_list(qemu_opts_find(&qemu_source_opts, NULL),
+                                   filename, fmt, chain);
     if (!list) {
         return 1;
     }
@@ -2412,7 +2612,7 @@ static int img_map(int argc, char **argv)
     OutputFormat output_format = OFORMAT_HUMAN;
     BlockBackend *blk;
     BlockDriverState *bs;
-    const char *filename, *fmt, *output;
+    const char *filename = NULL, *fmt, *output;
     int64_t length;
     MapEntry curr = { .length = 0 }, next;
     int ret = 0;
@@ -2427,6 +2627,7 @@ static int img_map(int argc, char **argv)
             {"format", required_argument, 0, 'f'},
             {"output", required_argument, 0, OPTION_OUTPUT},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:h",
@@ -2452,12 +2653,29 @@ static int img_map(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename) {
+                error_exit("--source can only be specified once");
+            }
+            filename = optarg;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind];
     }
-    filename = argv[optind];
 
     if (output && !strcmp(output, "json")) {
         output_format = OFORMAT_JSON;
@@ -2474,7 +2692,15 @@ static int img_map(int argc, char **argv)
         exit(1);
     }
 
-    blk = img_open("image", filename, fmt, BDRV_O_FLAGS, true, false);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            errx(EXIT_FAILURE, "--source and --format are mutually exclusive");
+        }
+        blk = img_open_opts("image", opts, BDRV_O_FLAGS);
+    } else {
+        blk = img_open_file("image", filename, fmt, BDRV_O_FLAGS, true, false);
+    }
     if (!blk) {
         return 1;
     }
@@ -2533,7 +2759,7 @@ static int img_snapshot(int argc, char **argv)
     BlockBackend *blk;
     BlockDriverState *bs;
     QEMUSnapshotInfo sn;
-    char *filename, *snapshot_name = NULL;
+    char *filename = NULL, *snapshot_name = NULL;
     int c, ret = 0, bdrv_oflags;
     int action = 0;
     qemu_timeval tv;
@@ -2548,6 +2774,7 @@ static int img_snapshot(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "la:c:d:hq",
@@ -2602,13 +2829,30 @@ static int img_snapshot(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            if (filename) {
+                error_exit("--source can only be specified once");
+            }
+            filename = optarg;
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
-    filename = argv[optind++];
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
@@ -2617,7 +2861,12 @@ static int img_snapshot(int argc, char **argv)
     }
 
     /* Open the image */
-    blk = img_open("image", filename, NULL, bdrv_oflags, true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        blk = img_open_opts("image", opts, bdrv_oflags);
+    } else {
+        blk = img_open_file("image", filename, NULL, bdrv_oflags, true, quiet);
+    }
     if (!blk) {
         return 1;
     }
@@ -2675,7 +2924,7 @@ static int img_rebase(int argc, char **argv)
 {
     BlockBackend *blk = NULL, *blk_old_backing = NULL, *blk_new_backing = NULL;
     BlockDriverState *bs = NULL;
-    char *filename;
+    char *filename = NULL;
     const char *fmt, *cache, *src_cache, *out_basefmt, *out_baseimg;
     int c, flags, src_flags, ret;
     int unsafe = 0;
@@ -2695,6 +2944,7 @@ static int img_rebase(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "hf:F:b:upt:T:q",
@@ -2738,6 +2988,13 @@ static int img_rebase(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
 
@@ -2745,13 +3002,20 @@ static int img_rebase(int argc, char **argv)
         progress = 0;
     }
 
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
+
     if (!unsafe && !out_baseimg) {
         error_exit("Must specify backing file (-b) or use unsafe mode (-u)");
     }
-    filename = argv[optind++];
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
@@ -2782,7 +3046,17 @@ static int img_rebase(int argc, char **argv)
      * Ignore the old backing file for unsafe rebase in case we want to correct
      * the reference to a renamed or moved backing file.
      */
-    blk = img_open("image", filename, fmt, flags, true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            error_report("--source and --format are mutually exclusive");
+            ret = -1;
+            goto out;
+        }
+        blk = img_open_opts("image", opts, flags);
+    } else {
+        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+    }
     if (!blk) {
         ret = -1;
         goto out;
@@ -3014,7 +3288,7 @@ static int img_resize(int argc, char **argv)
 {
     Error *err = NULL;
     int c, ret, relative;
-    const char *filename, *fmt, *size;
+    const char *filename = NULL, *fmt, *size;
     int64_t n, total_size;
     bool quiet = false;
     BlockBackend *blk = NULL;
@@ -3050,6 +3324,7 @@ static int img_resize(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "f:hq",
@@ -3075,12 +3350,25 @@ static int img_resize(int argc, char **argv)
                 exit(1);
             }
             break;
+        case OPTION_SOURCE:
+            opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                           optarg, true);
+            if (!opts) {
+                exit(1);
+            }
+            break;
         }
     }
-    if (optind != argc - 1) {
-        error_exit("Expecting one image file name");
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    } else {
+        if (optind != argc - 1) {
+            error_exit("Expecting one image file name");
+        }
+        filename = argv[optind++];
     }
-    filename = argv[optind++];
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
@@ -3115,8 +3403,18 @@ static int img_resize(int argc, char **argv)
     n = qemu_opt_get_size(param, BLOCK_OPT_SIZE, 0);
     qemu_opts_del(param);
 
-    blk = img_open("image", filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR,
-                   true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            error_report("--source and --format are mutually exclusive");
+            ret = -1;
+            goto out;
+        }
+        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);
+    }
     if (!blk) {
         ret = -1;
         goto out;
@@ -3169,7 +3467,7 @@ static int img_amend(int argc, char **argv)
     char *options = NULL;
     QemuOptsList *create_opts = NULL;
     QemuOpts *opts = NULL;
-    const char *fmt = NULL, *filename, *cache;
+    const char *fmt = NULL, *filename = NULL, *cache;
     int flags;
     bool quiet = false, progress = false;
     BlockBackend *blk = NULL;
@@ -3181,6 +3479,7 @@ static int img_amend(int argc, char **argv)
         static const struct option long_options[] = {
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
+            {"source", required_argument, 0, OPTION_SOURCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, "ho:f:t:pq",
@@ -3227,6 +3526,13 @@ static int img_amend(int argc, char **argv)
                     exit(1);
                 }
                 break;
+            case OPTION_SOURCE:
+                opts = qemu_opts_parse_noisily(qemu_find_opts("source"),
+                                               optarg, true);
+                if (!opts) {
+                    exit(1);
+                }
+                break;
         }
     }
 
@@ -3234,6 +3540,12 @@ static int img_amend(int argc, char **argv)
         error_exit("Must specify options (-o)");
     }
 
+    if (filename) {
+        if (optind != argc) {
+            error_exit("--source and filename are mutually exclusive");
+        }
+    }
+
     if (qemu_opts_foreach(qemu_find_opts("object"),
                           object_create,
                           NULL, NULL)) {
@@ -3245,7 +3557,9 @@ static int img_amend(int argc, char **argv)
     }
     qemu_progress_init(progress, 1.0);
 
-    filename = (optind == argc - 1) ? argv[argc - 1] : NULL;
+    if (!filename) {
+        filename = (optind == argc - 1) ? argv[argc - 1] : NULL;
+    }
     if (fmt && has_help_option(options)) {
         /* If a format is explicitly specified (and possibly no filename is
          * given), print option help here */
@@ -3253,8 +3567,9 @@ static int img_amend(int argc, char **argv)
         goto out;
     }
 
-    if (optind != argc - 1) {
-        error_report("Expecting one image file name");
+    if (!filename &&
+        (optind != argc - 1)) {
+        error_exit("Expecting one image file name");
         ret = -1;
         goto out;
     }
@@ -3266,7 +3581,17 @@ static int img_amend(int argc, char **argv)
         goto out;
     }
 
-    blk = img_open("image", filename, fmt, flags, true, quiet);
+    opts = qemu_opts_find(&qemu_source_opts, NULL);
+    if (opts) {
+        if (fmt) {
+            error_report("--source and --format are mutually exclusive");
+            ret = -1;
+            goto out;
+        }
+        blk = img_open_opts("image", opts, BDRV_O_FLAGS | BDRV_O_RDWR);
+    } else {
+        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+    }
     if (!blk) {
         ret = -1;
         goto out;
@@ -3364,6 +3689,7 @@ int main(int argc, char **argv)
 
     module_call_init(MODULE_INIT_QOM);
     qemu_add_opts(&qemu_object_opts);
+    qemu_add_opts(&qemu_source_opts);
 
     /* find the command */
     for (cmd = img_cmds; cmd->name != NULL; cmd++) {
diff --git a/util/qemu-option.c b/util/qemu-option.c
index a50ecea..48e1cc5 100644
--- a/util/qemu-option.c
+++ b/util/qemu-option.c
@@ -637,6 +637,12 @@ QemuOpts *qemu_opts_find(QemuOptsList *list, const char *id)
     return NULL;
 }
 
+
+QemuOpts *qemu_opts_next(QemuOpts *opts)
+{
+    return QTAILQ_NEXT(opts, next);
+}
+
 QemuOpts *qemu_opts_create(QemuOptsList *list, const char *id,
                            int fail_if_exists, Error **errp)
 {
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 12/30] block: rip out all traces of password prompting
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (10 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 11/30] qemu-img: " Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 13/30] block: remove all encryption handling APIs Daniel P. Berrange
                   ` (17 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Now that qcow & qcow2 are wired up to get encryption keys
via the QCryptoSecret object, all traces of code which
had to deal with prompting for passwords can be ripped
out.

When the image is initially opened, the encryption key
must be available immediately, or an error will be
reported.

$ qemu-system-x86_64 -drive file=secret.qcow2
qemu-system-x86_64: -drive file=secret.qcow2: Image is encrypted but no secret is provided

Users must provide the secret with -object

$ echo 123456 > mypasswd.txt
qemu-system-x86_64 -drive file=secret.qcow2,keysecret=sec0 -object secret,file=mypasswd.txt,id=sec0

The BDRV_O_NO_IO flag allows this error to be skipped,
for use when 'qemu-img info' wants to open the file
to query the headers, but not perform any actual I/O
operations.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow.c                 |  7 +++++
 block/qcow2.c                | 13 +++++++--
 hmp.c                        | 31 --------------------
 hw/usb/dev-storage.c         | 34 ----------------------
 include/block/block.h        |  1 +
 include/monitor/monitor.h    |  7 -----
 include/qemu/osdep.h         |  2 --
 monitor.c                    | 69 --------------------------------------------
 qemu-img.c                   | 46 +++++++++++------------------
 qemu-io.c                    | 21 --------------
 qmp.c                        |  9 ------
 tests/qemu-iotests/087       | 20 +++++++++++++
 tests/qemu-iotests/087.out   |  2 ++
 tests/qemu-iotests/134       | 17 +++++++----
 tests/qemu-iotests/134.out   |  8 -----
 tests/qemu-iotests/common.rc |  4 +--
 util/oslib-posix.c           | 66 ------------------------------------------
 util/oslib-win32.c           | 24 ---------------
 18 files changed, 72 insertions(+), 309 deletions(-)

diff --git a/block/qcow.c b/block/qcow.c
index 719ed7c..ff80ef5 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -231,6 +231,13 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     if (s->crypt_method_header) {
         bs->encrypted = 1;
     }
+    if (!(flags & BDRV_O_NO_IO) &&
+        bs->encrypted && !s->cipher) {
+        error_setg(errp, "Image is encrypted but no secret is provided");
+        ret = -EINVAL;
+        goto fail;
+    }
+
     s->cluster_bits = header.cluster_bits;
     s->cluster_size = 1 << s->cluster_bits;
     s->cluster_sectors = 1 << (s->cluster_bits - 9);
diff --git a/block/qcow2.c b/block/qcow2.c
index 1e6e975..d326148 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1136,6 +1136,13 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
+    if (!(flags & BDRV_O_NO_IO) &&
+        bs->encrypted && !s->cipher) {
+        error_setg(errp, "Image is encrypted but no secret is provided");
+        ret = -EINVAL;
+        goto fail;
+    }
+
     s->cluster_cache = g_malloc(s->cluster_size);
     /* one more sector for decompressed data alignment */
     s->cluster_data = qemu_try_blockalign(bs->file->bs, QCOW_MAX_CRYPT_CLUSTERS
@@ -2207,7 +2214,8 @@ static int qcow2_create2(const char *filename, int64_t total_size,
     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_FLUSH,
+                    BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH |
+                    BDRV_O_NO_IO,
                     &local_err);
     if (ret < 0) {
         error_propagate(errp, local_err);
@@ -2261,7 +2269,8 @@ static int qcow2_create2(const char *filename, int64_t total_size,
     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,
                     &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
diff --git a/hmp.c b/hmp.c
index 6044db3..ed08e0b 100644
--- a/hmp.c
+++ b/hmp.c
@@ -962,37 +962,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)
@@ -1379,12 +1354,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/block/block.h b/include/block/block.h
index 73edb1a..ccd2f72 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/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 861d84b..6e04f44 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
@@ -295,8 +295,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 e4cf34e..09e0df2 100644
--- a/monitor.c
+++ b/monitor.c
@@ -4138,75 +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) {
-        monitor_printf(mon, "%s\n", error_get_pretty(local_err));
-        error_free(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 6f8b6ab..54a3e6b 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -303,11 +303,9 @@ 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;
-    char password[256];
     Error *local_err = NULL;
     QDict *options = NULL;
 
@@ -324,18 +322,6 @@ static BlockBackend *img_open_file(const char *id, const char *filename,
         goto fail;
     }
 
-    bs = blk_bs(blk);
-    if (bdrv_is_encrypted(bs) && require_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);
@@ -727,7 +713,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;
@@ -940,7 +926,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;
@@ -1310,13 +1296,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;
@@ -2010,7 +1996,7 @@ static int img_convert(int argc, char **argv)
             opts = qemu_opts_next(opts);
         } 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]) {
@@ -2160,7 +2146,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;
@@ -2358,12 +2344,14 @@ static ImageInfoList *collect_image_info_list(QemuOpts *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;
@@ -2699,7 +2687,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;
@@ -2865,7 +2853,7 @@ static int img_snapshot(int argc, char **argv)
     if (opts) {
         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;
@@ -3055,7 +3043,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;
@@ -3413,7 +3401,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;
@@ -3590,7 +3578,7 @@ static int img_amend(int argc, char **argv)
         }
         blk = img_open_opts("image", opts, BDRV_O_FLAGS | BDRV_O_RDWR);
     } else {
-        blk = img_open_file("image", filename, fmt, flags, true, quiet);
+        blk = img_open_file("image", filename, fmt, flags, quiet);
     }
     if (!blk) {
         ret = -1;
diff --git a/qemu-io.c b/qemu-io.c
index fc7f81b..0018bfa 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) {
         fprintf(stderr, "file open already, try 'help close'\n");
@@ -73,27 +72,7 @@ static int openfile(char *name, int flags, QDict *opts)
         return 1;
     }
 
-    bs = blk_bs(qemuio_blk);
-    if (bdrv_is_encrypted(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 f38d597..025d4bc 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 af44299..a1498b6 100755
--- a/tests/qemu-iotests/087
+++ b/tests/qemu-iotests/087
@@ -187,11 +187,21 @@ echo
 _make_test_img -o encryption=on $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": {
         "driver": "$IMGFMT",
         "id": "disk",
+        "keyid": "sec0",
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
@@ -204,11 +214,21 @@ EOF
 
 run_qemu <<EOF
 { "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+  "arguments": {
+      "qom-type": "secret",
+      "id": "sec0",
+      "props": {
+         "data": "123456"
+      }
+  }
+}
 { "execute": "blockdev-add",
   "arguments": {
       "options": {
         "driver": "$IMGFMT",
         "id": "disk",
+        "keyid": "sec0",
         "file": {
             "driver": "file",
             "filename": "$TEST_IMG"
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 7d62cd5..85752bb 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -48,6 +48,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 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 +59,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.
diff --git a/tests/qemu-iotests/134 b/tests/qemu-iotests/134
index 1c3820b..a247473 100755
--- a/tests/qemu-iotests/134
+++ b/tests/qemu-iotests/134
@@ -44,23 +44,30 @@ _supported_os Linux
 
 
 size=128M
-IMGOPTS="encryption=on" _make_test_img $size
+SECRET1=secret,id=sec0,data=astrochicken
+SECRET2=secret,id=sec0,data=platypus
+
+EXTRA_IMG_ARGS="--object $SECRET1" IMGOPTS="encryption=on" _make_test_img $size
+
+
+QEMU_IO_OPTIONS=`echo $QEMU_IO_OPTIONS | sed -e 's/-f qcow2//'`
+TEST_IMG="driver=qcow2,file=$TEST_IMG,keyid=sec0"
 
 echo
 echo "== reading whole image =="
-echo "astrochicken" | $QEMU_IO -c "read 0 $size" "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET1 -c "read 0 $size" --source "$TEST_IMG" | _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 $SECRET1  -c "write -P 0xa 0 $size" --source "$TEST_IMG" | _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 $SECRET1  -c "read -P 0xa 0 $size" --source "$TEST_IMG" | _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 $SECRET2 -c "read -P 0xa 0 $size" --source "$TEST_IMG" | _filter_qemu_io | _filter_testdir
 
 
 # success, all done
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index a16acb8..845aa57 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -11,8 +11,6 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 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 +18,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,8 +25,6 @@ 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)
 
@@ -38,8 +32,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:
 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)
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 914cef5..73cb9be 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
@@ -406,72 +406,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 09f9e98..1ae5cc4 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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 13/30] block: remove all encryption handling APIs
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (11 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 12/30] block: rip out all traces of password prompting Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 14/30] block: remove support for writing to qcow/qcow2 encrypted images Daniel P. Berrange
                   ` (16 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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                    | 81 ++--------------------------------------------
 block/qapi.c               |  2 +-
 block/qcow.c               | 19 -----------
 block/qcow2.c              | 19 -----------
 blockdev.c                 | 40 ++---------------------
 include/block/block.h      |  4 ---
 tests/qemu-iotests/087.out |  4 +--
 7 files changed, 7 insertions(+), 162 deletions(-)

diff --git a/block.c b/block.c
index 3a7324b..f6d6067 100644
--- a/block.c
+++ b/block.c
@@ -1550,17 +1550,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);
@@ -2530,74 +2521,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/qapi.c b/block/qapi.c
index d20262d..e5e2e80 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 ff80ef5..ccf6de1 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -222,11 +222,6 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
         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;
@@ -335,19 +330,6 @@ static int qcow_reopen_prepare(BDRVReopenState *state,
     return 0;
 }
 
-static int qcow_set_key(BlockDriverState *bs, const char *key)
-{
-    BDRVQcowState *s = bs->opaque;
-
-    assert(bs->encrypted);
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = qcow_get_cipher_from_key(key, NULL);
-    if (!s->cipher) {
-        return -1;
-    }
-    return 0;
-}
-
 /* The crypt function is compatible with the linux cryptoloop
    algorithm for < 4 GB images. NOTE: out_buf == in_buf is
    supported */
@@ -1078,7 +1060,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/block/qcow2.c b/block/qcow2.c
index d326148..900eed1 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1026,11 +1026,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
         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;
@@ -1262,19 +1257,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;
-
-    assert(bs->encrypted);
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = qcow2_get_cipher_from_key(key, NULL);
-    if (!s->cipher) {
-        return -1;
-    }
-    return 0;
-}
-
 static int qcow2_reopen_prepare(BDRVReopenState *state,
                                 BlockReopenQueue *queue, Error **errp)
 {
@@ -3191,7 +3173,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/blockdev.c b/blockdev.c
index 917ae06..2c90b5e 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -624,10 +624,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)) {
@@ -2263,24 +2259,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,
@@ -2507,12 +2487,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);
@@ -3755,16 +3729,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 ccd2f72..ba42688 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -412,10 +412,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/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 85752bb..2b55b1b 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -52,7 +52,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"}
 
@@ -63,7 +63,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"}
 
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 14/30] block: remove support for writing to qcow/qcow2 encrypted images
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (12 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 13/30] block: remove all encryption handling APIs Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 15/30] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Refuse to open a qcow/qcow2 image with encryption if write
access has been requested. To enable historic data to be
liberated support for reading images is retained, as it
does not pose an unreasonable support burden now that the
new key handling infrastructure is inplace.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block.c                    |  7 -------
 block/qcow.c               |  6 ++++++
 block/qcow2.c              |  6 ++++++
 tests/qemu-iotests/087.out | 24 ++++--------------------
 tests/qemu-iotests/134     |  8 ++++----
 tests/qemu-iotests/134.out | 36 +++++++++---------------------------
 6 files changed, 29 insertions(+), 58 deletions(-)

diff --git a/block.c b/block.c
index f6d6067..7aa7ffa 100644
--- a/block.c
+++ b/block.c
@@ -920,13 +920,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 ccf6de1..1914e5e 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -224,6 +224,12 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
     }
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
+        if (flags & BDRV_O_RDWR) {
+            error_setg(errp,
+                       "Writing of encrypted qcow images is no longer supported");
+            ret = -ENOSYS;
+            goto fail;
+        }
         bs->encrypted = 1;
     }
     if (!(flags & BDRV_O_NO_IO) &&
diff --git a/block/qcow2.c b/block/qcow2.c
index 900eed1..3ac5a12 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1028,6 +1028,12 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     }
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
+        if (flags & BDRV_O_RDWR) {
+            error_setg(errp,
+                       "Writing of encrypted qcow2 images is no longer supported");
+            ret = -ENOSYS;
+            goto fail;
+        }
         bs->encrypted = 1;
     }
 
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 2b55b1b..d98881e 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -38,21 +38,13 @@ 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.
-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: TEST_DIR/t.IMGFMT: Writing of encrypted IMGFMT images is no longer supported
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 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": {}}
+{"error": {"class": "GenericError", "desc": "Writing of encrypted qcow2 images is no longer supported"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
 
@@ -60,22 +52,14 @@ 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": {}}
+{"error": {"class": "GenericError", "desc": "Writing of encrypted qcow2 images is no longer supported"}}
 {"return": {}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "SHUTDOWN"}
 
 
 === 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.
-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: TEST_DIR/t.IMGFMT: Writing of encrypted IMGFMT images is no longer supported
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 Testing: -S
 QMP_VERSION
diff --git a/tests/qemu-iotests/134 b/tests/qemu-iotests/134
index a247473..5b094bc 100755
--- a/tests/qemu-iotests/134
+++ b/tests/qemu-iotests/134
@@ -55,19 +55,19 @@ TEST_IMG="driver=qcow2,file=$TEST_IMG,keyid=sec0"
 
 echo
 echo "== reading whole image =="
-$QEMU_IO --object $SECRET1 -c "read 0 $size" --source "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET1 -c "read 0 $size" --source "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== rewriting whole image =="
-$QEMU_IO --object $SECRET1  -c "write -P 0xa 0 $size" --source "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET1  -c "write -P 0xa 0 $size" --source "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== verify pattern =="
-$QEMU_IO --object $SECRET1  -c "read -P 0xa 0 $size" --source "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET1  -c "read -P 0xa 0 $size" --source "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
 
 echo
 echo "== verify pattern failure with wrong password =="
-$QEMU_IO --object $SECRET2 -c "read -P 0xa 0 $size" --source "$TEST_IMG" | _filter_qemu_io | _filter_testdir
+$QEMU_IO --object $SECRET2 -c "read -P 0xa 0 $size" --source "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
 
 
 # success, all done
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index 845aa57..f386ebf 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -1,38 +1,20 @@
 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.
+qemu-img: TEST_DIR/t.IMGFMT: Writing of encrypted IMGFMT images is no longer supported
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on
 
 == 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)
+qemu-io: can't open device TEST_DIR/t.qcow2: Writing of encrypted qcow2 images is no longer supported
+no file open, try 'help open'
 
 == 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)
+qemu-io: can't open device TEST_DIR/t.qcow2: Writing of encrypted qcow2 images is no longer supported
+no file open, try 'help open'
 
 == 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)
+qemu-io: can't open device TEST_DIR/t.qcow2: Writing of encrypted qcow2 images is no longer supported
+no file open, try 'help open'
 
 == 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.
-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)
+qemu-io: can't open device TEST_DIR/t.qcow2: Writing of encrypted qcow2 images is no longer supported
+no file open, try 'help open'
 *** done
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 15/30] qcow2: make qcow2_encrypt_sectors encrypt in place
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (13 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 14/30] block: remove support for writing to qcow/qcow2 encrypted images Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 16/30] crypto: add ability to query the cipher key, block & IV lens Daniel P. Berrange
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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 24a60e2..c24b580 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 3ac5a12..c16f899 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1487,7 +1487,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;
@@ -1587,8 +1587,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 376f83f..ea31b25 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -537,8 +537,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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 16/30] crypto: add ability to query the cipher key, block & IV lens
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (14 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 15/30] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 17/30] crypto: add method for querying hash digest size Daniel P. Berrange
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Adds new methods to allow querying the length of the cipher
key, block size and initialization vectors.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/cipher.c            | 48 ++++++++++++++++++++++++++++++++++++++++++++++
 include/crypto/cipher.h    | 37 +++++++++++++++++++++++++++++++++++
 tests/test-crypto-cipher.c | 10 ++++++++++
 3 files changed, 95 insertions(+)

diff --git a/crypto/cipher.c b/crypto/cipher.c
index c8bd180..d02bb32 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -28,6 +28,54 @@ static size_t alg_key_len[QCRYPTO_CIPHER_ALG_LAST] = {
     [QCRYPTO_CIPHER_ALG_DES_RFB] = 8,
 };
 
+static size_t alg_block_len[QCRYPTO_CIPHER_ALG_LAST] = {
+    [QCRYPTO_CIPHER_ALG_AES_128] = 16,
+    [QCRYPTO_CIPHER_ALG_AES_192] = 16,
+    [QCRYPTO_CIPHER_ALG_AES_256] = 16,
+    [QCRYPTO_CIPHER_ALG_DES_RFB] = 8,
+};
+
+static bool mode_need_iv[QCRYPTO_CIPHER_MODE_LAST] = {
+    [QCRYPTO_CIPHER_MODE_ECB] = false,
+    [QCRYPTO_CIPHER_MODE_CBC] = true,
+};
+
+
+size_t qcrypto_cipher_get_block_len(QCryptoCipherAlgorithm alg)
+{
+    if (alg >= G_N_ELEMENTS(alg_key_len)) {
+        return 0;
+    }
+    return alg_block_len[alg];
+}
+
+
+size_t qcrypto_cipher_get_key_len(QCryptoCipherAlgorithm alg)
+{
+    if (alg >= G_N_ELEMENTS(alg_key_len)) {
+        return 0;
+    }
+    return alg_key_len[alg];
+}
+
+
+size_t qcrypto_cipher_get_iv_len(QCryptoCipherAlgorithm alg,
+                                 QCryptoCipherMode mode)
+{
+    if (alg >= G_N_ELEMENTS(alg_block_len)) {
+        return 0;
+    }
+    if (mode >= G_N_ELEMENTS(mode_need_iv)) {
+        return 0;
+    }
+
+    if (mode_need_iv[mode]) {
+        return alg_block_len[alg];
+    }
+    return 0;
+}
+
+
 static bool
 qcrypto_cipher_validate_key_length(QCryptoCipherAlgorithm alg,
                                    size_t nkey,
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h
index b4d714f..aa51c89 100644
--- a/include/crypto/cipher.h
+++ b/include/crypto/cipher.h
@@ -107,6 +107,43 @@ struct QCryptoCipher {
  */
 bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg);
 
+/**
+ * qcrypto_cipher_get_block_len:
+ * @alg: the cipher algorithm
+ *
+ * Get the required data block size in bytes. When
+ * encrypting data, it must be a multiple of the
+ * block size.
+ *
+ * Returns: the block size in bytes
+ */
+size_t qcrypto_cipher_get_block_len(QCryptoCipherAlgorithm alg);
+
+
+/**
+ * qcrypto_cipher_get_key_len:
+ * @alg: the cipher algorithm
+ *
+ * Get the required key size in bytes.
+ *
+ * Returns: the key size in bytes
+ */
+size_t qcrypto_cipher_get_key_len(QCryptoCipherAlgorithm alg);
+
+
+/**
+ * qcrypto_cipher_get_iv_len:
+ * @alg: the cipher algorithm
+ * @mode: the cipher mode
+ *
+ * Get the required initialization vector size
+ * in bytes, if one is required.
+ *
+ * Returns: the IV size in bytes, or 0 if no IV is permitted
+ */
+size_t qcrypto_cipher_get_iv_len(QCryptoCipherAlgorithm alg,
+                                 QCryptoCipherMode mode);
+
 
 /**
  * qcrypto_cipher_new:
diff --git a/tests/test-crypto-cipher.c b/tests/test-crypto-cipher.c
index f4946a0..c687307 100644
--- a/tests/test-crypto-cipher.c
+++ b/tests/test-crypto-cipher.c
@@ -229,6 +229,7 @@ static void test_cipher(const void *opaque)
     uint8_t *key, *iv, *ciphertext, *plaintext, *outtext;
     size_t nkey, niv, nciphertext, nplaintext;
     char *outtexthex;
+    size_t ivsize, keysize, blocksize;
 
     nkey = unhex_string(data->key, &key);
     niv = unhex_string(data->iv, &iv);
@@ -245,6 +246,15 @@ static void test_cipher(const void *opaque)
         &error_abort);
     g_assert(cipher != NULL);
 
+    keysize = qcrypto_cipher_get_key_len(data->alg);
+    blocksize = qcrypto_cipher_get_block_len(data->alg);
+    ivsize = qcrypto_cipher_get_iv_len(data->alg, data->mode);
+
+    g_assert_cmpint(keysize, ==, nkey);
+    g_assert_cmpint(ivsize, ==, niv);
+    if (niv) {
+        g_assert_cmpint(blocksize, ==, niv);
+    }
 
     if (iv) {
         g_assert(qcrypto_cipher_setiv(cipher,
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 17/30] crypto: add method for querying hash digest size
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (15 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 16/30] crypto: add ability to query the cipher key, block & IV lens Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 18/30] crypto: move QCryptoHashAlgorithm enum definition into QAPI Daniel P. Berrange
                   ` (12 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Add a qcrypto_hash_digest_len() method which allows querying of
the raw digest size for a given hash algorithm.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/hash.c            | 15 +++++++++++++++
 include/crypto/hash.h    | 11 +++++++++++
 tests/test-crypto-hash.c |  5 +++++
 3 files changed, 31 insertions(+)

diff --git a/crypto/hash.c b/crypto/hash.c
index 81e74de..5a47b90 100644
--- a/crypto/hash.c
+++ b/crypto/hash.c
@@ -30,6 +30,12 @@ static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG_LAST] = {
     [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256,
 };
 
+static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG_LAST] = {
+    [QCRYPTO_HASH_ALG_MD5] = 16,
+    [QCRYPTO_HASH_ALG_SHA1] = 20,
+    [QCRYPTO_HASH_ALG_SHA256] = 32,
+};
+
 gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg)
 {
     if (alg < G_N_ELEMENTS(qcrypto_hash_alg_map)) {
@@ -38,6 +44,15 @@ gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg)
     return false;
 }
 
+size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg)
+{
+    if (alg >= G_N_ELEMENTS(qcrypto_hash_alg_size)) {
+        return 0;
+    }
+    return qcrypto_hash_alg_size[alg];
+}
+
+
 int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg,
                         const struct iovec *iov,
                         size_t niov,
diff --git a/include/crypto/hash.h b/include/crypto/hash.h
index b5acbf6..3d18124 100644
--- a/include/crypto/hash.h
+++ b/include/crypto/hash.h
@@ -44,6 +44,17 @@ typedef enum {
  */
 gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg);
 
+
+/**
+ * qcrypto_hash_digest_len:
+ * @alg: the hash algorithm
+ *
+ * Determine the size of the hash digest in bytes
+ *
+ * Returns: the digest length in bytes
+ */
+size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg);
+
 /**
  * qcrypto_hash_bytesv:
  * @alg: the hash algorithm
diff --git a/tests/test-crypto-hash.c b/tests/test-crypto-hash.c
index 911437e..41721d0 100644
--- a/tests/test-crypto-hash.c
+++ b/tests/test-crypto-hash.c
@@ -163,6 +163,11 @@ static void test_hash_digest(void)
     for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
         int ret;
         char *digest;
+        size_t digestsize;
+
+        digestsize = qcrypto_hash_digest_len(i);
+
+        g_assert((digestsize * 2) == strlen(expected_outputs[i]));
 
         ret = qcrypto_hash_digest(i,
                                   INPUT_TEXT,
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 18/30] crypto: move QCryptoHashAlgorithm enum definition into QAPI
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (16 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 17/30] crypto: add method for querying hash digest size Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 19/30] crypto: move QCryptoCipherAlgorithm/Mode enum definitions " Daniel P. Berrange
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The QCryptoHashAlgorithm enum is defined in the crypto/hash.h
header. In the future some QAPI types will want to reference
the hash enums, so move the enum definition into QAPI too.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/hash.c         |  4 ++--
 include/crypto/hash.h |  9 +--------
 qapi/crypto.json      | 15 +++++++++++++++
 3 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/crypto/hash.c b/crypto/hash.c
index 5a47b90..b5f81c4 100644
--- a/crypto/hash.c
+++ b/crypto/hash.c
@@ -24,13 +24,13 @@
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
 
-static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG_LAST] = {
+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_LAST] = {
+static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG_MAX] = {
     [QCRYPTO_HASH_ALG_MD5] = 16,
     [QCRYPTO_HASH_ALG_SHA1] = 20,
     [QCRYPTO_HASH_ALG_SHA256] = 32,
diff --git a/include/crypto/hash.h b/include/crypto/hash.h
index 3d18124..41822c0 100644
--- a/include/crypto/hash.h
+++ b/include/crypto/hash.h
@@ -24,14 +24,7 @@
 #include "qemu-common.h"
 #include "qapi/error.h"
 
-typedef enum {
-    QCRYPTO_HASH_ALG_MD5,
-    QCRYPTO_HASH_ALG_SHA1,
-    QCRYPTO_HASH_ALG_SHA256,
-
-    QCRYPTO_HASH_ALG_LAST
-} QCryptoHashAlgorithm;
-
+/* See also "QCryptoHashAlgorithm" defined in qapi/crypto.json */
 
 /**
  * qcrypto_hash_supports:
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 88d9ead..a83be0e 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -33,3 +33,18 @@
 { 'enum': 'QCryptoSecretFormat',
   'prefix': 'QCRYPTO_SECRET_FORMAT',
   'data': ['raw', 'base64']}
+
+
+##
+# QCryptoHashAlgorithm:
+#
+# The supported algorithms for computing content digests
+#
+# @md5: MD5. Should not be used in any new code, legacy compat only
+# @sha1: SHA-1. Should not be used in any new code, legacy compat only
+# @sha256: SHA-256. Current recommended strong hash.
+# Since: 2.6
+##
+{ 'enum': 'QCryptoHashAlgorithm',
+  'prefix': 'QCRYPTO_HASH_ALG',
+  'data': ['md5', 'sha1', 'sha256']}
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 19/30] crypto: move QCryptoCipherAlgorithm/Mode enum definitions into QAPI
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (17 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 18/30] crypto: move QCryptoHashAlgorithm enum definition into QAPI Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 20/30] crypto: ensure qapi/crypto.json is listed in qapi-modules Daniel P. Berrange
                   ` (10 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The QCryptoCipherAlgorithm and QCryptoCipherMode enums are
defined in the crypto/cipher.h header. In the future some
QAPI types will want to reference the hash enums, so move
the enum definition into QAPI too.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/cipher.c         |  8 ++++----
 include/crypto/cipher.h | 17 ++---------------
 qapi/crypto.json        | 30 ++++++++++++++++++++++++++++++
 3 files changed, 36 insertions(+), 19 deletions(-)

diff --git a/crypto/cipher.c b/crypto/cipher.c
index d02bb32..e92d49a 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -21,21 +21,21 @@
 #include "crypto/cipher.h"
 
 
-static size_t alg_key_len[QCRYPTO_CIPHER_ALG_LAST] = {
+static size_t alg_key_len[QCRYPTO_CIPHER_ALG_MAX] = {
     [QCRYPTO_CIPHER_ALG_AES_128] = 16,
     [QCRYPTO_CIPHER_ALG_AES_192] = 24,
     [QCRYPTO_CIPHER_ALG_AES_256] = 32,
     [QCRYPTO_CIPHER_ALG_DES_RFB] = 8,
 };
 
-static size_t alg_block_len[QCRYPTO_CIPHER_ALG_LAST] = {
+static size_t alg_block_len[QCRYPTO_CIPHER_ALG_MAX] = {
     [QCRYPTO_CIPHER_ALG_AES_128] = 16,
     [QCRYPTO_CIPHER_ALG_AES_192] = 16,
     [QCRYPTO_CIPHER_ALG_AES_256] = 16,
     [QCRYPTO_CIPHER_ALG_DES_RFB] = 8,
 };
 
-static bool mode_need_iv[QCRYPTO_CIPHER_MODE_LAST] = {
+static bool mode_need_iv[QCRYPTO_CIPHER_MODE_MAX] = {
     [QCRYPTO_CIPHER_MODE_ECB] = false,
     [QCRYPTO_CIPHER_MODE_CBC] = true,
 };
@@ -81,7 +81,7 @@ qcrypto_cipher_validate_key_length(QCryptoCipherAlgorithm alg,
                                    size_t nkey,
                                    Error **errp)
 {
-    if ((unsigned)alg >= QCRYPTO_CIPHER_ALG_LAST) {
+    if ((unsigned)alg >= QCRYPTO_CIPHER_ALG_MAX) {
         error_setg(errp, "Cipher algorithm %d out of range",
                    alg);
         return false;
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h
index aa51c89..a812803 100644
--- a/include/crypto/cipher.h
+++ b/include/crypto/cipher.h
@@ -26,21 +26,8 @@
 
 typedef struct QCryptoCipher QCryptoCipher;
 
-typedef enum {
-    QCRYPTO_CIPHER_ALG_AES_128,
-    QCRYPTO_CIPHER_ALG_AES_192,
-    QCRYPTO_CIPHER_ALG_AES_256,
-    QCRYPTO_CIPHER_ALG_DES_RFB, /* A stupid variant on DES for VNC */
-
-    QCRYPTO_CIPHER_ALG_LAST
-} QCryptoCipherAlgorithm;
-
-typedef enum {
-    QCRYPTO_CIPHER_MODE_ECB,
-    QCRYPTO_CIPHER_MODE_CBC,
-
-    QCRYPTO_CIPHER_MODE_LAST
-} QCryptoCipherMode;
+/* See also "QCryptoCipherAlgorithm" and "QCryptoCipherMode"
+ * enums defined in qapi/crypto.json */
 
 /**
  * QCryptoCipher:
diff --git a/qapi/crypto.json b/qapi/crypto.json
index a83be0e..2cea2bd 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -48,3 +48,33 @@
 { 'enum': 'QCryptoHashAlgorithm',
   'prefix': 'QCRYPTO_HASH_ALG',
   'data': ['md5', 'sha1', 'sha256']}
+
+
+##
+# QCryptoCipherAlgorithm:
+#
+# The supported algorithms for content encryption ciphers
+#
+# @aes-128: AES with 128 bit / 16 byte keys
+# @aes-192: AES with 192 bit / 24 byte keys
+# @aes-256: AES with 256 bit / 32 byte keys
+# @des-rfb: RFB specific variant of single DES. Do not use except in VNC.
+# Since: 2.6
+##
+{ 'enum': 'QCryptoCipherAlgorithm',
+  'prefix': 'QCRYPTO_CIPHER_ALG',
+  'data': ['aes-128', 'aes-192', 'aes-256', 'des-rfb']}
+
+
+##
+# QCryptoCipherMode:
+#
+# The supported modes for content encryption ciphers
+#
+# @ecb: Electronic Code Book
+# @cbc: Cipher Block Chaining
+# Since: 2.6
+##
+{ 'enum': 'QCryptoCipherMode',
+  'prefix': 'QCRYPTO_CIPHER_MODE',
+  'data': ['ecb', 'cbc']}
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 20/30] crypto: ensure qapi/crypto.json is listed in qapi-modules
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (18 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 19/30] crypto: move QCryptoCipherAlgorithm/Mode enum definitions " Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 21/30] crypto: add cryptographic random byte source Daniel P. Berrange
                   ` (9 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The rebuild of qapi-types.c/h is not correctly triggered
when qapi/crypto.json is changed because it was missing
from the list of files in the qapi-modules variable.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index c7fa427..949b2c5 100644
--- a/Makefile
+++ b/Makefile
@@ -269,7 +269,8 @@ $(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
 
 qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
-               $(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json
+               $(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json \
+               $(SRC_PATH)/qapi/crypto.json
 
 qapi-types.c qapi-types.h :\
 $(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 21/30] crypto: add cryptographic random byte source
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (19 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 20/30] crypto: ensure qapi/crypto.json is listed in qapi-modules Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 22/30] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
                   ` (8 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs    |  1 +
 crypto/random.c         | 50 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/crypto/random.h | 43 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 94 insertions(+)
 create mode 100644 crypto/random.c
 create mode 100644 include/crypto/random.h

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index a3135f1..5f38d2d 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -8,6 +8,7 @@ crypto-obj-y += tlscredsanon.o
 crypto-obj-y += tlscredsx509.o
 crypto-obj-y += tlssession.o
 crypto-obj-y += secret.o
+crypto-obj-y += random.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/random.c b/crypto/random.c
new file mode 100644
index 0000000..8257d24
--- /dev/null
+++ b/crypto/random.c
@@ -0,0 +1,50 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015 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,
+                         size_t buflen,
+                         Error **errp)
+{
+    ssize_t ret;
+    int fd = open("/dev/random", O_RDONLY);
+    if (fd < 0) {
+        error_setg_errno(errp, errno,
+                         "Unable to open /dev/random");
+        return -1;
+    }
+
+    while (buflen) {
+        ret = read(fd, buf, buflen);
+        if (ret < 0) {
+            error_setg_errno(errp, errno,
+                             "Unable to read random bytes");
+            close(fd);
+            return -1;
+        }
+        buflen -= ret;
+    }
+
+    close(fd);
+    return 0;
+}
diff --git a/include/crypto/random.h b/include/crypto/random.h
new file mode 100644
index 0000000..ce1626b
--- /dev/null
+++ b/include/crypto/random.h
@@ -0,0 +1,43 @@
+/*
+ * QEMU Crypto random number provider
+ *
+ * Copyright (c) 2015 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 uninitialized error objet
+ *
+ * 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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 22/30] crypto: add support for PBKDF2 algorithm
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (20 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 21/30] crypto: add cryptographic random byte source Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 23/30] crypto: add support for generating initialization vectors Daniel P. Berrange
                   ` (7 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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      |   1 +
 crypto/pbkdf-gcrypt.c     |  64 ++++++++
 crypto/pbkdf-nettle.c     |  63 ++++++++
 crypto/pbkdf-stub.c       |  39 +++++
 crypto/pbkdf.c            |  76 ++++++++++
 include/crypto/pbkdf.h    | 152 +++++++++++++++++++
 tests/.gitignore          |   1 +
 tests/Makefile            |   2 +
 tests/test-crypto-pbkdf.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 776 insertions(+)
 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 5f38d2d..c1cf45c 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -9,6 +9,7 @@ crypto-obj-y += tlscredsx509.o
 crypto-obj-y += tlssession.o
 crypto-obj-y += secret.o
 crypto-obj-y += random.o
+crypto-obj-y += pbkdf.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/pbkdf-gcrypt.c b/crypto/pbkdf-gcrypt.c
new file mode 100644
index 0000000..2e494ce
--- /dev/null
+++ b/crypto/pbkdf-gcrypt.c
@@ -0,0 +1,64 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015 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 "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_LAST] = {
+        [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..d86cd58
--- /dev/null
+++ b/crypto/pbkdf-nettle.c
@@ -0,0 +1,63 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015 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 "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");
+        return -1;
+    }
+    return 0;
+}
diff --git a/crypto/pbkdf-stub.c b/crypto/pbkdf-stub.c
new file mode 100644
index 0000000..624154a
--- /dev/null
+++ b/crypto/pbkdf-stub.c
@@ -0,0 +1,39 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015 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/>.
+ *
+ */
+
+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..176c813
--- /dev/null
+++ b/crypto/pbkdf.c
@@ -0,0 +1,76 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015 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>
+
+#if defined CONFIG_NETTLE
+#include "crypto/pbkdf-nettle.c"
+#elif defined CONFIG_GCRYPT
+#include "crypto/pbkdf-gcrypt.c"
+#else /* ! CONFIG_GCRYPT */
+#include "crypto/pbkdf-stub.c"
+#endif /* ! CONFIG_GCRYPT */
+
+
+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..7d75357
--- /dev/null
+++ b/include/crypto/pbkdf.h
@@ -0,0 +1,152 @@
+/*
+ * QEMU Crypto PBKDF support (Password-Based Key Derivation Function)
+ *
+ * Copyright (c) 2015 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 uninitialized 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 uninitialized 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 6cc8efe..8ccbbc4 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,6 +13,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 72265e8..68d1ade 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -85,6 +85,7 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
 check-unit-y += tests/test-timed-average$(EXESUF)
+check-unit-y += tests/test-crypto-pbkdf$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -471,6 +472,7 @@ tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
 tests/test-crypto-tlssession.o-cflags := $(TASN1_CFLAGS)
 tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 	tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-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..3ff8cd8
--- /dev/null
+++ b/tests/test-crypto-pbkdf.c
@@ -0,0 +1,378 @@
+/*
+ * QEMU Crypto cipher algorithms
+ *
+ * Copyright (c) 2015 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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 23/30] crypto: add support for generating initialization vectors
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (21 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 22/30] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 24/30] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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      | 116 ++++++++++++++++++++++++++++++++
 crypto/ivgen-essiv.h      |  28 ++++++++
 crypto/ivgen-plain.c      |  60 +++++++++++++++++
 crypto/ivgen-plain.h      |  28 ++++++++
 crypto/ivgen-plain64.c    |  60 +++++++++++++++++
 crypto/ivgen-plain64.h    |  28 ++++++++
 crypto/ivgen.c            | 117 ++++++++++++++++++++++++++++++++
 crypto/ivgenpriv.h        |  47 +++++++++++++
 include/crypto/ivgen.h    | 167 +++++++++++++++++++++++++++++++++++++++++++++
 qapi/crypto.json          |  16 +++++
 tests/.gitignore          |   1 +
 tests/Makefile            |   2 +
 tests/test-crypto-ivgen.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++
 14 files changed, 842 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 c1cf45c..a973483 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -10,6 +10,10 @@ crypto-obj-y += tlssession.o
 crypto-obj-y += secret.o
 crypto-obj-y += random.o
 crypto-obj-y += pbkdf.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..fad8f95
--- /dev/null
+++ b/crypto/ivgen-essiv.c
@@ -0,0 +1,116 @@
+/*
+ * QEMU Crypto block IV generator - essiv
+ *
+ * Copyright (c) 2015 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;
+    QCryptoCipherAlgorithm cipheralg;
+};
+
+static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
+                                    QCryptoCipherAlgorithm cipheralg,
+                                    QCryptoHashAlgorithm hash,
+                                    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(hash);
+    /* Salt must be larger of hash size or key size */
+    salt = g_new0(uint8_t, nhash > nkey ? nhash : nkey);
+
+    if (qcrypto_hash_bytes(hash, (const gchar*)key, nkey,
+                           &salt, &nhash,
+                           errp) < 0) {
+        g_free(essiv);
+        return -1;
+    }
+
+    essiv->cipheralg = cipheralg;
+    essiv->cipher = qcrypto_cipher_new(cipheralg,
+                                       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(essiv->cipheralg);
+    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..eed80c1
--- /dev/null
+++ b/crypto/ivgen-essiv.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - essiv
+ *
+ * Copyright (c) 2015 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..784ebc1
--- /dev/null
+++ b/crypto/ivgen-plain.c
@@ -0,0 +1,60 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015 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,
+                                    QCryptoCipherAlgorithm cipheralg,
+                                    QCryptoHashAlgorithm hash,
+                                    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..82c0939
--- /dev/null
+++ b/crypto/ivgen-plain.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015 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..40bbcc2
--- /dev/null
+++ b/crypto/ivgen-plain64.c
@@ -0,0 +1,60 @@
+/*
+ * QEMU Crypto block IV generator - plain
+ *
+ * Copyright (c) 2015 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,
+                                    QCryptoCipherAlgorithm cipheralg,
+                                    QCryptoHashAlgorithm hash,
+                                    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..031d8f4
--- /dev/null
+++ b/crypto/ivgen-plain64.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block IV generator - plain64
+ *
+ * Copyright (c) 2015 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..2353660
--- /dev/null
+++ b/crypto/ivgen.c
@@ -0,0 +1,117 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015 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"
+
+
+/**
+ * @qcrypto_ivgen_new:
+ * @alg: the initialization vector generator algorithm
+ * @cipheralg: the cipher algorithm (if applicable)
+ * @hash: the hash algorithm (if applicable)
+ * @key: the encryption key (if applicable)
+ * @nkey: the length of @key in bytes
+ * @errp: pointer to an uninitialized error object
+ *
+ * Create a new initialization vector generator implementing
+ * the algorithm specified in @alg.
+ *
+ * For the ESSIV algorithm, the @cipheralg, @ciphermode
+ * @hash, @key and @nkey parameters are required. For
+ * other algorithms they should be NULL / zero as
+ * appropriate.
+ *
+ * Returns the 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)
+{
+    QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
+
+
+    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, cipheralg, hash, key, nkey, errp) < 0) {
+        g_free(ivgen);
+        return NULL;
+    }
+
+    return ivgen;
+}
+
+
+/**
+ * qcrypto_ivgen_calculate:
+ * @ivgen: the IV generator object
+ * @sector: the sector number in the volume
+ * @iv: buffer to store the generated IV in
+ * @niv: length of @iv in bytes
+ * @errp: pointer to an uninitialized error object
+ *
+ * Calculate a new initialization vector storing the
+ * result in @iv. The @iv buffer must be pre-allocated
+ * by the caller and @niv provides its length in bytes.
+ *
+ * Returns 0 on success, -1 on failure
+ */
+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);
+}
+
+
+/**
+ * qcrypto_ivgen_free:
+ * @ivgen: the IV generator object
+ *
+ * Release all resources associated with the
+ * IV generator
+ */
+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..f9ebe8a
--- /dev/null
+++ b/crypto/ivgenpriv.h
@@ -0,0 +1,47 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015 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,
+                QCryptoCipherAlgorithm cipheralg,
+                QCryptoHashAlgorithm hash,
+                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;
+};
+
+
+#endif /* QCRYPTO_IVGEN_PRIV_H__ */
diff --git a/include/crypto/ivgen.h b/include/crypto/ivgen.h
new file mode 100644
index 0000000..6f8acdd
--- /dev/null
+++ b/include/crypto/ivgen.h
@@ -0,0 +1,167 @@
+/*
+ * QEMU Crypto block IV generator
+ *
+ * Copyright (c) 2015 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 an uninitialized 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_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 2cea2bd..596282d 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 8ccbbc4..9991677 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,6 +13,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 68d1ade..cd64316 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -86,6 +86,7 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
 check-unit-y += tests/test-timed-average$(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
 
@@ -473,6 +474,7 @@ tests/test-crypto-tlssession.o-cflags := $(TASN1_CFLAGS)
 tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 	tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-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..3c58e46
--- /dev/null
+++ b/tests/test-crypto-ivgen.c
@@ -0,0 +1,168 @@
+/*
+ * QEMU Crypto IV generator algorithms
+ *
+ * Copyright (c) 2015 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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 24/30] crypto: add support for anti-forensic split algorithm
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (22 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 23/30] crypto: add support for generating initialization vectors Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 25/30] crypto: fix transposed arguments in cipher error message Daniel P. Berrange
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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            | 194 ++++++++++++++++++++++++++++++++++++++++++++
 include/crypto/afsplit.h    | 133 ++++++++++++++++++++++++++++++
 tests/.gitignore            |   1 +
 tests/Makefile              |   2 +
 tests/test-crypto-afsplit.c | 176 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 507 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 a973483..1e08a3a 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -14,6 +14,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..7131622
--- /dev/null
+++ b/crypto/afsplit.c
@@ -0,0 +1,194 @@
+/*
+ * QEMU Crypto anti forensic information splitter
+ *
+ * Copyright (c) 2015 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;
+}
+
+
+/**
+ * qcrypto_afsplit_encode:
+ * @hash: the hash function to use when splitting
+ * @blocklen: the length of @in in bytes
+ * @stripes: the number of stripes to split @in into
+ * @in: the input data, @blocklen bytes
+ * @out: the output data, @blocklen * @stripes bytes
+ * @errp: pointer to an uninitialized error object
+ *
+ * Transform the data (key material) in @in which is @blocklen
+ * bytes long, into data that is (@blocklen * @stripes)  bytes
+ * long. The caller is responsible for allocating @out with
+ * @blocklen * @stripes bytes of memory.
+ *
+ * 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)
+{
+    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;
+}
+
+
+/**
+ * qcrypto_afsplit_decode:
+ * @hash: the hash function to use when splitting
+ * @blocklen: the length of @iout in bytes
+ * @stripes: the number of stripes to split @in into
+ * @in: the input data, @blocklen * @stripes bytes
+ * @out: the output data, @blocklen bytes
+ * @errp: pointer to an uninitialized error object
+ *
+ * Transform the split data in @in which is (@blocklen * @stripes)
+ * bytes long, into the original data (key material) that is
+ * @blocklen bytes long. The caller is responsible for allocating
+ * @out with @blocklen bytes of memory.
+ *
+ * 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)
+{
+    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..a331d8a
--- /dev/null
+++ b/include/crypto/afsplit.h
@@ -0,0 +1,133 @@
+/*
+ * QEMU Crypto anti forensic information splitter
+ *
+ * Copyright (c) 2015 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
+ *
+ * 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
+ *
+ * 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 9991677..4055cfc 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -11,6 +11,7 @@ test-aio
 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 cd64316..27777fc 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -87,6 +87,7 @@ check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
 check-unit-y += tests/test-timed-average$(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
 
@@ -475,6 +476,7 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 	tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-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..da301b8
--- /dev/null
+++ b/tests/test-crypto-afsplit.c
@@ -0,0 +1,176 @@
+/*
+ * QEMU Crypto anti-forensic splitter
+ *
+ * Copyright (c) 2015 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] 37+ messages in thread

* [Qemu-devel] [PATCH WIP 25/30] crypto: fix transposed arguments in cipher error message
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (23 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 24/30] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 26/30] crypto: add block encryption framework Daniel P. Berrange
                   ` (4 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

When reporting an incorrect key length for a cipher, we
mixed up the actual vs expected arguments.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/cipher.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crypto/cipher.c b/crypto/cipher.c
index e92d49a..a69ff5e 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -89,7 +89,7 @@ qcrypto_cipher_validate_key_length(QCryptoCipherAlgorithm alg,
 
     if (alg_key_len[alg] != nkey) {
         error_setg(errp, "Cipher key length %zu should be %zu",
-                   alg_key_len[alg], nkey);
+                   nkey, alg_key_len[alg]);
         return false;
     }
     return true;
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 26/30] crypto: add block encryption framework
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (24 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 25/30] crypto: fix transposed arguments in cipher error message Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 27/30] crypto: implement the LUKS block encryption format Daniel P. Berrange
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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-qcowaes.c | 150 +++++++++++++++++++++++++++++
 crypto/block-qcowaes.h |  28 ++++++
 crypto/block.c         | 251 +++++++++++++++++++++++++++++++++++++++++++++++++
 crypto/blockpriv.h     |  87 +++++++++++++++++
 include/crypto/block.h | 199 +++++++++++++++++++++++++++++++++++++++
 qapi/crypto.json       |  62 ++++++++++++
 7 files changed, 779 insertions(+)
 create mode 100644 crypto/block-qcowaes.c
 create mode 100644 crypto/block-qcowaes.h
 create mode 100644 crypto/block.c
 create mode 100644 crypto/blockpriv.h
 create mode 100644 include/crypto/block.h

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 1e08a3a..b5b32a6 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -15,6 +15,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-qcowaes.o
 
 # Let the userspace emulators avoid linking gnutls/etc
 crypto-aes-obj-y = aes.o
diff --git a/crypto/block-qcowaes.c b/crypto/block-qcowaes.c
new file mode 100644
index 0000000..36d738d
--- /dev/null
+++ b/crypto/block-qcowaes.c
@@ -0,0 +1,150 @@
+/*
+ * QEMU Crypto block device encryption QCOWAES format
+ *
+ * Copyright (c) 2015 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-qcowaes.h"
+#include "crypto/secret.h"
+
+static gboolean
+qcrypto_block_qcowaes_has_format(const uint8_t *buf G_GNUC_UNUSED,
+                                 size_t buf_size G_GNUC_UNUSED)
+{
+    return false;
+}
+
+
+static int
+qcrypto_block_qcowaes_init(QCryptoBlock *block,
+                           const char *keyid,
+                           Error **errp)
+{
+    char *password;
+    int ret;
+    uint8_t keybuf[16];
+    int len, i;
+
+    memset(keybuf, 0, 16);
+
+    password = qcrypto_secret_lookup_as_utf8(keyid, 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_qcowaes_open(QCryptoBlock *block,
+                           QCryptoBlockOpenOptions *options,
+                           QCryptoBlockReadFunc readfunc G_GNUC_UNUSED,
+                           void *opaque G_GNUC_UNUSED,
+                           Error **errp)
+{
+    return qcrypto_block_qcowaes_init(block, options->u.qcowaes->keyid, errp);
+}
+
+
+static int
+qcrypto_block_qcowaes_create(QCryptoBlock *block,
+                             QCryptoBlockCreateOptions *options,
+                             QCryptoBlockWriteFunc writefunc G_GNUC_UNUSED,
+                             void *opaque G_GNUC_UNUSED,
+                             Error **errp)
+{
+    return qcrypto_block_qcowaes_init(block, options->u.qcowaes->keyid, errp);
+}
+
+
+static void
+qcrypto_block_qcowaes_cleanup(QCryptoBlock *block)
+{
+}
+
+
+static int
+qcrypto_block_qcowaes_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_qcowaes_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_qcowaes = {
+    .open = qcrypto_block_qcowaes_open,
+    .create = qcrypto_block_qcowaes_create,
+    .cleanup = qcrypto_block_qcowaes_cleanup,
+    .decrypt = qcrypto_block_qcowaes_decrypt,
+    .encrypt = qcrypto_block_qcowaes_encrypt,
+    .has_format = qcrypto_block_qcowaes_has_format,
+};
diff --git a/crypto/block-qcowaes.h b/crypto/block-qcowaes.h
new file mode 100644
index 0000000..e43b7e5
--- /dev/null
+++ b/crypto/block-qcowaes.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block device encryption QCow AES format
+ *
+ * Copyright (c) 2015 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_QCOWAES_H__
+#define QCRYPTO_BLOCK_QCOWAES_H__
+
+#include "crypto/blockpriv.h"
+
+extern const QCryptoBlockDriver qcrypto_block_driver_qcowaes;
+
+#endif /* QCRYPTO_BLOCK_QCOWAES_H__ */
diff --git a/crypto/block.c b/crypto/block.c
new file mode 100644
index 0000000..1eebbcf
--- /dev/null
+++ b/crypto/block.c
@@ -0,0 +1,251 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015 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-qcowaes.h"
+
+static const QCryptoBlockDriver *qcrypto_block_drivers[] = {
+    [Q_CRYPTO_BLOCK_FORMAT_QCOWAES] = &qcrypto_block_driver_qcowaes,
+};
+
+
+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,
+                                 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, errp) < 0) {
+        g_free(block);
+        return NULL;
+    }
+
+    return block;
+}
+
+
+QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
+                                   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, 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;
+}
+
+
+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);
+
+    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..300ba4b
--- /dev/null
+++ b/crypto/blockpriv.h
@@ -0,0 +1,87 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015 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;
+    size_t niv;
+    uint64_t payload_offset; /* In 512 byte sectors */
+};
+
+struct QCryptoBlockDriver {
+    int (*open)(QCryptoBlock *block,
+                QCryptoBlockOpenOptions *options,
+                QCryptoBlockReadFunc readfunc,
+                void *opaque,
+                Error **errp);
+
+    int (*create)(QCryptoBlock *block,
+                  QCryptoBlockCreateOptions *options,
+                  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..ba17924
--- /dev/null
+++ b/include/crypto/block.h
@@ -0,0 +1,199 @@
+/*
+ * QEMU Crypto block device encryption
+ *
+ * Copyright (c) 2015 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 (*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);
+
+/**
+ * qcrypto_block_open:
+ * @options: the encryption options
+ * @readfunc: callback for reading data from the volume
+ * @opaque: data to pass to @readfunc
+ * @errp: pointer to an uninitialized 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 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,
+                                 Error **errp);
+
+/**
+ * qcrypto_block_create:
+ * @format: the encryption format
+ * @keyid: ID of a QCryptoSecret with key for unlocking master key
+ * @writefunc: callback for writing data to the volume header
+ * @opaque: data to pass to @writefunc
+ * @errp: pointer to an uninitialized 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 write a new volume header 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,
+                                   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 an uninitialized 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 an uninitialized 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_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 596282d..99d9da8 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -94,3 +94,65 @@
 { 'enum': 'QCryptoIVGenAlgorithm',
   'prefix': 'QCRYPTO_IVGEN_ALG',
   'data': ['plain', 'plain64', 'essiv']}
+
+##
+# QCryptoBlockFormat:
+#
+# The supported full disk encryption formats
+#
+# @qcowaes: QCow/QCow2 built-in AES-CBC encryption. Do not use
+# Since: 2.6
+##
+{ 'enum': 'QCryptoBlockFormat',
+#  'prefix': 'QCRYPTO_BLOCK_FORMAT',
+  'data': ['qcowaes']}
+
+##
+# QCryptoBlockOptionsBase:
+#
+# The common options that apply to all full disk
+# encryption formats
+#
+# @format: the encryption format
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsBase',
+  'data': { 'format': 'QCryptoBlockFormat' }}
+
+##
+# QCryptoBlockOptionsQCowAES:
+#
+# The options that apply to QCow AES encryption format
+#
+# @keyid: the ID of a QCryptoSecret object providing the decryption key
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsQCowAES',
+  'data': { 'keyid': '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': { 'qcowaes': 'QCryptoBlockOptionsQCowAES' } }
+
+
+##
+# 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': { 'qcowaes': 'QCryptoBlockOptionsQCowAES' } }
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 27/30] crypto: implement the LUKS block encryption format
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (25 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 26/30] crypto: add block encryption framework Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 28/30] block: add generic full disk encryption driver Daniel P. Berrange
                   ` (2 subsequent siblings)
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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  | 1056 ++++++++++++++++++++++++++++++++++++++++++++++++++
 crypto/block-luks.h  |   28 ++
 crypto/block.c       |    2 +
 qapi/crypto.json     |   42 +-
 5 files changed, 1126 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 b5b32a6..c65880f 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -17,6 +17,7 @@ crypto-obj-y += ivgen-plain64.o
 crypto-obj-y += afsplit.o
 crypto-obj-y += block.o
 crypto-obj-y += block-qcowaes.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..5e3ed5e
--- /dev/null
+++ b/crypto/block-luks.c
@@ -0,0 +1,1056 @@
+/*
+ * QEMU Crypto block device encryption LUKS format
+ *
+ * Copyright (c) 2015 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 */
+        return 1;
+    }
+
+    /* 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,
+                        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;
+
+    password = qcrypto_secret_lookup_as_utf8(options->u.luks->keyid, 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;
+    }
+
+
+    /* 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->niv = qcrypto_cipher_get_iv_len(cipheralg,
+                                           ciphermode);
+    block->ivgen = qcrypto_ivgen_new(ivalg,
+                                     cipheralg,
+                                     hash,
+                                     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,
+                          QCryptoBlockWriteFunc writefunc,
+                          void *opaque,
+                          Error **errp)
+{
+    QCryptoBlockLUKS *luks;
+    QCryptoBlockCreateOptionsLUKS *luks_opts = options->u.luks;
+    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;
+
+    password = qcrypto_secret_lookup_as_utf8(luks_opts->keyid, 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->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;
+
+    /* 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(password);
+    g_free(cipher_mode_spec);
+
+    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(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..0dd5261
--- /dev/null
+++ b/crypto/block-luks.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU Crypto block device encryption LUKS format
+ *
+ * Copyright (c) 2015 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 1eebbcf..4c2d362 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -20,9 +20,11 @@
 
 #include "crypto/blockpriv.h"
 #include "crypto/block-qcowaes.h"
+#include "crypto/block-luks.h"
 
 static const QCryptoBlockDriver *qcrypto_block_drivers[] = {
     [Q_CRYPTO_BLOCK_FORMAT_QCOWAES] = &qcrypto_block_driver_qcowaes,
+    [Q_CRYPTO_BLOCK_FORMAT_LUKS] = &qcrypto_block_driver_luks,
 };
 
 
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 99d9da8..3d43cdb 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -101,11 +101,12 @@
 # The supported full disk encryption formats
 #
 # @qcowaes: QCow/QCow2 built-in AES-CBC encryption. Do not use
+# @luks: LUKS encryption format. Recommended
 # Since: 2.6
 ##
 { 'enum': 'QCryptoBlockFormat',
 #  'prefix': 'QCRYPTO_BLOCK_FORMAT',
-  'data': ['qcowaes']}
+  'data': ['qcowaes', 'luks']}
 
 ##
 # QCryptoBlockOptionsBase:
@@ -131,6 +132,39 @@
   'data': { 'keyid': 'str' }}
 
 ##
+# QCryptoBlockOptionsLUKS:
+#
+# The options that apply to LUKS encryption format
+#
+# @keyid: the ID of a QCryptoSecret object providing the decryption key
+# Since: 2.6
+##
+{ 'struct': 'QCryptoBlockOptionsLUKS',
+  'data': { 'keyid': 'str' }}
+
+
+##
+# QCryptoBlockCreateOptionsLUKS:
+#
+# The options that apply to LUKS encryption format initialization
+#
+# @cipher_alg: the cipher algorithm for data encryption
+# @cipher_mode: the cipher mode for data encryption
+# @ivgen_alg: the initialization vector generator
+# @ivgen_hash_alg: (optional) the initialization vector generator hash
+# @hash_alg: 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
@@ -141,7 +175,8 @@
 { 'union': 'QCryptoBlockOpenOptions',
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
-  'data': { 'qcowaes': 'QCryptoBlockOptionsQCowAES' } }
+  'data': { 'qcowaes': 'QCryptoBlockOptionsQCowAES',
+            'luks': 'QCryptoBlockOptionsLUKS' } }
 
 
 ##
@@ -155,4 +190,5 @@
 { 'union': 'QCryptoBlockCreateOptions',
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
-  'data': { 'qcowaes': 'QCryptoBlockOptionsQCowAES' } }
+  'data': { 'qcowaes': 'QCryptoBlockOptionsQCowAES',
+            'luks': 'QCryptoBlockCreateOptionsLUKS' } }
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 28/30] block: add generic full disk encryption driver
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (26 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 27/30] crypto: implement the LUKS block encryption format Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 29/30] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 30/30] qcow2: add LUKS full disk encryption support Daniel P. Berrange
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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. A new LUKS
compatible volume can be formatted using qemu-img

$ qemu-img create --object secret,data=123456,id=sec0 \
      -f luks -o keyid=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,keyid=sec0
image: json:{"keyid": "sec0", "driver": "luks", "file": {"driver": "file", "filename": "demo.luks"}}
file format: luks
virtual size: 10.0G (10737416192 bytes)
disk size: 132K

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/Makefile.objs |   2 +
 block/fde.c         | 522 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 524 insertions(+)
 create mode 100644 block/fde.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index 58ef2ef..4f997c9 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 += fde.o
+
 common-obj-y += stream.o
 common-obj-y += commit.o
 common-obj-y += backup.o
diff --git a/block/fde.c b/block/fde.c
new file mode 100644
index 0000000..98d8a0d
--- /dev/null
+++ b/block/fde.c
@@ -0,0 +1,522 @@
+/*
+ * QEMU block full disk encryption
+ *
+ * Copyright (c) 2015 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 FDE_OPT_LUKS_KEY_ID "keyid"
+#define FDE_OPT_LUKS_CIPHER_ALG "cipher_alg"
+#define FDE_OPT_LUKS_CIPHER_MODE "cipher_mode"
+#define FDE_OPT_LUKS_IVGEN_ALG "ivgen_alg"
+#define FDE_OPT_LUKS_IVGEN_HASH_ALG "ivgen_hash_alg"
+#define FDE_OPT_LUKS_HASH_ALG "hash_alg"
+
+typedef struct QBlockFDE QBlockFDE;
+
+struct QBlockFDE {
+    QCryptoBlock *block;
+    CoMutex lock;
+};
+
+
+static int qblock_fde_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 qblock_fde_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 qblock_fde_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 QemuOptsList qblock_fde_runtime_opts_luks = {
+    .name = "fde",
+    .head = QTAILQ_HEAD_INITIALIZER(qblock_fde_runtime_opts_luks.head),
+    .desc = {
+        {
+            .name = FDE_OPT_LUKS_KEY_ID,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        { /* end of list */ }
+    },
+};
+
+
+static QemuOptsList qblock_fde_create_opts_luks = {
+    .name = "fde",
+    .head = QTAILQ_HEAD_INITIALIZER(qblock_fde_create_opts_luks.head),
+    .desc = {
+        {
+            .name = BLOCK_OPT_SIZE,
+            .type = QEMU_OPT_SIZE,
+            .help = "Virtual disk size"
+        },
+        {
+            .name = FDE_OPT_LUKS_KEY_ID,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        {
+            .name = FDE_OPT_LUKS_CIPHER_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm",
+        },
+        {
+            .name = FDE_OPT_LUKS_CIPHER_MODE,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher mode",
+        },
+        {
+            .name = FDE_OPT_LUKS_IVGEN_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator algorithm",
+        },
+        {
+            .name = FDE_OPT_LUKS_IVGEN_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator hash algorithm",
+        },
+        {
+            .name = FDE_OPT_LUKS_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption hash algorithm",
+        },
+        { /* end of list */ }
+    },
+};
+
+
+static QCryptoBlockOpenOptions *
+qblock_fde_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 *
+qblock_fde_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 qblock_fde_open_generic(QCryptoBlockFormat format,
+                                   QemuOptsList *opts_spec,
+                                   BlockDriverState *bs,
+                                   QDict *options,
+                                   int flags,
+                                   Error **errp)
+{
+    QBlockFDE *fde = bs->opaque;
+    QemuOpts *opts = NULL;
+    Error *local_err = NULL;
+    int ret = -EINVAL;
+    QCryptoBlockOpenOptions *open_opts = NULL;
+
+    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 = qblock_fde_open_opts_init(format, opts, errp);
+    if (!open_opts) {
+        goto cleanup;
+    }
+
+    fde->block = qcrypto_block_open(open_opts,
+                                    qblock_fde_read_func,
+                                    bs,
+                                    errp);
+
+    if (!fde->block) {
+        ret = -EIO;
+        goto cleanup;
+    }
+
+    qemu_co_mutex_init(&fde->lock);
+
+    ret = 0;
+ cleanup:
+    qapi_free_QCryptoBlockOpenOptions(open_opts);
+    return ret;
+}
+
+
+static int qblock_fde_create_generic(QCryptoBlockFormat format,
+                                     const char *filename,
+                                     QemuOpts *opts,
+                                     Error **errp)
+{
+    int ret = -EINVAL;
+    QCryptoBlockCreateOptions *create_opts = NULL;
+    BlockDriverState *bs = NULL;
+    QCryptoBlock *fde = NULL;
+    uint64_t size = 0;
+
+    size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+                    BDRV_SECTOR_SIZE);
+
+    create_opts = qblock_fde_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;
+    }
+
+    fde = qcrypto_block_create(create_opts,
+                               qblock_fde_write_func,
+                               bs,
+                               errp);
+
+    if (!fde) {
+        ret = -EIO;
+        goto cleanup;
+    }
+
+    ret = 0;
+ cleanup:
+    qcrypto_block_free(fde);
+    bdrv_unref(bs);
+    qapi_free_QCryptoBlockCreateOptions(create_opts);
+    return ret;
+}
+
+static void qblock_fde_close(BlockDriverState *bs)
+{
+    QBlockFDE *fde = bs->opaque;
+    qcrypto_block_free(fde->block);
+}
+
+
+#define QBLOCK_FDE_MAX_SECTORS 32
+
+static coroutine_fn int
+qblock_fde_co_readv(BlockDriverState *bs, int64_t sector_num,
+                    int remaining_sectors, QEMUIOVector *qiov)
+{
+    QBlockFDE *fde = 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(fde->block);
+
+    qemu_iovec_init(&hd_qiov, qiov->niov);
+
+    qemu_co_mutex_lock(&fde->lock);
+
+    while (remaining_sectors) {
+        cur_nr_sectors = remaining_sectors;
+
+        if (cur_nr_sectors > QBLOCK_FDE_MAX_SECTORS) {
+            cur_nr_sectors = QBLOCK_FDE_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(&fde->lock);
+        ret = bdrv_co_readv(bs->file->bs,
+                            payload_offset + sector_num,
+                            cur_nr_sectors, &hd_qiov);
+        qemu_co_mutex_lock(&fde->lock);
+        if (ret < 0) {
+            goto cleanup;
+        }
+
+        if (qcrypto_block_decrypt(fde->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(&fde->lock);
+
+    qemu_iovec_destroy(&hd_qiov);
+    qemu_vfree(cipher_data);
+
+    return ret;
+}
+
+
+static coroutine_fn int
+qblock_fde_co_writev(BlockDriverState *bs, int64_t sector_num,
+                     int remaining_sectors, QEMUIOVector *qiov)
+{
+    QBlockFDE *fde = 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(fde->block);
+
+    qemu_iovec_init(&hd_qiov, qiov->niov);
+
+    qemu_co_mutex_lock(&fde->lock);
+
+    while (remaining_sectors) {
+        cur_nr_sectors = remaining_sectors;
+
+        if (cur_nr_sectors > QBLOCK_FDE_MAX_SECTORS) {
+            cur_nr_sectors = QBLOCK_FDE_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(fde->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(&fde->lock);
+        ret = bdrv_co_writev(bs->file->bs,
+                             payload_offset + sector_num,
+                             cur_nr_sectors, &hd_qiov);
+        qemu_co_mutex_lock(&fde->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(&fde->lock);
+
+    qemu_iovec_destroy(&hd_qiov);
+    qemu_vfree(cipher_data);
+
+    return ret;
+}
+
+
+static int64_t qblock_fde_getlength(BlockDriverState *bs)
+{
+    QBlockFDE *fde = bs->opaque;
+    int64_t len = bdrv_getlength(bs->file->bs);
+
+    ssize_t offset = qcrypto_block_get_payload_offset(fde->block);
+
+    len -= (offset * 512);
+
+    return len;
+}
+
+#define QBLOCK_FDE_DRIVER(name, format)                                 \
+    static int qblock_fde_probe_ ## name(const uint8_t *buf,            \
+                                         int buf_size,                  \
+                                         const char *filename) {        \
+        return qblock_fde_probe_generic(format,                         \
+                                        buf, buf_size, filename);       \
+    }                                                                   \
+                                                                        \
+    static int qblock_fde_open_ ## name(BlockDriverState *bs,           \
+                                        QDict *options,                 \
+                                        int flags,                      \
+                                        Error **errp)                   \
+    {                                                                   \
+        return qblock_fde_open_generic(format,                          \
+                                       &qblock_fde_runtime_opts_ ## name, \
+                                       bs, options, flags, errp);       \
+    }                                                                   \
+                                                                        \
+    static int qblock_fde_create_ ## name(const char *filename,         \
+                                          QemuOpts *opts,               \
+                                          Error **errp)                 \
+    {                                                                   \
+        return qblock_fde_create_generic(format,                        \
+                                         filename, opts, errp);         \
+    }                                                                   \
+                                                                        \
+    BlockDriver bdrv_fde_ ## name = {                                   \
+        .format_name        = #name,                                    \
+        .instance_size      = sizeof(QBlockFDE),                        \
+        .bdrv_probe         = qblock_fde_probe_ ## name,                \
+        .bdrv_open          = qblock_fde_open_ ## name,                 \
+        .bdrv_close         = qblock_fde_close,                         \
+        .bdrv_create        = qblock_fde_create_ ## name,               \
+        .create_opts        = &qblock_fde_create_opts_ ## name,         \
+                                                                        \
+        .bdrv_co_readv      = qblock_fde_co_readv,                      \
+        .bdrv_co_writev     = qblock_fde_co_writev,                     \
+        .bdrv_getlength     = qblock_fde_getlength,                     \
+    }
+
+QBLOCK_FDE_DRIVER(luks, Q_CRYPTO_BLOCK_FORMAT_LUKS);
+
+static void qblock_fde_init(void)
+{
+    bdrv_register(&bdrv_fde_luks);
+}
+
+block_init(qblock_fde_init);
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 29/30] qcow2: convert QCow2 to use QCryptoBlock for encryption
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (27 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 28/30] block: add generic full disk encryption driver Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 30/30] qcow2: add LUKS full disk encryption support Daniel P. Berrange
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The QCryptoBlock framework trivially supports the legecy QCow
encryption format. Convert QCow2 to use QCryptoBlock, since
this will unlock the ability to support LUKS in QCow2 without
increasing the code burden for encryption in QCow2.

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

diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index c24b580..78a9bea 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,11 @@ 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,
+        assert(s->fde);
+        if (qcrypto_block_encrypt(s->fde,
+                                  start_sect + n_start,
                                   iov.iov_base, n,
-                                  true, &err) < 0) {
+                                  &err) < 0) {
             ret = -EIO;
             error_free(err);
             goto out;
diff --git a/block/qcow2.c b/block/qcow2.c
index c16f899..9158dd0 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -595,31 +595,6 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
     }
 }
 
-static QCryptoCipher *qcow2_get_cipher_from_key(const char *key,
-                                                Error **errp)
-{
-    uint8_t keybuf[16];
-    int len, i;
-
-    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];
-    }
-
-    return qcrypto_cipher_new(
-        QCRYPTO_CIPHER_ALG_AES_128,
-        QCRYPTO_CIPHER_MODE_CBC,
-        keybuf, G_N_ELEMENTS(keybuf),
-        errp);
-}
-
-
 typedef struct Qcow2ReopenState {
     Qcow2Cache *l2_table_cache;
     Qcow2Cache *refcount_block_cache;
@@ -627,7 +602,7 @@ typedef struct Qcow2ReopenState {
     int overlap_check;
     bool discard_passthrough[QCOW2_DISCARD_MAX];
     uint64_t cache_clean_interval;
-    QCryptoCipher *cipher;
+    QCryptoBlockOpenOptions *fde_opts; /* Disk encryption runtime options */
 } Qcow2ReopenState;
 
 static int qcow2_update_options_prepare(BlockDriverState *bs,
@@ -643,8 +618,6 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     int i;
     Error *local_err = NULL;
     int ret;
-    const char *keyid;
-    char *key;
 
     opts = qemu_opts_create(&qcow2_runtime_opts, NULL, 0, &error_abort);
     qemu_opts_absorb_qdict(opts, options, &local_err);
@@ -788,22 +761,13 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     r->discard_passthrough[QCOW2_DISCARD_OTHER] =
         qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
 
-    keyid = qemu_opt_get(opts, QCOW2_OPT_KEY_ID);
-    if (keyid) {
-        key = qcrypto_secret_lookup_as_utf8(keyid,
-                                            errp);
-        if (!key) {
-            ret = -ENOENT;
-            goto fail;
-        }
+    if (s->crypt_method_header) {
+        r->fde_opts = g_new0(QCryptoBlockOpenOptions, 1);
+        r->fde_opts->format = Q_CRYPTO_BLOCK_FORMAT_QCOWAES;
+        r->fde_opts->u.qcowaes = g_new0(QCryptoBlockOptionsQCowAES, 1);
 
-        r->cipher = qcow2_get_cipher_from_key(key,
-                                              errp);
-        g_free(key);
-        if (!r->cipher) {
-            ret = -ENOSYS;
-            goto fail;
-        }
+        r->fde_opts->u.qcowaes->keyid =
+            g_strdup(qemu_opt_get(opts, QCOW2_OPT_KEY_ID));
     }
 
     ret = 0;
@@ -841,8 +805,8 @@ static void qcow2_update_options_commit(BlockDriverState *bs,
         cache_clean_timer_init(bs, bdrv_get_aio_context(bs));
     }
 
-    qcrypto_cipher_free(s->cipher);
-    s->cipher = r->cipher;
+    qapi_free_QCryptoBlockOpenOptions(s->fde_opts);
+    s->fde_opts = r->fde_opts;
 }
 
 static void qcow2_update_options_abort(BlockDriverState *bs,
@@ -854,7 +818,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,
     if (r->refcount_block_cache) {
         qcow2_cache_destroy(bs, r->refcount_block_cache);
     }
-    qcrypto_cipher_free(r->cipher);
+    qapi_free_QCryptoBlockOpenOptions(r->fde_opts);
 }
 
 static int qcow2_update_options(BlockDriverState *bs, QDict *options,
@@ -1138,10 +1102,15 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     }
 
     if (!(flags & BDRV_O_NO_IO) &&
-        bs->encrypted && !s->cipher) {
-        error_setg(errp, "Image is encrypted but no secret is provided");
-        ret = -EINVAL;
-        goto fail;
+        bs->encrypted) {
+        s->fde = qcrypto_block_open(s->fde_opts,
+                                    NULL, NULL,
+                                    errp);
+        if (!s->fde) {
+            error_setg(errp, "Could not setup encryption layer");
+            ret = -EINVAL;
+            goto fail;
+        }
     }
 
     s->cluster_cache = g_malloc(s->cluster_size);
@@ -1328,7 +1297,7 @@ static int64_t coroutine_fn qcow2_co_get_block_status(BlockDriverState *bs,
     }
 
     if (cluster_offset != 0 && ret != QCOW2_CLUSTER_COMPRESSED &&
-        !s->cipher) {
+        !s->fde) {
         index_in_cluster = sector_num & (s->cluster_sectors - 1);
         cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
         status |= BDRV_BLOCK_OFFSET_VALID | cluster_offset;
@@ -1378,7 +1347,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->fde) {
             cur_nr_sectors = MIN(cur_nr_sectors,
                 QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors);
         }
@@ -1450,7 +1419,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
             }
 
             if (bs->encrypted) {
-                assert(s->cipher);
+                assert(s->fde);
 
                 /*
                  * For encrypted images, read everything into a temporary
@@ -1484,10 +1453,12 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
                 goto fail;
             }
             if (bs->encrypted) {
-                assert(s->cipher);
+                assert(s->fde);
                 Error *err = NULL;
-                if (qcow2_encrypt_sectors(s, sector_num,  cluster_data,
-                                          cur_nr_sectors, false,
+                if (qcrypto_block_decrypt(s->fde,
+                                          sector_num,
+                                          cluster_data,
+                                          cur_nr_sectors,
                                           &err) < 0) {
                     error_free(err);
                     ret = -EIO;
@@ -1571,7 +1542,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
 
         if (bs->encrypted) {
             Error *err = NULL;
-            assert(s->cipher);
+            assert(s->fde);
             if (!cluster_data) {
                 cluster_data = qemu_try_blockalign(bs->file->bs,
                                                    QCOW_MAX_CRYPT_CLUSTERS
@@ -1586,8 +1557,11 @@ 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->fde,
+                                      sector_num,
+                                      cluster_data,
+                                      cur_nr_sectors,
+                                      &err) < 0) {
                 error_free(err);
                 ret = -EIO;
                 goto fail;
@@ -1698,8 +1672,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->fde);
+    s->fde = NULL;
 
     g_free(s->unknown_header_fields);
     cleanup_unknown_header_ext(bs);
@@ -1717,7 +1691,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
 {
     BDRVQcow2State *s = bs->opaque;
     int flags = s->flags;
-    QCryptoCipher *cipher = NULL;
+    QCryptoBlock *fde = NULL;
     QDict *options;
     Error *local_err = NULL;
     int ret;
@@ -1727,8 +1701,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;
+    fde = s->fde;
+    s->fde = NULL;
 
     qcow2_close(bs);
 
@@ -1753,7 +1727,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
         return;
     }
 
-    s->cipher = cipher;
+    s->fde = fde;
 }
 
 static size_t header_ext_add(char *buf, uint32_t magic, const void *s,
@@ -2961,9 +2935,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->fde);
 
-            if (encrypt != !!s->cipher) {
+            if (encrypt != !!s->fde) {
                 fprintf(stderr, "Changing the encryption flag is not "
                         "supported.\n");
                 return -ENOTSUP;
diff --git a/block/qcow2.h b/block/qcow2.h
index ea31b25..a41a1e3 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
@@ -257,7 +257,8 @@ typedef struct BDRVQcow2State {
 
     CoMutex lock;
 
-    QCryptoCipher *cipher; /* current cipher, NULL if no key yet */
+    QCryptoBlockOpenOptions *fde_opts; /* Disk encryption runtime options */
+    QCryptoBlock *fde; /* Disk encryption format driver */
     uint32_t crypt_method_header;
     uint64_t snapshots_offset;
     int snapshots_size;
-- 
2.5.0

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

* [Qemu-devel] [PATCH WIP 30/30] qcow2: add LUKS full disk encryption support
  2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
                   ` (28 preceding siblings ...)
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 29/30] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
@ 2015-11-20 18:04 ` Daniel P. Berrange
  29 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-20 18:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-block

The QCow2 format currently has support for built-in AES
encryption, however, this is fundamentally flawed from a
cryptographic security POV, so its use is deprecated.
The previously added generic full disk encryption driver
could be used to encrypt QCow2 files by either laying it
above or below the QCow2 driver in the QEMU BlockBackend
tree.

If it is layered above (FDE -> QCow2 -> File), then only
the image payload will be encrypted. There is no safe way
to auto-detect use of FDE for the image payload, as you
cannot safely distinguish between a QCow2 image that has
the FDE driver layered above in the host, from a QCow2
image where the guest is using LUKS over a partitionless
drive. Layering above the image is a valid use case, but
this auto-detection limitation makes it undesirable as
a default approach for QCow2 encryption.

If it is layered below (QCow2 -> FDE -> File), then both
the image payload and QCow2 headers are encrypted. This
makes it impossible to query the disk image to determine
its logical disk size, or backing file requirements
without first unlocking the decryption key. This is again
a valid use case for scenarios where it is desirable to
avoid any leakage of information about the underling disk
format, but it is undesirable as a default approach for
QCow2 encryption.

Thus this patch takes a third approach of integrating LUKS
support directly into the QCow2 file format. Only the image
payload is encrypted, the QCow2 file header remainins in
clear text. Thus makes it possible to probe info about the
disk size, backing files, etc without needing decryption
keys. Since use of LUKS is encoded in the QCow2 header, it
is still possible to reliabily distinguish host side encryption
from guest side encryption.

First a new QCow2 encryption scheme is defined to represent
the LUKS format, with a value '2' (0 == plain text, 1 == the
legacy AES format). A corresponding new QCow2 header extension
is defined to hold the LUKS partition header data. This stores
the various encryption parameters and key slot metadata and the
encrypted master keys. The payload of the QCow2 file does not
change in structure. Sectors are simply processed via the
QCryptoBlock object to apply/remove encryption when required.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow2.c        | 294 +++++++++++++++++++++++++++++++++++++++++++++++++--
 block/qcow2.h        |  11 +-
 docs/specs/qcow2.txt |  53 ++++++++++
 3 files changed, 347 insertions(+), 11 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 9158dd0..1650345 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -35,6 +35,8 @@
 #include "trace.h"
 #include "qemu/option_int.h"
 #include "crypto/secret.h"
+#include "qapi/opts-visitor.h"
+#include "qapi-visit.h"
 
 /*
   Differences with QCOW:
@@ -61,6 +63,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_LUKS_HEADER 0x4c554b53
 
 static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
 {
@@ -75,6 +78,63 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
 }
 
 
+struct QCow2FDEData {
+    BlockDriverState *bs;
+    size_t hdr_ext_offset; /* Offset of encryption header extension data */
+    size_t hdr_ext_length; /* Length of encryption header extension data */
+};
+
+static ssize_t qcow2_header_read_func(QCryptoBlock *block,
+                                      size_t offset,
+                                      uint8_t *buf,
+                                      size_t buflen,
+                                      Error **errp,
+                                      void *opaque)
+{
+    struct QCow2FDEData *data = opaque;
+    ssize_t ret;
+
+    if ((offset + buflen) > data->hdr_ext_length) {
+        error_setg_errno(errp, EINVAL,
+                         "Request for data outside of extension header");
+        return -1;
+    }
+
+    ret = bdrv_pread(data->bs->file->bs,
+                     data->hdr_ext_offset + offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not read encryption header");
+        return ret;
+    }
+    return ret;
+}
+
+
+static ssize_t qcow2_header_write_func(QCryptoBlock *block,
+                                       size_t offset,
+                                       const uint8_t *buf,
+                                       size_t buflen,
+                                       Error **errp,
+                                       void *opaque)
+{
+    struct QCow2FDEData *data = opaque;
+    ssize_t ret;
+
+    if ((offset + buflen) > data->hdr_ext_length) {
+        error_setg_errno(errp, EINVAL,
+                         "Request for data outside of extension header");
+        return -1;
+    }
+
+    ret = bdrv_pwrite(data->bs, data->hdr_ext_offset + offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not read encryption header");
+        return ret;
+    }
+    return ret;
+}
+
+
 /* 
  * read qcow2 extension and fill bs
  * start reading from start_offset
@@ -90,6 +150,7 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
     QCowExtension ext;
     uint64_t offset;
     int ret;
+    struct QCow2FDEData fdedata;
 
 #ifdef DEBUG_EXT
     printf("qcow2_read_extensions: start=%ld end=%ld\n", start_offset, end_offset);
@@ -160,6 +221,24 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
             }
             break;
 
+        case QCOW2_EXT_MAGIC_LUKS_HEADER:
+            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+                error_setg(errp, "LUKS header extension only "
+                           "expected with LUKS encryption method");
+                return -EINVAL;
+            }
+            fdedata.bs = bs;
+            fdedata.hdr_ext_offset = offset;
+            fdedata.hdr_ext_length = ext.len;
+
+            s->fde = qcrypto_block_open(s->fde_opts,
+                                        qcow2_header_read_func,
+                                        &fdedata,
+                                        errp);
+            if (!s->fde) {
+                return -EINVAL;
+            }
+            break;
         default:
             /* unknown magic - save it in case we need to rewrite the header */
             {
@@ -474,7 +553,7 @@ static QemuOptsList qcow2_runtime_opts = {
             .help = "Clean unused cache entries after this time (in seconds)",
         },
         {
-            .name = QCOW2_OPT_KEY_ID,
+            .name = QCOW2_OPT_FDE_KEY_ID,
             .type = QEMU_OPT_STRING,
             .help = "ID of the secret that provides the encryption key",
         },
@@ -595,6 +674,98 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
     }
 }
 
+
+static QCryptoBlockOpenOptions *
+qcow2_fde_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_QCOWAES:
+        ret->u.qcowaes = g_new0(QCryptoBlockOptionsQCowAES, 1);
+        visit_type_QCryptoBlockOptionsQCowAES(opts_get_visitor(ov),
+                                              &ret->u.qcowaes,
+                                              "qcowaes", &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_fde_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_QCOWAES:
+        ret->u.qcowaes = g_new0(QCryptoBlockOptionsQCowAES, 1);
+        visit_type_QCryptoBlockOptionsQCowAES(opts_get_visitor(ov),
+                                              &ret->u.qcowaes,
+                                              "qcowaes", &local_err);
+        break;
+
+    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;
+}
+
+
 typedef struct Qcow2ReopenState {
     Qcow2Cache *l2_table_cache;
     Qcow2Cache *refcount_block_cache;
@@ -761,13 +932,30 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
     r->discard_passthrough[QCOW2_DISCARD_OTHER] =
         qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
 
-    if (s->crypt_method_header) {
-        r->fde_opts = g_new0(QCryptoBlockOpenOptions, 1);
-        r->fde_opts->format = Q_CRYPTO_BLOCK_FORMAT_QCOWAES;
-        r->fde_opts->u.qcowaes = g_new0(QCryptoBlockOptionsQCowAES, 1);
+    switch (s->crypt_method_header) {
+    case QCOW_CRYPT_NONE:
+        break;
+
+    case QCOW_CRYPT_AES:
+        r->fde_opts = qcow2_fde_open_opts_init(
+            Q_CRYPTO_BLOCK_FORMAT_QCOWAES,
+            opts,
+            errp);
+        break;
 
-        r->fde_opts->u.qcowaes->keyid =
-            g_strdup(qemu_opt_get(opts, QCOW2_OPT_KEY_ID));
+    case QCOW_CRYPT_LUKS:
+        r->fde_opts = qcow2_fde_open_opts_init(
+            Q_CRYPTO_BLOCK_FORMAT_QCOWAES,
+            opts,
+            errp);
+        break;
+
+    default:
+        g_assert_not_reached();
+    }
+    if (s->crypt_method_header &&
+        !r->fde_opts) {
+        goto fail;
     }
 
     ret = 0;
@@ -1102,7 +1290,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
     }
 
     if (!(flags & BDRV_O_NO_IO) &&
-        bs->encrypted) {
+        s->crypt_method_header == QCOW_CRYPT_AES) {
         s->fde = qcrypto_block_open(s->fde_opts,
                                     NULL, NULL,
                                     errp);
@@ -1143,6 +1331,13 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
         goto fail;
     }
 
+    if (!(flags & BDRV_O_NO_IO) &&
+        bs->encrypted && !s->fde) {
+        error_setg(errp, "No encryption layer was initiailized");
+        ret = -EINVAL;
+        goto fail;
+    }
+
     /* read the backing file name */
     if (header.backing_file_offset != 0) {
         len = header.backing_file_size;
@@ -2013,6 +2208,10 @@ static int qcow2_create2(const char *filename, int64_t total_size,
 {
     int cluster_bits;
     QDict *options;
+    const char *fdestr;
+    QCryptoBlockCreateOptions *fdeopts = NULL;
+    QCryptoBlock *fde = NULL;
+    size_t i;
 
     /* Calculate cluster_bits */
     cluster_bits = ctz32(cluster_size);
@@ -2135,8 +2334,35 @@ 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);
+    fdestr = qemu_opt_get(opts, QCOW2_OPT_FDE);
+    if (fdestr) {
+        for (i = 0; i < Q_CRYPTO_BLOCK_FORMAT_MAX; i++) {
+            if (g_str_equal(QCryptoBlockFormat_lookup[i],
+                            fdestr)) {
+                fdeopts = qcow2_fde_create_opts_init(i,
+                                                     opts,
+                                                     errp);
+                if (!fdeopts) {
+                    goto out;
+                }
+                break;
+            }
+        }
+        if (!fdeopts) {
+            error_setg(errp, "Unknown fde format %s", fdestr);
+            goto out;
+        }
+        switch (fdeopts->format) {
+        case Q_CRYPTO_BLOCK_FORMAT_QCOWAES:
+            header->crypt_method = cpu_to_be32(QCOW_CRYPT_AES);
+            break;
+        case Q_CRYPTO_BLOCK_FORMAT_LUKS:
+            header->crypt_method = cpu_to_be32(QCOW_CRYPT_LUKS);
+            break;
+        default:
+            error_setg(errp, "Unsupported fde format %s", fdestr);
+            goto out;
+        }
     } else {
         header->crypt_method = cpu_to_be32(QCOW_CRYPT_NONE);
     }
@@ -2153,6 +2379,24 @@ static int qcow2_create2(const char *filename, int64_t total_size,
         goto out;
     }
 
+    /* XXXX this is roughly where we need to write the LUKS header,
+     * but its not going to fit inside the first cluster. Need to
+     * allow qcow2 header extensions to consume >1 cluster....
+     */
+    if (fdeopts && 0) {
+        struct QCow2FDEData fdedata;
+        fdedata.bs = bs;
+        fdedata.hdr_ext_offset = cluster_size;
+
+        fde = qcrypto_block_create(fdeopts,
+                                   qcow2_header_write_func,
+                                   &fdedata,
+                                   errp);
+        if (!fde) {
+            goto out;
+        }
+    }
+
     /* Write a refcount table with one refcount block */
     refcount_table = g_malloc0(2 * cluster_size);
     refcount_table[0] = cpu_to_be64(2 * cluster_size);
@@ -3136,6 +3380,36 @@ static QemuOptsList qcow2_create_opts = {
             .help = "Width of a reference count entry in bits",
             .def_value_str = "16"
         },
+        {
+            .name = QCOW2_OPT_FDE_KEY_ID,
+            .type = QEMU_OPT_STRING,
+            .help = "ID of the secret that provides the encryption key",
+        },
+        {
+            .name = QCOW2_OPT_FDE_CIPHER_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm",
+        },
+        {
+            .name = QCOW2_OPT_FDE_CIPHER_MODE,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher mode",
+        },
+        {
+            .name = QCOW2_OPT_FDE_IVGEN_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator algorithm",
+        },
+        {
+            .name = QCOW2_OPT_FDE_IVGEN_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of IV generator hash algorithm",
+        },
+        {
+            .name = QCOW2_OPT_FDE_HASH_ALG,
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption hash algorithm",
+        },
         { /* end of list */ }
     }
 };
diff --git a/block/qcow2.h b/block/qcow2.h
index a41a1e3..69b99ae 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -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
@@ -97,7 +98,15 @@
 #define QCOW2_OPT_L2_CACHE_SIZE "l2-cache-size"
 #define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"
 #define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval"
-#define QCOW2_OPT_KEY_ID "keyid"
+
+#define QCOW2_OPT_FDE "fde"
+#define QCOW2_OPT_FDE_KEY_ID "keyid"
+#define QCOW2_OPT_FDE_CIPHER_ALG "cipher_alg"
+#define QCOW2_OPT_FDE_CIPHER_MODE "cipher_mode"
+#define QCOW2_OPT_FDE_IVGEN_ALG "ivgen_alg"
+#define QCOW2_OPT_FDE_IVGEN_HASH_ALG "ivgen_hash_alg"
+#define QCOW2_OPT_FDE_HASH_ALG "hash_alg"
+
 
 typedef struct QCowHeader {
     uint32_t magic;
diff --git a/docs/specs/qcow2.txt b/docs/specs/qcow2.txt
index f236d8c..3742f01 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 - LUKS partition header + key data
                         other      - Unknown header extension, can be safely
                                      ignored
 
@@ -166,6 +168,57 @@ the header extension data. Each entry look like this:
                     terminated if it has full length)
 
 
+== LUKS header and key slots ==
+
+If the 'crypt_method' header field specifies LUKS ( value == 2), the qcow2
+LUKS header extension is mandatory, to provide the data tables used by the
+LUKS encryption format.
+
+The first 592 bytes 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 QCow2 header extension.
+
+In the LUKS key slots header, the "key-material-offset" is an absolute
+location in the qcow2 container.
+
+Logically the layout looks like
+
+  +-----------------------------+
+  | QCow2 header                |
+  +-----------------------------+
+  | QCow2 header extension X    |
+  +-----------------------------+
+  | QCow2 header extension LUKS |
+  | +-------------------------+ |
+  | | LUKS partition header   | |
+  | +-------------------------+ |
+  | | LUKS key material 1     | |
+  | +-------------------------+ |
+  | | LUKS key material 2     | |
+  | +-------------------------+ |
+  | | LUKS key material ...   | |
+  | +-------------------------+ |
+  | | LUKS key material 8     | |
+  | +-------------------------+ |
+  +-----------------------------+
+  | QCow2 header extension ...  |
+  +-----------------------------+
+  | QCow2 header extension Z    |
+  +-----------------------------+
+  | QCow2 cluster payload       |
+  .                             .
+  .                             .
+  .                             .
+  |                             |
+  +-----------------------------+
+
 == Host cluster management ==
 
 qcow2 manages the allocation of host clusters by maintaining a reference count
-- 
2.5.0

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

* Re: [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling Daniel P. Berrange
@ 2015-11-20 22:09   ` Eric Blake
  2015-11-23 12:33     ` Daniel P. Berrange
  0 siblings, 1 reply; 37+ messages in thread
From: Eric Blake @ 2015-11-20 22:09 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: qemu-block

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

On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
> Introduce a new QCryptoSecret object class which will be used
> for providing passwords and keys to other objects which need
> sensitive credentials.
> 
> The new object can provide secret values directly as properties,
> or indirectly via a file. The latter includes support for file
> descriptor passing syntax on UNIX platforms. Ordinarily passing
> secret values directly as properties is insecure, since they
> are visible in process listings, or in log files showing the
> CLI args / QMP commands. It is possible to use AES-256-CBC to
> encrypt the secret values though, in which case all that is
> visible is the ciphertext.  For adhoc developer testing though,
> it is fine to provide the secrets directly without encryption
> so this is not explicitly forbidden.
> 
> The anticipated scenario is that libvirtd will create a random
> master key per QEMU instance (eg /var/run/libvirt/qemu/$VMNAME.key)
> and will use that key to encrypt all passwords it provides to
> QEMU via '-object secret,....'.  This avoids the need for libvirt
> (or other mgmt apps) to worry about file descriptor passing.
> 
> It also makes life easier for people who are scripting the
> management of QEMU, for whom FD passing is significantly more
> complex.
> 
> Providing data inline (insecure, only for adhoc dev tetsing)

s/tetsing/testing/

> 
>   $QEMU -object secret,id=sec0,data=letmein
> 
> Providing data indirectly in raw format
> 
>   echo -n "letmein" > mypasswd.txt

'echo -n' is non-portable (just this month, there was a user complaining
that gentoo disables it on dash, even though upstream dash supports it).
 Might be better as:

printf "letmein" > mypasswd.txt

>   $QEMU -object secret,id=sec0,file=mypasswd.txt
> 
> Providing data indirectly in base64 format
> 
>   $QEMU -object secret,id=sec0,file=mykey.b64,format=base64
> 
> Providing data with encyption

s/encyption/encryption/

> 
>   $QEMU -object secret,id=master0,file=mykey.b64,format=base64 \
>         -object secret,id=sec0,data=[base64 ciphertext],\
> 	           keyid=master0,iv=[base64 IV],format=base64
> 
> Note that 'format' here refers to the format of the ciphertext
> data. The decrypted data must always be in raw byte format.
> 
> More examples are shown in the updated docs.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  crypto/Makefile.objs       |   1 +
>  crypto/secret.c            | 567 +++++++++++++++++++++++++++++++++++++++++++++
>  include/crypto/secret.h    | 148 ++++++++++++
>  qapi/crypto.json           |  14 ++
>  qemu-options.hx            |  78 +++++++
>  tests/.gitignore           |   1 +
>  tests/Makefile             |   2 +
>  tests/test-crypto-secret.c | 446 +++++++++++++++++++++++++++++++++++
>  8 files changed, 1257 insertions(+)
>  create mode 100644 crypto/secret.c
>  create mode 100644 include/crypto/secret.h
>  create mode 100644 tests/test-crypto-secret.c

Just focusing on the interface on this pass:


> +
> +static const char *base64_valid_chars =
> +    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
> +
> +static int
> +qcrypto_secret_validate_base64(const uint8_t *input,
> +                               size_t inputlen,
> +                               Error **errp)

Don't we already have base64 utility methods available?

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

> +/**
> + * QCryptoSecret:
> + *
> + * The QCryptoSecret object provides storage of secrets,
> + * which may be user passwords, encryption keys or any
> + * other kind of sensitive data that is represented as
> + * a sequence of bytes.
> + *
> + * The sensitive data associated with the secret can
> + * be provided directly via the 'data' property, or
> + * indirectly via the 'file' property. In the latter
> + * case there is support for file descriptor passing
> + * via the usual /dev/fdset/NN syntax that QEMU uses.
> + *
> + * The data for a secret can be provided in two formats,
> + * either as a UTF-8 string (the default), or as base64
> + * encoded 8-bit binary data. The latter is appropriate
> + * for  raw encrypton keys, while the former is appropriate

s/for  raw/for raw/
s/encrypton/encryption/

> + * for user entered passwords.
> + *
> + * The data may be optionally encrypted with AES-256-CBC,
> + * and the decryption key provided by another
> + * QCryptoSecret instance identified by the 'keyid'
> + * property. When passing sensitive data directly
> + * via the 'data' property it is strongly recommended
> + * to use the AES encryption facility to prevent the
> + * sensitive data being exposed in the process listing
> + * or system log files.
> + *
> + * Providing data directly, insecurely (suitable for
> + * adhoc developer testing only)
> + *
> + *  $QEMU -object secret,id=sec0,data=letmein
> + *
> + * Providing data indirectly:
> + *
> + *  # echo -n "letmein" > password.txt

same comment as on commit message

> + *  # $QEMU \
> + *      -object secret,id=sec0,file=password.txt
> + *
> + * Using a master encryption key with data.
> + *
> + * The master key needs to be created as 32 secure
> + * random bytes (optionally base64 encoded)
> + *
> + *  # openssl rand -base64 32 > key.b64
> + *  # KEY=$(base64 -d key.b64 | hexdump  -v -e '/1 "%02X"')
> + *
> + * Each secret to be encrypted needs to have a random
> + * initialization vector generated. These do not need
> + * to be kept secret
> + *
> + *  # openssl rand -base64 16 > iv.b64
> + *  # IV=$(base64 -d iv.b64 | hexdump  -v -e '/1 "%02X"')
> + *
> + * A secret to be defined can now be encrypted
> + *
> + *  # SECRET=$(echo -n "letmein" |
> + *             openssl enc -aes-256-cbc -a -K $KEY -iv $IV)
> + *
> + * When launching QEMU, create a master secret pointing
> + * to key.b64 and specify that to be used to decrypt
> + * the user password
> + *
> + *  # $QEMU \
> + *      -object secret,id=secmaster0,format=base64,file=key.b64 \
> + *      -object secret,id=sec0,keyid=secmaster0,format=base64,\
> + *          data=$SECRET,iv=$(<iv.b64)
> + *
> + * When encrypting, the data can still be provided via an
> + * external file, in which case it is possible to use either
> + * raw binary data, or base64 encoded. This example uses
> + * raw format
> + *
> + *  # echo -n "letmein" |
> + *       openssl enc -aes-256-cbc -K $KEY -iv $IV -o pw.aes
> + *  # $QEMU \
> + *      -object secret,id=secmaster0,format=base64,file=key.b64 \
> + *      -object secret,id=sec0,keyid=secmaster0,\
> + *          file=pw.aes,iv=$(<iv.b64)
> + *
> + * Note that the ciphertext can be in either raw or base64
> + * format, as indicated by the 'format' parameter, but the
> + * plaintext resulting from decryption is expected to always
> + * be in raw format.
> + */


> +++ b/qapi/crypto.json
> @@ -19,3 +19,17 @@
>  { 'enum': 'QCryptoTLSCredsEndpoint',
>    'prefix': 'QCRYPTO_TLS_CREDS_ENDPOINT',
>    'data': ['client', 'server']}
> +
> +
> +##
> +# QCryptoSecretFormat:
> +#
> +# The data format that the secret is provided in
> +#
> +# @raw: raw bytes. When encoded in JSON only valid UTF-8 sequences can be used
> +# @base64: arbitrary base64 encoded binary data
> +# Since: 2.5

You've missed 2.5.  Probably need to tweak the whole series to call out 2.6.

> +##
> +{ 'enum': 'QCryptoSecretFormat',
> +  'prefix': 'QCRYPTO_SECRET_FORMAT',
> +  'data': ['raw', 'base64']}
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 0eea4ee..dd3f7f8 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -3670,6 +3670,7 @@ queue @var{all|rx|tx} is an option that can be applied to any netfilter.
>  @option{tx}: the filter is attached to the transmit queue of the netdev,
>               where it will receive packets sent by the netdev.
>  
> +
>  @item -object filter-dump,id=@var{id},netdev=@var{dev},file=@var{filename}][,maxlen=@var{len}]

Why the added blank line here?

-- 
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] 37+ messages in thread

* Re: [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options
  2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options Daniel P. Berrange
@ 2015-11-20 22:15   ` Eric Blake
  2015-11-23 12:40     ` Daniel P. Berrange
  0 siblings, 1 reply; 37+ messages in thread
From: Eric Blake @ 2015-11-20 22:15 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: qemu-block

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

On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
> Add a 'keyid' parameter that refers to the ID of a
> QCryptoSecret instance that provides the encryption key.
> 
> $QEMU \
>     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
>     -drive file=/home/berrange/encrypted.qcow2,keyid=sec0
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  block/qcow2.c        | 80 +++++++++++++++++++++++++++++++++++++---------------
>  block/qcow2.h        |  1 +
>  qapi/block-core.json |  8 ++++--
>  3 files changed, 64 insertions(+), 25 deletions(-)

> +++ b/qapi/block-core.json
> @@ -1698,7 +1698,7 @@
>  # Driver specific block device options for qcow.
>  #
>  # @keyid:                 #optional ID of the "secret" object providing the
> -#                         AES decryption key.
> +#                         AES decryption key (since 2.5)
>  #
>  # Since: 2.5

I already pointed this out on the previous post, but this hunk is wrong
(since the entire BlockdevOptionsQcow struct is new); it instead belongs...

>  ##
> @@ -1742,6 +1742,9 @@
>  #                         caches. The interval is in seconds. The default value
>  #                         is 0 and it disables this feature (since 2.5)
>  #
> +# @keyid:                 #optional ID of the "secret" object providing the
> +#                         AES decryption key.

...here as part of BlockdevOptionsQcow2.  Also, I wonder if inheriting
from BlockdevOptionsQcow is any easier here than just declaring keyid
directly.

> +#
>  # Since: 1.7
>  ##
>  { 'struct': 'BlockdevOptionsQcow2',
> @@ -1754,7 +1757,8 @@
>              '*cache-size': 'int',
>              '*l2-cache-size': 'int',
>              '*refcount-cache-size': 'int',
> -            '*cache-clean-interval': 'int' } }
> +            '*cache-clean-interval': 'int',
> +            '*keyid': 'str' } }
>  
>  
>  ##
> 

-- 
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] 37+ messages in thread

* Re: [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling
  2015-11-20 22:09   ` Eric Blake
@ 2015-11-23 12:33     ` Daniel P. Berrange
  2015-11-23 13:39       ` Markus Armbruster
  0 siblings, 1 reply; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-23 12:33 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, qemu-block

On Fri, Nov 20, 2015 at 03:09:25PM -0700, Eric Blake wrote:
> On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
> > +
> > +static const char *base64_valid_chars =
> > +    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
> > +
> > +static int
> > +qcrypto_secret_validate_base64(const uint8_t *input,
> > +                               size_t inputlen,
> > +                               Error **errp)
> 
> Don't we already have base64 utility methods available?

We normally use glib,  g_base64_encode/decode. Unfortunately the
decode method doesn't provide any usefull error reporting facility.
It just silently skips any characters that are outside the valid
set.  So the only way I could get any kind of sensible error report
was to do this validation myself against the set of permitted base64
characters.


> > +++ b/qapi/crypto.json
> > @@ -19,3 +19,17 @@
> >  { 'enum': 'QCryptoTLSCredsEndpoint',
> >    'prefix': 'QCRYPTO_TLS_CREDS_ENDPOINT',
> >    'data': ['client', 'server']}
> > +
> > +
> > +##
> > +# QCryptoSecretFormat:
> > +#
> > +# The data format that the secret is provided in
> > +#
> > +# @raw: raw bytes. When encoded in JSON only valid UTF-8 sequences can be used
> > +# @base64: arbitrary base64 encoded binary data
> > +# Since: 2.5
> 
> You've missed 2.5.  Probably need to tweak the whole series to call out 2.6.

Yep.

> > +##
> > +{ 'enum': 'QCryptoSecretFormat',
> > +  'prefix': 'QCRYPTO_SECRET_FORMAT',
> > +  'data': ['raw', 'base64']}
> > diff --git a/qemu-options.hx b/qemu-options.hx
> > index 0eea4ee..dd3f7f8 100644
> > --- a/qemu-options.hx
> > +++ b/qemu-options.hx
> > @@ -3670,6 +3670,7 @@ queue @var{all|rx|tx} is an option that can be applied to any netfilter.
> >  @option{tx}: the filter is attached to the transmit queue of the netdev,
> >               where it will receive packets sent by the netdev.
> >  
> > +
> >  @item -object filter-dump,id=@var{id},netdev=@var{dev},file=@var{filename}][,maxlen=@var{len}]
> 
> Why the added blank line here?

Rebase error I presume

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] 37+ messages in thread

* Re: [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options
  2015-11-20 22:15   ` Eric Blake
@ 2015-11-23 12:40     ` Daniel P. Berrange
  0 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-23 12:40 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, qemu-block

On Fri, Nov 20, 2015 at 03:15:27PM -0700, Eric Blake wrote:
> On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
> > Add a 'keyid' parameter that refers to the ID of a
> > QCryptoSecret instance that provides the encryption key.
> > 
> > $QEMU \
> >     -object secret,id=sec0,filename=/home/berrange/encrypted.pw \
> >     -drive file=/home/berrange/encrypted.qcow2,keyid=sec0
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  block/qcow2.c        | 80 +++++++++++++++++++++++++++++++++++++---------------
> >  block/qcow2.h        |  1 +
> >  qapi/block-core.json |  8 ++++--
> >  3 files changed, 64 insertions(+), 25 deletions(-)
> 
> > +++ b/qapi/block-core.json
> > @@ -1698,7 +1698,7 @@
> >  # Driver specific block device options for qcow.
> >  #
> >  # @keyid:                 #optional ID of the "secret" object providing the
> > -#                         AES decryption key.
> > +#                         AES decryption key (since 2.5)
> >  #
> >  # Since: 2.5
> 
> I already pointed this out on the previous post, but this hunk is wrong
> (since the entire BlockdevOptionsQcow struct is new); it instead belongs...
> 
> >  ##
> > @@ -1742,6 +1742,9 @@
> >  #                         caches. The interval is in seconds. The default value
> >  #                         is 0 and it disables this feature (since 2.5)
> >  #
> > +# @keyid:                 #optional ID of the "secret" object providing the
> > +#                         AES decryption key.
> 
> ...here as part of BlockdevOptionsQcow2.  Also, I wonder if inheriting
> from BlockdevOptionsQcow is any easier here than just declaring keyid
> directly.

When I fully integrate LUKS support in qcow2, there will be several
more parameters added to this struct, which I won't be adding to
qcow, since I don't fancy doing any work on qcow code to improve
its encryption, since its essentially obsolte. So on this basis,
I don't think inheriting BlockdevOptionsQcow will have tangible
benefit.


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] 37+ messages in thread

* Re: [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling
  2015-11-23 12:33     ` Daniel P. Berrange
@ 2015-11-23 13:39       ` Markus Armbruster
  2015-11-23 14:43         ` Daniel P. Berrange
  0 siblings, 1 reply; 37+ messages in thread
From: Markus Armbruster @ 2015-11-23 13:39 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: qemu-devel, qemu-block

"Daniel P. Berrange" <berrange@redhat.com> writes:

> On Fri, Nov 20, 2015 at 03:09:25PM -0700, Eric Blake wrote:
>> On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
>> > +
>> > +static const char *base64_valid_chars =
>> > +    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
>> > +
>> > +static int
>> > +qcrypto_secret_validate_base64(const uint8_t *input,
>> > +                               size_t inputlen,
>> > +                               Error **errp)
>> 
>> Don't we already have base64 utility methods available?
>
> We normally use glib,  g_base64_encode/decode. Unfortunately the
> decode method doesn't provide any usefull error reporting facility.
> It just silently skips any characters that are outside the valid
> set.  So the only way I could get any kind of sensible error report
> was to do this validation myself against the set of permitted base64
> characters.

Yes.  Same problem elsewhere, e.g. ringbuf-write.  qapi-schema.json:

#          - base64: data must be base64 encoded text.  Its binary
#            decoding gets written.
#            Bug: invalid base64 is currently not rejected.
#            Whitespace *is* invalid.

This suggests that we shouldn't bury this in crypto/, but instead add it
to util/.

A replacement for g_base64_decode() could be easier to use than a
checker function to use in addition to g_base64_decode(),

[...]

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

* Re: [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling
  2015-11-23 13:39       ` Markus Armbruster
@ 2015-11-23 14:43         ` Daniel P. Berrange
  0 siblings, 0 replies; 37+ messages in thread
From: Daniel P. Berrange @ 2015-11-23 14:43 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, qemu-block

On Mon, Nov 23, 2015 at 02:39:27PM +0100, Markus Armbruster wrote:
> "Daniel P. Berrange" <berrange@redhat.com> writes:
> 
> > On Fri, Nov 20, 2015 at 03:09:25PM -0700, Eric Blake wrote:
> >> On 11/20/2015 11:04 AM, Daniel P. Berrange wrote:
> >> > +
> >> > +static const char *base64_valid_chars =
> >> > +    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
> >> > +
> >> > +static int
> >> > +qcrypto_secret_validate_base64(const uint8_t *input,
> >> > +                               size_t inputlen,
> >> > +                               Error **errp)
> >> 
> >> Don't we already have base64 utility methods available?
> >
> > We normally use glib,  g_base64_encode/decode. Unfortunately the
> > decode method doesn't provide any usefull error reporting facility.
> > It just silently skips any characters that are outside the valid
> > set.  So the only way I could get any kind of sensible error report
> > was to do this validation myself against the set of permitted base64
> > characters.
> 
> Yes.  Same problem elsewhere, e.g. ringbuf-write.  qapi-schema.json:
> 
> #          - base64: data must be base64 encoded text.  Its binary
> #            decoding gets written.
> #            Bug: invalid base64 is currently not rejected.
> #            Whitespace *is* invalid.
> 
> This suggests that we shouldn't bury this in crypto/, but instead add it
> to util/.
> 
> A replacement for g_base64_decode() could be easier to use than a
> checker function to use in addition to g_base64_decode(),

Yeah, that's a good idea. I'll look at 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] 37+ messages in thread

end of thread, other threads:[~2015-11-23 14:44 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-20 18:04 [Qemu-devel] [PATCH WIP 00/30] Support for full disk encryption Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 01/30] crypto: add QCryptoSecret object class for password/key handling Daniel P. Berrange
2015-11-20 22:09   ` Eric Blake
2015-11-23 12:33     ` Daniel P. Berrange
2015-11-23 13:39       ` Markus Armbruster
2015-11-23 14:43         ` Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 02/30] crypto: add support for loading encrypted x509 keys Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 03/30] qcow: add a 'keyid' parameter to qcow options Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 04/30] qcow2: add a 'keyid' parameter to qcow2 options Daniel P. Berrange
2015-11-20 22:15   ` Eric Blake
2015-11-23 12:40     ` Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 05/30] qom: add user_creatable_add & user_creatable_del methods Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 06/30] qemu-img: add support for --object command line arg Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 07/30] qemu-nbd: " Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 08/30] qemu-io: " Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 09/30] qemu-io: allow specifying image as a set of options args Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 10/30] qemu-nbd: " Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 11/30] qemu-img: " Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 12/30] block: rip out all traces of password prompting Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 13/30] block: remove all encryption handling APIs Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 14/30] block: remove support for writing to qcow/qcow2 encrypted images Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 15/30] qcow2: make qcow2_encrypt_sectors encrypt in place Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 16/30] crypto: add ability to query the cipher key, block & IV lens Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 17/30] crypto: add method for querying hash digest size Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 18/30] crypto: move QCryptoHashAlgorithm enum definition into QAPI Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 19/30] crypto: move QCryptoCipherAlgorithm/Mode enum definitions " Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 20/30] crypto: ensure qapi/crypto.json is listed in qapi-modules Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 21/30] crypto: add cryptographic random byte source Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 22/30] crypto: add support for PBKDF2 algorithm Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 23/30] crypto: add support for generating initialization vectors Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 24/30] crypto: add support for anti-forensic split algorithm Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 25/30] crypto: fix transposed arguments in cipher error message Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 26/30] crypto: add block encryption framework Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 27/30] crypto: implement the LUKS block encryption format Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 28/30] block: add generic full disk encryption driver Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 29/30] qcow2: convert QCow2 to use QCryptoBlock for encryption Daniel P. Berrange
2015-11-20 18:04 ` [Qemu-devel] [PATCH WIP 30/30] qcow2: add LUKS full disk encryption support Daniel P. Berrange

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.