diff mbox

[v2,06/17] crypto: add block encryption framework

Message ID 1453311539-1193-7-git-send-email-berrange@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel P. Berrangé Jan. 20, 2016, 5:38 p.m. UTC
Add a generic framework for support different block encryption
formats. Upon instantiating a QCryptoBlock object, it will read
the encryption header and extract the encryption keys. It is
then possible to call methods to encrypt/decrypt data buffers.

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

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

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

Comments

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

> +++ b/crypto/block-qcow.c
> @@ -0,0 +1,167 @@
> +/*
> + * QEMU Crypto block device encryption QCow/QCow2 AES-CBC format
> + *
> + * Copyright (c) 2015-2016 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */

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

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

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

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

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

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


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

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

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

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

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

> +++ b/crypto/blockpriv.h

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

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

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

No documentation on any of these contracts?

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

Why gboolean instead of bool?

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


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

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

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

s/supoported/supported/

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

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

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

s/irrevokably/irrevocably/

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

s/supoported/supported/

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

From the guest's point of view, right?

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

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

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


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

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

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

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

Heh, yeah.

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

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

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

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

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

Sure.

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

Ok will do.

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

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


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

Yep


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

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


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

Ok


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

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

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


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

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


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

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

git config diff.orderFile /path/to/foo

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

Patch

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