Support LUKS encryption in block devices
Subsystems (2)
cover Support LUKS encryption in block devices
This is a posting of the previously submitted work in progress code:
https://lists.gnu.org/archive/html/qemu-devel/2015-11/msg04748.html
This series depends on these previously submitted patches to the block tools:
https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg04354.html
As can be guessed from the subject, the primary goal of this patch series is to support LUKS encryption in the QEMU block layer. QEMU has increasingly been adding native clients for network block protocols (RBD, gluster, NFS, iSCSI, etc) and apps like OpenStack are embracing them as it is much easier to deal with this from a management POV than to deal with the kernel block layer & userspace tools. Unfortunately when using QEMU native clients, apps are locked out of using dm-crypt and LUKS which is undesirable.
This series introduces two new features to the block layer. First there is a general purpose 'luks' format driver which can be layered over any other existing block driver. eg it can be layed above RBD, iSCSI, etc. Second the qcow2 file format is extended so that its embedded encryption can be replaced with the LUKS data format. While you could just layer the general purpose luks driver over qcow2, this is slightly less desirable, as it removes the ability to reliably auto-detect that LUKS is used by QEMU, as opposed to used by the guest OS. Having use of LUKS encoded in the qcow2 header addresses this.
The code is designed such that there is a strict separation between the full disk encryption format and the block I/O layer. Thus there is an generic API for dealing with full disk encryption added to the crypto/ subsystem. The block layer merely calls this FDE API when required, which serves to minimize the code present in the already complex block layer.
The first 4 patches add some supporting APIs to the crypto subsystem.
The 5-6 patches introduce the general full disk encryption API to the crypto subsystem and LUKS implementation
Patches 7-12 add the new 'luks' block driver format and convert qcow & qcow2 to the new FDE APIs, enabling LUKS in qcow2 (but not qcow) at the same time.
Patches 13-14 clean up the horrible password handling cruft in the block layer and monitor.
Patch 15 blocks use of the legacy qcow[2] encryption from the system emulators.
Some notable things since the original WIP posting
- QAPI parameters defined for the encryption key ID
- The qcow2 integration of LUKS is working, using extra allocated clusters to store LUKS header instead of trying to expand the main qcow2 header region to > 1 cluster
- qemu-img info now works without prompting for decryption password
- Unit testing of pbkdf2, afsplit and ivgen APis.
Still to do
- Unit testing coverage of the full disk encryption APIs
- Functional testing of LUKS driver via the qemu-iotests for the block layer
- Use GNUTLS random API for the random byte source
- Add support for XTS cipher mode for dm-crypt compat
Daniel P. Berrange (15):
crypto: add cryptographic random byte source
crypto: add support for PBKDF2 algorithm
crypto: add support for generating initialization vectors
crypto: add support for anti-forensic split algorithm
crypto: add block encryption framework
crypto: implement the LUKS block encryption format
block: add flag to indicate that no I/O will be performed
block: add generic full disk encryption driver
qcow2: make qcow2_encrypt_sectors encrypt in place
qcow2: convert QCow2 to use QCryptoBlock for encryption
qcow: make encrypt_sectors encrypt in place
qcow: convert QCow to use QCryptoBlock for encryption
block: rip out all traces of password prompting
block: remove all encryption handling APIs
block: remove support for legecy AES qcow/qcow2 encryption
block.c | 94 +--- block/Makefile.objs | 2 + block/fde.c | 538 +++++++++++++++++++++ block/qapi.c | 2 +- block/qcow.c | 199 ++++---- block/qcow2-cluster.c | 55 +-- block/qcow2.c | 511 +++++++++++++++++--- block/qcow2.h | 24 +- blockdev.c | 40 +- crypto/Makefile.objs | 10 + crypto/afsplit.c | 194 ++++++++ crypto/block-luks.c | 1092 ++++++++++++++++++++++++++++++++++++++++++ crypto/block-luks.h | 28 ++ crypto/block-qcowaes.c | 165 +++++++ crypto/block-qcowaes.h | 28 ++ crypto/block.c | 256 ++++++++++ crypto/blockpriv.h | 89 ++++ 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 ++ docs/specs/qcow2.txt | 71 +++ hmp.c | 31 -- hw/usb/dev-storage.c | 34 -- include/block/block.h | 6 +- include/block/block_int.h | 1 - include/crypto/afsplit.h | 133 +++++ include/crypto/block.h | 222 +++++++++ include/crypto/ivgen.h | 167 +++++++ include/crypto/pbkdf.h | 152 ++++++ include/crypto/random.h | 43 ++ include/monitor/monitor.h | 7 - include/qemu/osdep.h | 2 - monitor.c | 69 --- qapi/block-core.json | 41 +- qapi/crypto.json | 117 +++++ qemu-img.c | 45 +- qemu-io.c | 21 - qmp.c | 9 - tests/.gitignore | 3 + tests/Makefile | 6 + tests/qemu-iotests/087 | 20 + tests/qemu-iotests/087.out | 24 +- tests/qemu-iotests/134 | 17 +- tests/qemu-iotests/134.out | 26 - tests/qemu-iotests/common.rc | 4 +- tests/test-crypto-afsplit.c | 176 +++++++ tests/test-crypto-ivgen.c | 168 +++++++ tests/test-crypto-pbkdf.c | 378 +++++++++++++++ util/oslib-posix.c | 66 --- util/oslib-win32.c | 24 - 60 files changed, 5471 insertions(+), 715 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 include/crypto/afsplit.h create mode 100644 include/crypto/block.h create mode 100644 include/crypto/ivgen.h create mode 100644 include/crypto/pbkdf.h create mode 100644 include/crypto/random.h create mode 100644 tests/test-crypto-afsplit.c create mode 100644 tests/test-crypto-ivgen.c create mode 100644 tests/test-crypto-pbkdf.c
Signature · 1 line
2.5.0
PATCH 1/15 crypto: add cryptographic random byte source
--- 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
crypto/Makefile.objs +1 -0
@@ -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
crypto/random.c new file +50 -0
50 lines not on this page · raw message
include/crypto/random.h new file +43 -0
43 lines not on this page · raw message
Signature · 1 line
2.5.0
Re: [Qemu-devel] [PATCH v1 01/15] crypto: add cryptographic random byte source
Quoted 27 lines · Daniel P. Berrange
Signed-off-by Daniel P. Berrange <[email protected]>--- 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.hcrypto/Makefile.objs +1
-0@@ -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.ocrypto/random.c new file +0
-0
crypto/random.c line 4
Quoted 4 lines
crypto/random.c new file +4
-0@@ -0,0 +1,50 @@ +/* +* QEMU Crypto random number provider +* +* Copyright (c) 2015 Red Hat, Inc.
s/2015/2016/
Quoted 52 lines
crypto/random.c new file +46
-0+* +* 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 ; +}include/crypto/random.h new file +0
-0
include/crypto/random.h line 4
Quoted 4 lines
include/crypto/random.h new file +4
-0@@ -0,0 +1,43 @@ +/* +* QEMU Crypto random number provider +* +* Copyright (c) 2015 Red Hat, Inc.
s/2015/2016/
Quoted 18 lines
include/crypto/random.h new file +18
-0+* +* 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/crypto/random.h line 32
Quoted 10 lines
include/crypto/random.h new file +10
-0+ +#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
s/objet/object/, but more importantly @errp must be NULL, so it's not exact to say it "uninitialized".
Quoted 15 lines
include/crypto/random.h new file +11
-0+* +* 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__ */ Signature · 1 line
2.5.0
PATCH 2/15 crypto: add support for PBKDF2 algorithm
In reply to Daniel P. BerrangeThe 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.
--- 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
crypto/Makefile.objs +1 -0
@@ -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
crypto/pbkdf-gcrypt.c new file +64 -0
64 lines not on this page · raw message
crypto/pbkdf-nettle.c new file +63 -0
63 lines not on this page · raw message
crypto/pbkdf-stub.c new file +39 -0
@@ -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 ; +}
crypto/pbkdf.c new file +76 -0
76 lines not on this page · raw message
include/crypto/pbkdf.h new file +152 -0
152 lines not on this page · raw message
tests/.gitignore +1 -0
@@ -14,6 +14,7 @@ test-blockjob-txn test-coroutine test-crypto-cipher test-crypto-hash +test-crypto-pbkdf test-crypto-secret test-crypto-tlscredsx509 test-crypto-tlscredsx509-work/
tests/Makefile +2 -0
@@ -92,6 +92,7 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF) check-unit-y += tests/test-io-channel-command$(EXESUF) check-unit-y += tests/test-io-channel-buffer$(EXESUF) check-unit-y += tests/test-base64$(EXESUF) +check-unit-y += tests/test-crypto-pbkdf$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -495,6 +496,7 @@ tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \ tests/io-channel-helpers.o $(test-io-obj-y) tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \ tests/io-channel-helpers.o $(test-io-obj-y) +tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y) libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
tests/test-crypto-pbkdf.c new file +378 -0
378 lines not on this page · raw message
Signature · 1 line
2.5.0
Re: [Qemu-devel] [PATCH v1 02/15] crypto: add support for PBKDF2 algorithm
crypto/pbkdf.c line 30
Quoted 7 lines · Daniel P. Berrange
crypto/pbkdf.c new file +7
-0+#if definedCONFIG_NETTLE +#include "crypto/pbkdf-nettle.c" +#elif definedCONFIG_GCRYPT +#include "crypto/pbkdf-gcrypt.c" +#else /* ! CONFIG_GCRYPT */ +#include "crypto/pbkdf-stub.c" +#endif /* ! CONFIG_GCRYPT */
I think the convention in QEMU is in crypto/Makefile.objs:
crypto-obj-$(CONFIG_NETTLE) += pbkdf-nettle.o
crypto-obj-$(if $(CONFIG_NETTLE),n,$(CONFIG_GCRYPT)) += pbkdf-gcrypt.oAnd move pbkdf-stub.c to stub/pbkdf.c.
Re: [Qemu-devel] [PATCH v1 02/15] crypto: add support for PBKDF2 algorithm
Quoted 11 lines · Fam Zheng
crypto/pbkdf.c line 30
Quoted 7 lines · Daniel P. Berrange
crypto/pbkdf.c new file +7
-0+#if definedCONFIG_NETTLE +#include "crypto/pbkdf-nettle.c" +#elif definedCONFIG_GCRYPT +#include "crypto/pbkdf-gcrypt.c" +#else /* ! CONFIG_GCRYPT */ +#include "crypto/pbkdf-stub.c" +#endif /* ! CONFIG_GCRYPT */ I think the convention in QEMU is in
crypto/Makefile.objs:
Quoted 2 lines
crypto-obj-$(CONFIG_NETTLE) += pbkdf-nettle.o
crypto-obj-$(if $(CONFIG_NETTLE),n,$(CONFIG_GCRYPT)) += pbkdf-gcrypt.oOk, that's fine.
Quoted 1 line
And move
pbkdf-stub.ctostub/pbkdf.c.
I'd rather key the source in the crypto/ directory since it matches up with MAINTAINER file rules. I can still add it to stub-obj-y in the makefile though, so that has same end effect on the build.
Regards,
Daniel
Signature · 4 lines
|: 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 :|
PATCH 3/15 crypto: add support for generating initialization vectors
In reply to Daniel P. BerrangeThere 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.
--- 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
crypto/Makefile.objs +4 -0
@@ -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
crypto/ivgen-essiv.c new file +116 -0
116 lines not on this page · raw message
crypto/ivgen-essiv.h new file +28 -0
@@ -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__ */
crypto/ivgen-plain.c new file +60 -0
60 lines not on this page · raw message
crypto/ivgen-plain.h new file +28 -0
@@ -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__ */
crypto/ivgen-plain64.c new file +60 -0
60 lines not on this page · raw message
crypto/ivgen-plain64.h new file +28 -0
@@ -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__ */
crypto/ivgen.c new file +117 -0
117 lines not on this page · raw message
crypto/ivgenpriv.h new file +47 -0
47 lines not on this page · raw message
include/crypto/ivgen.h new file +167 -0
167 lines not on this page · raw message
qapi/crypto.json +16 -0
@@ -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']}
tests/.gitignore +1 -0
@@ -14,6 +14,7 @@ test-blockjob-txn test-coroutine test-crypto-cipher test-crypto-hash +test-crypto-ivgen test-crypto-pbkdf test-crypto-secret test-crypto-tlscredsx509
tests/Makefile +2 -0
@@ -93,6 +93,7 @@ check-unit-y += tests/test-io-channel-command$(EXESUF) check-unit-y += tests/test-io-channel-buffer$(EXESUF) check-unit-y += tests/test-base64$(EXESUF) check-unit-y += tests/test-crypto-pbkdf$(EXESUF) +check-unit-y += tests/test-crypto-ivgen$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -497,6 +498,7 @@ tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \ tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \ tests/io-channel-helpers.o $(test-io-obj-y) tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y) +tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y) libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
tests/test-crypto-ivgen.c new file +168 -0
168 lines not on this page · raw message
Signature · 1 line
2.5.0
PATCH 4/15 crypto: add support for anti-forensic split algorithm
In reply to Daniel P. BerrangeThe 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.
--- 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
crypto/Makefile.objs +1 -0
@@ -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
crypto/afsplit.c new file +194 -0
194 lines not on this page · raw message
include/crypto/afsplit.h new file +133 -0
133 lines not on this page · raw message
tests/.gitignore +1 -0
@@ -12,6 +12,7 @@ test-base64 test-bitops test-blockjob-txn test-coroutine +test-crypto-afsplit test-crypto-cipher test-crypto-hash test-crypto-ivgen
tests/Makefile +2 -0
@@ -94,6 +94,7 @@ check-unit-y += tests/test-io-channel-buffer$(EXESUF) check-unit-y += tests/test-base64$(EXESUF) check-unit-y += tests/test-crypto-pbkdf$(EXESUF) check-unit-y += tests/test-crypto-ivgen$(EXESUF) +check-unit-y += tests/test-crypto-afsplit$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -499,6 +500,7 @@ tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \ tests/io-channel-helpers.o $(test-io-obj-y) tests/test-crypto-pbkdf$(EXESUF): tests/test-crypto-pbkdf.o $(test-crypto-obj-y) tests/test-crypto-ivgen$(EXESUF): tests/test-crypto-ivgen.o $(test-crypto-obj-y) +tests/test-crypto-afsplit$(EXESUF): tests/test-crypto-afsplit.o $(test-crypto-obj-y) libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
tests/test-crypto-afsplit.c new file +176 -0
176 lines not on this page · raw message
Signature · 1 line
2.5.0
PATCH 5/15 crypto: add block encryption framework
In reply to Daniel P. BerrangeAdd 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.
--- crypto/Makefile.objs | 2 + crypto/block-qcowaes.c | 165 ++++++++++++++++++++++++++++++++ crypto/block-qcowaes.h | 28 ++++++ crypto/block.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ crypto/blockpriv.h | 89 +++++++++++++++++ include/crypto/block.h | 222 ++++++++++++++++++++++++++++++++++++++++++ qapi/crypto.json | 65 +++++++++++++ 7 files changed, 825 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
crypto/Makefile.objs +2 -0
@@ -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
crypto/block-qcowaes.c new file +165 -0
165 lines not on this page · raw message
crypto/block-qcowaes.h new file +28 -0
@@ -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__ */
crypto/block.c new file +254 -0
254 lines not on this page · raw message
crypto/blockpriv.h new file +89 -0
89 lines not on this page · raw message
include/crypto/block.h new file +222 -0
222 lines not on this page · raw message
qapi/crypto.json +65 -0
68 lines not on this page · raw message
Signature · 1 line
2.5.0
Re: [Qemu-devel] [PATCH v1 05/15] crypto: add block encryption framework
Quoted 18 lines · Daniel P. Berrange
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 <[email protected]>---
+++
b/qapi/crypto.jsonqapi/crypto.json +0
-0@@ -94,3 +94,68 @@ { 'enum': 'QCryptoIVGenAlgorithm',
qapi/crypto.json line 104
Quoted 10 lines
qapi/crypto.json +8
-0'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 +#
Well, the only reason to use it would be to read data off an old insecurely-encrypted qcow2 file; so maybe it should read "Do not use on new files"
qapi/crypto.json line 109
Quoted 5 lines
qapi/crypto.json +5
-0+# Since: 2.6 +## +{ 'enum': 'QCryptoBlockFormat', +# 'prefix': 'QCRYPTO_BLOCK_FORMAT', + 'data': ['qcowaes']}
Would 'qcow-aes' be any easier to read?
qapi/crypto.json line 122
Quoted 13 lines
qapi/crypto.json +13
-0+ +## +# QCryptoBlockOptionsBase: +# +# The common options that apply to all full disk +# encryption formats +# +# @format: the encryption format +# +# Since: 2.6 +## +{ 'struct': 'QCryptoBlockOptionsBase', + 'data': { 'format': 'QCryptoBlockFormat' }}
My pending series to add anonymous flat union base types can simplify this a bit; I've added it to my list of cleanups that are awaiting merge of my code.
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
Re: [Qemu-devel] [PATCH v1 05/15] crypto: add block encryption framework
Quoted 25 lines · Eric Blake
Quoted 24 lines · Daniel P. Berrange
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 <[email protected]>---
+++
b/qapi/crypto.jsonqapi/crypto.json +4
-0@@ -94,3 +94,68 @@ { 'enum': 'QCryptoIVGenAlgorithm', 'prefix': 'QCRYPTO_IVGEN_ALG', 'data': ['plain', 'plain64', 'essiv']} + +## +# QCryptoBlockFormat: +#
Quoted 8 lines
qapi/crypto.json line 104
Quoted 4 lines
qapi/crypto.json +4
-0+# The supported full disk encryption formats +# +# @qcowaes: QCow/QCow2 built-in AES-CBC encryption. Do not use +#Well, the only reason to use it would be to read data off an old insecurely-encrypted qcow2 file; so maybe it should read "Do not use on new files"
Yep
Quoted 7 lines
qapi/crypto.json line 109
Quoted 5 lines
qapi/crypto.json +5
-0+# Since: 2.6 +## +{ 'enum': 'QCryptoBlockFormat', +# 'prefix': 'QCRYPTO_BLOCK_FORMAT', + 'data': ['qcowaes']}Would 'qcow-aes' be any easier to read?
Or just shorten to 'qcow' perhaps ?
Regards,
Daniel
Signature · 4 lines
|: 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 :|
Re: [Qemu-devel] [PATCH v1 05/15] crypto: add block encryption framework
Quoted 17 lines · Daniel P. Berrange
Quoted 6 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+# @qcowaes: QCow/QCow2 built-in AES-CBC encryption. Do not use +#Well, the only reason to use it would be to read data off an old insecurely-encrypted qcow2 file; so maybe it should read "Do not use on new files"
Yep
Quoted 7 lines
qapi/crypto.json line 109
Quoted 5 lines
qapi/crypto.json +5
-0+# Since: 2.6 +## +{ 'enum': 'QCryptoBlockFormat', +# 'prefix': 'QCRYPTO_BLOCK_FORMAT', + 'data': ['qcowaes']}Would 'qcow-aes' be any easier to read?
Quoted 1 line
Or just shorten to 'qcow' perhaps ?
Or maybe 'old-qcow' to emphasize that it is old? At this point, we're painting the bikeshed, so I'll live with whatever you like.
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
Re: [Qemu-devel] [PATCH v1 05/15] crypto: add block encryption framework
Quoted 19 lines · Eric Blake
Quoted 17 lines · Daniel P. Berrange
Quoted 6 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+# @qcowaes: QCow/QCow2 built-in AES-CBC encryption. Do not use +#Well, the only reason to use it would be to read data off an old insecurely-encrypted qcow2 file; so maybe it should read "Do not use on new files"
Yep
Quoted 7 lines
qapi/crypto.json line 109
Quoted 5 lines
qapi/crypto.json +5
-0+# Since: 2.6 +## +{ 'enum': 'QCryptoBlockFormat', +# 'prefix': 'QCRYPTO_BLOCK_FORMAT', + 'data': ['qcowaes']}Would 'qcow-aes' be any easier to read?
Quoted 4 lines
Quoted 1 line
Or just shorten to 'qcow' perhaps ?
Or maybe 'old-qcow' to emphasize that it is old? At this point, we're painting the bikeshed, so I'll live with whatever you like.
I've gone for just 'qcow', since that's also what libvirt calls it, and it avoids a need for further debate as to whether to hyphenate the two words or underscore or neither ;-P
Regards,
Daniel
Signature · 4 lines
|: 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 :|
PATCH 6/15 crypto: implement the LUKS block encryption format
In reply to Daniel P. BerrangeProvide 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.
--- crypto/Makefile.objs | 1 + crypto/block-luks.c | 1092 ++++++++++++++++++++++++++++++++++++++++++++++++++ crypto/block-luks.h | 28 ++ crypto/block.c | 2 + qapi/crypto.json | 42 +- 5 files changed, 1162 insertions(+), 3 deletions(-) create mode 100644 crypto/block-luks.c create mode 100644 crypto/block-luks.h
crypto/Makefile.objs +1 -0
@@ -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
crypto/block-luks.c new file +1092 -0
1,092 lines not on this page · raw message
crypto/block-luks.h new file +28 -0
@@ -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__ */
crypto/block.c +2 -0
@@ -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, };
qapi/crypto.json +39 -3
68 lines not on this page · raw message
Signature · 1 line
2.5.0
Re: [Qemu-devel] [PATCH v1 06/15] crypto: implement the LUKS block encryption format
Quoted 37 lines · Daniel P. Berrange
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 <[email protected]>---
+++
b/qapi/crypto.jsonqapi/crypto.json +2
-1@@ -101,12 +101,13 @@ # 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:
qapi/crypto.json line 142
Quoted 8 lines
qapi/crypto.json +5
-0@@ -134,6 +135,39 @@ 'data': { '*key-id': 'str' }} ## +# QCryptoBlockOptionsLUKS: +# +# The options that apply to LUKS encryption format +# +# @key-id: the ID of a QCryptoSecret object providing the decryption key
Is the key-id really optional? If so, missing the '#optional' tag.
qapi/crypto.json line 154
Quoted 12 lines
qapi/crypto.json +12
-0+# Since: 2.6 +## +{ 'struct': 'QCryptoBlockOptionsLUKS', + 'data': { '*key-id': 'str' }} + + +## +# QCryptoBlockCreateOptionsLUKS: +# +# The options that apply to LUKS encryption format initialization +# +# @cipher-alg: (optional) the cipher algorithm for data encryption
Marc-Andre's pending patches to auto-generate docs from the .json files want this to be s/(optional)/#optional/ (here and elsewhere).
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
Re: [Qemu-devel] [PATCH v1 06/15] crypto: implement the LUKS block encryption format
Quoted 40 lines · Eric Blake
Quoted 39 lines · Daniel P. Berrange
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 <[email protected]>---
+++
b/qapi/crypto.jsonqapi/crypto.json +2
-1@@ -101,12 +101,13 @@ # 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:@@ -134,6 +135,39 @@ 'data': { '*key-id': 'str' }}
Quoted 8 lines
qapi/crypto.json line 142
Quoted 6 lines
qapi/crypto.json +5
-0## +# QCryptoBlockOptionsLUKS: +# +# The options that apply to LUKS encryption format +# +# @key-id: the ID of a QCryptoSecret object providing the decryption keyIs the key-id really optional? If so, missing the '#optional' tag.
Yes & no. It is not optional if you actually want to open the disk and do I/O, but we want 'qemu-img info' to be able to report on data when using the BDRV_O_NO_IO flag, so we have to declare it optional to allow that to work. This is why we have an explicit check for key_id being non-NULL in the code at time of use.
Regards,
Daniel
Signature · 4 lines
|: 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 :|
PATCH 7/15 block: add flag to indicate that no I/O will be performed
In reply to Daniel P. BerrangeWhen opening an image it is useful to know whether the caller intends to perform I/O on the image or not. In the case of encrypted images this will allow the block driver to avoid having to prompt for decryption keys when we merely want to query header metadata about the image. eg qemu-img info
--- include/block/block.h | 1 + qemu-img.c | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 16 deletions(-)
include/block/block.h +1 -0
@@ -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)
qemu-img.c +17 -16
@@ -267,7 +267,7 @@ static BlockBackend *img_open_opts(const char *id,static BlockBackend *img_open_file (const char *id,const char *filename,const char *fmt,int flags, -bool require_io,bool quiet) +bool quiet) {BlockBackend *blk;BlockDriverState *bs;
@@ -289,7 +289,7 @@ static BlockBackend *img_open_file(const char *id, const char *filename, } bs =blk_bs (blk); -if (bdrv_is_encrypted (bs) && require_io) { +if (bdrv_is_encrypted (bs) && !(flags &BDRV_O_NO_IO )) {qprintf (quiet,"Disk image '%s' is encrypted.\n" , filename);if (qemu_read_password (password,sizeof (password)) <0 ) {error_report ("No password given" );
@@ -683,7 +683,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 ;
@@ -888,7 +888,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 ;
@@ -1250,13 +1250,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;
@@ -1936,7 +1936,7 @@ static int img_convert(int argc, char **argv) blk[bs_i] =img_open_opts (id, opts, src_flags); }else { blk[bs_i] =img_open_file (id, argv[optind + bs_i], fmt, src_flags, - true, quiet); + quiet); }g_free (id);if (!blk[bs_i]) {
@@ -2086,7 +2086,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;
@@ -2290,12 +2290,13 @@ static ImageInfoList *collect_image_info_list(bool image_opts, goto err; } blk =img_open_opts ("image" , opts, -BDRV_O_FLAGS |BDRV_O_NO_BACKING ); +BDRV_O_FLAGS |BDRV_O_NO_BACKING | +BDRV_O_NO_IO ); opts =NULL ; }else { blk =img_open_file ("image" , filename, fmt, -BDRV_O_FLAGS |BDRV_O_NO_BACKING , - false, false); +BDRV_O_FLAGS |BDRV_O_NO_BACKING | +BDRV_O_NO_IO , false); }if (!blk) { goto err;
@@ -2612,7 +2613,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 ;
@@ -2769,7 +2770,7 @@ static int img_snapshot(int argc, char **argv) } blk =img_open_opts ("image" , opts, bdrv_oflags); }else { - blk =img_open_file ("image" , filename,NULL , bdrv_oflags, true, quiet); + blk =img_open_file ("image" , filename,NULL , bdrv_oflags, quiet); }if (!blk) {return 1 ;
@@ -2955,7 +2956,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 ;
@@ -3309,7 +3310,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 ;
@@ -3480,7 +3481,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 ;
Signature · 1 line
2.5.0
Re: [Qemu-devel] [Qemu-block] [PATCH v1 07/15] block: add flag to indicate that no I/O will be performed
Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
Quoted 7 lines
When opening an image it is useful to know whether the caller intends to perform I/O on the image or not. In the case of encrypted images this will allow the block driver to avoid having to prompt for decryption keys when we merely want to query header metadata about the image. eg qemu-img info
Signed-off-by Daniel P. Berrange <[email protected]>
Would it be worth adding assertions to the block layer I/O functions (most importantly, bdrv_aligned_preadv/pwritev) that this flag isn't set?
Kevin
Re: [Qemu-devel] [Qemu-block] [PATCH v1 07/15] block: add flag to indicate that no I/O will be performed
Quoted 7 lines · Kevin Wolf
Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
Quoted 6 lines
When opening an image it is useful to know whether the caller intends to perform I/O on the image or not. In the case of encrypted images this will allow the block driver to avoid having to prompt for decryption keys when we merely want to query header metadata about the image. eg qemu-img info
Quoted 5 lines
Quoted 1 line
Signed-off-by Daniel P. Berrange <[email protected]>Would it be worth adding assertions to the block layer I/O functions (most importantly, bdrv_aligned_preadv/pwritev) that this flag isn't set?
Yes, I think so.
In the read/write code paths that trigger encryption, I'm already doing an assert that the encryption context is setup, which is indirectly related to BDRV_O_NO_IO. It is probably worth adding an assertion explicitly based on the flag at the top level though, as it'd give a clearer error if it ever got triggered.
Regards,
Daniel
Signature · 4 lines
|: 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 :|
PATCH 8/15 block: add generic full disk encryption driver
In reply to Daniel P. BerrangeAdd a block driver that is capable of supporting any full disk encryption format. This utilizes the previously added block encryption code, and at this time supports the LUKS format.
The driver code is capable of supporting any format supported by the QCryptoBlock module, so it registers one block driver for each format.
At this time, the "luks" driver is registered. New LUKS compatible volume can be formatted using qemu-img
$ qemu-img create --object secret,data=123456,id=sec0 \
-f luks -o key-id=sec0,cipher-alg=aes-256,\
cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \
demo.luks 10GAnd query its size
$ qemu-img info --object secret,data=123456,id=sec0 --source demo.luks,driver=luks,keyid=sec0
image: json:{"key-id": "sec0", "driver": "luks", "file": {"driver": "file", "filename": "demo.luks"}}
file format: luks
virtual size: 10.0G (10737416192 bytes)
disk size: 132KAll volumes created by this new 'luks' driver should be capable of being opened by the kernel dm-crypt driver. With this initial impl, not all volumes created with dm-crypt can be opened by the QEMU 'luks' driver. This is due to lack of support for certain algorithms, in particular the 'xts' cipher mode. These limitations will be addressed in a later series of patches, with the intent that QEMU should be able to open anything that dm-crypt LUKS supports.
--- block/Makefile.objs | 2 + block/fde.c | 538 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi/block-core.json | 18 +- 3 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 block/fde.c
block/Makefile.objs +2 -0
@@ -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
block/fde.c new file +538 -0
538 lines not on this page · raw message
qapi/block-core.json +17 -1
@@ -1546,7 +1546,7 @@ { 'enum': 'BlockdevDriver', 'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop', 'dmg', 'file', 'ftp', 'ftps', 'host_cdrom', 'host_device', - 'http', 'https', 'null-aio', 'null-co', 'parallels', + 'http', 'https', 'luks', 'null-aio', 'null-co', 'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'tftp', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
@@ -1664,6 +1664,21 @@ 'data': { 'file': 'BlockdevRef' } } ## +# @BlockdevOptionsLUKS +# +# Driver specific block device options for LUKS. +# +# @key-id: #optional the ID of a QCryptoSecret object providing +# the decryption key (since 2.6) +# +# Since: 2.6 +## +{ 'struct': 'BlockdevOptionsLUKS', + 'base': 'BlockdevOptionsGenericFormat', + 'data': { '*key-id': 'str' } } + + +## # @BlockdevOptionsGenericCOWFormat # # Driver specific block device options for image format that have no option
@@ -2000,6 +2015,7 @@ 'http': 'BlockdevOptionsFile', 'https': 'BlockdevOptionsFile', # TODO iscsi: Wait for structured options + 'luks': 'BlockdevOptionsLUKS', # TODO nbd: Should take InetSocketAddress for 'host'? # TODO nfs: Wait for structured options 'null-aio': 'BlockdevOptionsNull',
Signature · 1 line
2.5.0
Re: [Qemu-devel] [PATCH v1 08/15] block: add generic full disk encryption driver
Quoted 23 lines · Daniel P. Berrange
Add a block driver that is capable of supporting any full disk encryption format. This utilizes the previously added block encryption code, and at this time supports the LUKS format.
The driver code is capable of supporting any format supported by the QCryptoBlock module, so it registers one block driver for each format.
At this time, the "luks" driver is registered. New LUKS compatible volume can be formatted using qemu-img
$ qemu-img create --object secret,data=123456,id=sec0 \ -f luks -o key-id=sec0,cipher-alg=aes-256,\ cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \ demo.luks 10Gqapi/block-core.json +2
-0@@ -1664,6 +1664,21 @@ 'data': { 'file': 'BlockdevRef' } } ## +# @BlockdevOptionsLUKS +#
qapi/block-core.json line 1678
Quoted 10 lines
qapi/block-core.json +10
-0+# Driver specific block device options for LUKS. +# +# @key-id: #optional the ID of a QCryptoSecret object providing +# the decryption key (since 2.6) +# +# Since: 2.6 +## +{ 'struct': 'BlockdevOptionsLUKS', + 'base': 'BlockdevOptionsGenericFormat', + 'data': { '*key-id': 'str' } }
And if key-id is omitted, how does it work?
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
Re: [Qemu-devel] [PATCH v1 08/15] block: add generic full disk encryption driver
Quoted 21 lines · Eric Blake
Quoted 20 lines · Daniel P. Berrange
Add a block driver that is capable of supporting any full disk encryption format. This utilizes the previously added block encryption code, and at this time supports the LUKS format.
The driver code is capable of supporting any format supported by the QCryptoBlock module, so it registers one block driver for each format.
At this time, the "luks" driver is registered. New LUKS compatible volume can be formatted using qemu-img
$ qemu-img create --object secret,data=123456,id=sec0 \ -f luks -o key-id=sec0,cipher-alg=aes-256,\ cipher-mode=cbc,ivgen-alg=plain64,hash-alg=sha256 \ demo.luks 10Gqapi/block-core.json +0
-0@@ -1664,6 +1664,21 @@ 'data': { 'file': 'BlockdevRef' } }
Quoted 15 lines
qapi/block-core.json line 1678
Quoted 13 lines
qapi/block-core.json +12
-0## +# @BlockdevOptionsLUKS +# +# Driver specific block device options for LUKS. +# +# @key-id: #optional the ID of a QCryptoSecret object providing +# the decryption key (since 2.6) +# +# Since: 2.6 +## +{ 'struct': 'BlockdevOptionsLUKS', + 'base': 'BlockdevOptionsGenericFormat', + 'data': { '*key-id': 'str' } }And if key-id is omitted, how does it work?
You can only omit it if using BDRV_O_NO_IO, which is used by qemu-img info to report info about the disk without needing to decrypt any payload.
Regards,
Daniel
Signature · 4 lines
|: 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 :|
PATCH 9/15 qcow2: make qcow2_encrypt_sectors encrypt in place
In reply to Daniel P. BerrangeInstead 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.
--- block/qcow2-cluster.c | 17 +++++------------ block/qcow2.c | 5 ++--- block/qcow2.h | 3 +-- 3 files changed, 8 insertions(+), 17 deletions(-)
block/qcow2-cluster.c +5 -12
@@ -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);
block/qcow2.c +2 -3
@@ -1504,7 +1504,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,assert (s->cipher);Error *err =NULL ;if (qcow2_encrypt_sectors (s, sector_num, cluster_data, - cluster_data, cur_nr_sectors, false, + cur_nr_sectors, false, &err) <0 ) {error_free (err); ret = -EIO ;
@@ -1604,8 +1604,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,qemu_iovec_to_buf (&hd_qiov,0 , cluster_data, hd_qiov.size);if (qcow2_encrypt_sectors (s, sector_num, cluster_data, - cluster_data, cur_nr_sectors, - true, &err) <0 ) { + cur_nr_sectors, true, &err) <0 ) {error_free (err); ret = -EIO ; goto fail;
block/qcow2.h +1 -2
@@ -540,8 +540,7 @@ int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);void qcow2_l2_cache_reset (BlockDriverState *bs);int qcow2_decompress_cluster (BlockDriverState *bs,uint64_t cluster_offset);int qcow2_encrypt_sectors (BDRVQcow2State *s,int64_t sector_num, -uint8_t *out_buf,const uint8_t *in_buf, -int nb_sectors,bool enc,Error **errp); +uint8_t *buf,int nb_sectors,bool enc,Error **errp);int qcow2_get_cluster_offset (BlockDriverState *bs,uint64_t offset,int *num,uint64_t *cluster_offset);
Signature · 1 line
2.5.0
PATCH 10/15 qcow2: convert QCow2 to use QCryptoBlock for encryption
In reply to Daniel P. BerrangeThis converts the qcow2 driver to make use of the QCryptoBlock APIs for encrypting image content. As well as continued support for the legacy QCow2 encryption format, the appealing benefit is that it enables support for the LUKS format inside qcow2.
With the LUKS format it is neccessary to store the LUKS partition header and key material in the QCow2 file. This data can be many MB in size, so cannot go into the QCow2 header region directly. Thus the spec is defines a FDE (Full Disk Encryption) header extension that specifies the offset of a set of clusters to hold the FDE headers, as well as the length of that region. The LUKS header is thus stored in these extra allocated clusters before the main image payload.
With this change it is now required to use the QCryptoSecret object for providing passwords, instead of the current block password APIs / interactive prompting.
$QEMU \
-object secret,id=sec0,filename=/home/berrange/encrypted.pw \
-drive file=/home/berrange/encrypted.qcow2,key-id=sec0The new LUKS format is set as the new default format when creating encrypted images. ie
# qemu-img create --object secret,data=123456,id=sec0 \
-f qcow2 -o encryption,key-id=sec0 \
test.qcow2 10GResults in creation of an image using the LUKS format.
For compatibility the old qcow2 AES format can still be used via the 'encryption-format' parameter which accepts the values 'luks' or 'qcowaes'.
# qemu-img create --object secret,data=123456,id=sec0 \
-f qcow2 -o encryption,key-id=sec0,encryption-format=qcowaes \
test.qcow2 10G--- block/qcow2-cluster.c | 46 +---- block/qcow2.c | 502 +++++++++++++++++++++++++++++++++++++++++++------- block/qcow2.h | 21 ++- docs/specs/qcow2.txt | 71 +++++++ qapi/block-core.json | 6 +- 5 files changed, 529 insertions(+), 117 deletions(-)
block/qcow2-cluster.c +4 -42
@@ -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;
block/qcow2.c +431 -71
723 lines not on this page · raw message
block/qcow2.h +19 -2
@@ -25,7 +25,7 @@#ifndef BLOCK_QCOW2_H #define BLOCK_QCOW2_H -#include "crypto/cipher.h" +#include "crypto/block.h" #include "qemu/coroutine.h" //#define DEBUG_ALLOC
@@ -36,6 +36,7 @@#define QCOW_CRYPT_NONE 0#define QCOW_CRYPT_AES 1 +#define QCOW_CRYPT_LUKS 2#define QCOW_MAX_CRYPT_CLUSTERS 32#define QCOW_MAX_SNAPSHOTS 65536
@@ -98,6 +99,15 @@#define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"#define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval" +#define QCOW2_OPT_ENC_FORMAT "encryption-format" +#define QCOW2_OPT_KEY_ID "key-id" +#define QCOW2_OPT_CIPHER_ALG "cipher-alg" +#define QCOW2_OPT_CIPHER_MODE "cipher-mode" +#define QCOW2_OPT_IVGEN_ALG "ivgen-alg" +#define QCOW2_OPT_IVGEN_HASH_ALG "ivgen-hash-alg" +#define QCOW2_OPT_HASH_ALG "hash-alg" + + typedef struct QCowHeader { uint32_t magic; uint32_t version;
@@ -163,6 +173,11 @@ typedef struct QCowSnapshot {struct Qcow2Cache ;typedef struct Qcow2Cache Qcow2Cache ; +typedef struct Qcow2FDEHeaderExtension { +uint64_t offset; +uint64_t length; +}Qcow2FDEHeaderExtension ; + typedef struct Qcow2UnknownHeaderExtension { uint32_t magic; uint32_t len;
@@ -256,7 +271,9 @@ typedef struct BDRVQcow2State {CoMutex lock; -QCryptoCipher *cipher;/* current cipher, NULL if no key yet */ +Qcow2FDEHeaderExtension fde_header;/* QCow2 header extension */ +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;
docs/specs/qcow2.txt +71 -0
89 lines not on this page · raw message
qapi/block-core.json +4 -2
@@ -1789,6 +1789,8 @@ # @cache-clean-interval: #optional clean unused entries in the L2 and refcount # caches. The interval is in seconds. The default value # is 0 and it disables this feature (since 2.5) +# @key-id: #optional the ID of a QCryptoSecret object providing +# the decryption key (since 2.6) # # Since: 1.7 ##
@@ -1802,8 +1804,8 @@ '*cache-size': 'int', '*l2-cache-size': 'int', '*refcount-cache-size': 'int', - '*cache-clean-interval': 'int' } } - + '*cache-clean-interval': 'int', + '*key-id': 'str' } } ## # @BlockdevOptionsArchipelago
Signature · 1 line
2.5.0
Re: [Qemu-devel] [Qemu-block] [PATCH v1 10/15] qcow2: convert QCow2 to use QCryptoBlock for encryption
Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
Quoted 36 lines
This converts the qcow2 driver to make use of the QCryptoBlock APIs for encrypting image content. As well as continued support for the legacy QCow2 encryption format, the appealing benefit is that it enables support for the LUKS format inside qcow2.
With the LUKS format it is neccessary to store the LUKS partition header and key material in the QCow2 file. This data can be many MB in size, so cannot go into the QCow2 header region directly. Thus the spec is defines a FDE (Full Disk Encryption) header extension that specifies the offset of a set of clusters to hold the FDE headers, as well as the length of that region. The LUKS header is thus stored in these extra allocated clusters before the main image payload.
With this change it is now required to use the QCryptoSecret object for providing passwords, instead of the current block password APIs / interactive prompting.
$QEMU \ -object secret,id=sec0,filename=/home/berrange/encrypted.pw \ -drive file=/home/berrange/encrypted.qcow2,key-id=sec0The new LUKS format is set as the new default format when creating encrypted images. ie
# qemu-img create --object secret,data=123456,id=sec0 \ -f qcow2 -o encryption,key-id=sec0 \ test.qcow2 10GResults in creation of an image using the LUKS format.
For compatibility the old qcow2 AES format can still be used via the 'encryption-format' parameter which accepts the values 'luks' or 'qcowaes'.
Quoted 5 lines
# qemu-img create --object secret,data=123456,id=sec0 \ -f qcow2 -o encryption,key-id=sec0,encryption-format=qcowaes \ test.qcow2 10GSigned-off-by Daniel P. Berrange <[email protected]>
I think for your additional pointer to some clusters you need to change some function(s) called by qcow2_check_refcounts(). Otherwise 'qemu-img check' won't count the reference and helpfully free the "leaked" cluster.
Quoted 14 lines
block/qcow2.c +2
-0@@ -34,6 +34,8 @@#include "qapi-event.h" #include "trace.h" #include "qemu/option_int.h" +#include "qapi/opts-visitor.h" +#include "qapi-visit.h" /* Differences with QCOW:
block/qcow2.c typedef struct { · line 65
Quoted 4 lines
block/qcow2.c +1
-0@@ -60,6 +62,7 @@ typedef struct {#define QCOW2_EXT_MAGIC_END 0#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857 +#define QCOW2_EXT_MAGIC_FDE_HEADER 0x4c554b53
General naming comment on this series: I would prefer avoiding "FDE" in favour of "encryption" or "crypt" in the block layer parts. With all image formats having their own terminology, "FDE" could mean anything.
Quoted 10 lines
block/qcow2.c +4
-0static int qcow2_probe (const uint8_t *buf,int buf_size,const char *filename) {@@ -74,6 +77,83 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) } +static ssize_t qcow2_fde_header_read_func(QCryptoBlock *block, + size_t offset, + uint8_t *buf, + size_t buflen,
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 93
Quoted 10 lines
block/qcow2.c +10
-0+ Error **errp, + void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque; +ssize_t ret; + +if ((offset + buflen) > s->fde_header.length) { +error_setg_errno (errp,EINVAL , +"Request for data outside of extension header" );
error_setg_errno() with a constant errno doesn't look very useful. Better use plain error_setg() in such cases.
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 94
Quoted 1 line
block/qcow2.c +1
-0+return -1 ;
Here returning -EINVAL could be useful, I'm not sure what your crypto API requires. At least you seem to be returning -errno below and mixing -1 and -errno is probably a bad idea.
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 103
Quoted 9 lines
block/qcow2.c +9
-0+ } + + ret =bdrv_pread (bs->file->bs, + s->fde_header.offset + offset, buf, buflen); +if (ret <0 ) { +error_setg_errno (errp, -ret,"Could not read encryption header" ); +return ret; + } +return ret;
return 0? You already processed ret in the if block and two 'return ret' in a row look odd.
Quoted 9 lines
block/qcow2.c +9
-0+} + + +static ssize_t qcow2_fde_header_init_func (QCryptoBlock *block, +size_t headerlen, +Error **errp, +void *opaque) +{ +BlockDriverState *bs = opaque;
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 122
Quoted 10 lines
block/qcow2.c +10
-0+BDRVQcow2State *s = bs->opaque; +int64_t ret; + + s->fde_header.length = headerlen + (headerlen % s->cluster_size); + + ret =qcow2_alloc_clusters (bs, s->fde_header.length); +if (ret <0 ) { + s->fde_header.length =0 ; +error_setg (errp,"Cannot allocate cluster for LUKS header size %zu" , + headerlen);
I think ret is -errno on failure, so use error_setg_errno()?
Quoted 12 lines
block/qcow2.c +12
-0+return -1 ; + } + + s->fde_header.offset = ret; +return 0 ; +} + + +static ssize_t qcow2_fde_header_write_func(QCryptoBlock *block, + size_t offset, + const uint8_t *buf, + size_t buflen,
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 144
Quoted 10 lines
block/qcow2.c +10
-0+ Error **errp, + void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque; +ssize_t ret; + +if ((offset + buflen) > s->fde_header.length) { +error_setg_errno (errp,EINVAL , +"Request for data outside of extension header" );
error_setg(). Probably worth checking all error paths whether there is a useful errno or not. I won't comment on additional instances.
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 154
Quoted 10 lines
block/qcow2.c +10
-0+return -1 ; + } + + ret =bdrv_pwrite (bs->file->bs, s->fde_header.offset + offset, buf, buflen); +if (ret <0 ) { +error_setg_errno (errp, -ret,"Could not read encryption header" ); +return ret; + } +return ret; +}
Mixing -1 and -errno again.
Quoted 5 lines
block/qcow2.c +0
-0/* * read qcow2 extension and fill bs * start reading from start_offset @@ -83,12 +163,14 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)*/
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 173
Quoted 10 lines
block/qcow2.c +2
-0static int qcow2_read_extensions (BlockDriverState *bs,uint64_t start_offset,uint64_t end_offset,void **p_feature_table, +int flags,Error **errp) {BDRVQcow2State *s = bs->opaque;QCowExtension ext;uint64_t offset;int ret; +unsigned int cflags =0 ;
Should we create a block for QCOW2_EXT_MAGIC_FDE_HEADER and declare it there?
Quoted 10 lines
block/qcow2.c +4
-0#ifdef DEBUG_EXT printf ("qcow2_read_extensions: start=%ld end=%ld\n" , start_offset, end_offset);@@ -159,6 +241,35 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, }break ; +case QCOW2_EXT_MAGIC_FDE_HEADER : +if (s->crypt_method_header !=QCOW_CRYPT_LUKS ) { +error_setg (errp,"FDE header extension only " +"expected with LUKS encryption method" );
block/qcow2.c static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, · line 257
Quoted 10 lines
block/qcow2.c +10
-0+return -EINVAL ; + } +if (ext.len !=sizeof (Qcow2FDEHeaderExtension)) { +error_setg (errp,"LUKS header extension size %u, " +"but expected size %zu" , ext.len, +sizeof (Qcow2FDEHeaderExtension)); +return -EINVAL ; + } + + ret =bdrv_pread (bs->file->bs, offset, &s->fde_header, ext.len);
No error check?
block/qcow2.c static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, · line 268
Quoted 11 lines
block/qcow2.c +11
-0+be64_to_cpu (s->fde_header.offset); +be64_to_cpu (s->fde_header.length); + +if (flags &BDRV_O_NO_IO ) { + cflags |=QCRYPTO_BLOCK_OPEN_NO_IO ; + } + s->fde =qcrypto_block_open (s->fde_opts, + qcow2_fde_header_read_func, + bs, + cflags, + errp);
The surrounding code doesn't put every parameter on its own line if the next parameter can fit on the same line. There are more instances of this in the patch, I won't comment on each one.
Quoted 227 lines
block/qcow2.c +150
-13+if (!s->fde) { +return -EINVAL ; + } +break ;default :/* unknown magic - save it in case we need to rewrite the header */ {@@ -472,6 +583,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 +705,113 @@ 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 , *end_err =NULL ; +Visitor *v; + + ret =g_new0 (QCryptoBlockCreateOptions,1 ); + ret->format = format; + + ov =opts_visitor_new (opts); + v =opts_get_visitor (ov); +visit_start_struct (v,NULL ,NULL ,NULL ,0 , &local_err); +if (local_err) { + goto cleanup; + } + +switch (format) { +case Q_CRYPTO_BLOCK_FORMAT_QCOWAES : + ret->u.qcowaes =g_new0 (QCryptoBlockOptionsQCowAES,1 ); +visit_type_QCryptoBlockOptionsQCowAES (v, &ret->u.qcowaes, +"qcowaes" , &local_err); +break ; + +case Q_CRYPTO_BLOCK_FORMAT_LUKS : + ret->u.luks =g_new0 (QCryptoBlockCreateOptionsLUKS,1 ); +visit_type_QCryptoBlockCreateOptionsLUKS (v, &ret->u.luks, +"luks" , &local_err); +break ; + +default : +error_setg (&local_err,"Unsupported block format %d" , format); +break ; + } + +visit_end_struct (v, &end_err); +if (end_err) { +if (!local_err) { +error_propagate (&local_err, end_err); + }else { +error_free (end_err); + } + } + + cleanup: +if (local_err) { +error_propagate (errp, local_err); +qapi_free_QCryptoBlockCreateOptions (ret); + ret =NULL ; +return NULL ; + } + +opts_visitor_cleanup (ov); +return ret; +} + + typedef struct Qcow2ReopenState { Qcow2Cache *l2_table_cache; Qcow2Cache *refcount_block_cache;@@ -596,6 +819,7 @@ typedef struct Qcow2ReopenState {int overlap_check;bool discard_passthrough[QCOW2_DISCARD_MAX ];uint64_t cache_clean_interval; +QCryptoBlockOpenOptions *fde_opts;/* Disk encryption runtime options */ } Qcow2ReopenState; static int qcow2_update_options_prepare(BlockDriverState *bs,@@ -754,6 +978,33 @@ static int qcow2_update_options_prepare(BlockDriverState *bs, r->discard_passthrough[QCOW2_DISCARD_OTHER ] =qemu_opt_get_bool (opts,QCOW2_OPT_DISCARD_OTHER , false); +switch (s->crypt_method_header) { +case QCOW_CRYPT_NONE : +break ; + +case QCOW_CRYPT_AES : + r->fde_opts =qcow2_fde_open_opts_init ( +Q_CRYPTO_BLOCK_FORMAT_QCOWAES , + opts, + errp); +break ; + +case QCOW_CRYPT_LUKS : + r->fde_opts =qcow2_fde_open_opts_init ( +Q_CRYPTO_BLOCK_FORMAT_LUKS , + opts, + errp); +break ; + +default : +g_assert_not_reached (); + } +if (s->crypt_method_header && + !r->fde_opts) { + ret = -EINVAL ; + goto fail; + } + ret =0 ; fail:qemu_opts_del (opts);@@ -788,6 +1039,9 @@ static void qcow2_update_options_commit(BlockDriverState *bs, s->cache_clean_interval = r->cache_clean_interval;cache_clean_timer_init (bs,bdrv_get_aio_context (bs)); } + +qapi_free_QCryptoBlockOpenOptions (s->fde_opts); + s->fde_opts = r->fde_opts; } static void qcow2_update_options_abort(BlockDriverState *bs,@@ -799,6 +1053,7 @@ static void qcow2_update_options_abort(BlockDriverState *bs,if (r->refcount_block_cache) {qcow2_cache_destroy (bs, r->refcount_block_cache); } +qapi_free_QCryptoBlockOpenOptions (r->fde_opts); } static int qcow2_update_options(BlockDriverState *bs, QDict *options,@@ -932,7 +1187,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,if (s->incompatible_features & ~QCOW2_INCOMPAT_MASK ) {void *feature_table =NULL ;qcow2_read_extensions (bs, header.header_length, ext_end, - &feature_table,NULL ); + &feature_table, flags,NULL );report_unsupported_feature (bs, errp, feature_table, s->incompatible_features & ~QCOW2_INCOMPAT_MASK );@@ -964,17 +1219,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, s->refcount_max =UINT64_C (1 ) << (s->refcount_bits -1 ); s->refcount_max += s->refcount_max -1 ; -if (header.crypt_method >QCOW_CRYPT_AES ) { -error_setg (errp,"Unsupported encryption method: %" PRIu32, - header.crypt_method); - ret = -EINVAL ; - goto fail; - } -if (!qcrypto_cipher_supports (QCRYPTO_CIPHER_ALG_AES_128 )) { -error_setg (errp,"AES cipher not available" ); - ret = -EINVAL ; - goto fail; - } s->crypt_method_header = header.crypt_method;if (s->crypt_method_header) { bs->encrypted =1 ;@@ -1104,12 +1348,40 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,/* read qcow2 extensions */ if (qcow2_read_extensions (bs, header.header_length, ext_end,NULL , - &local_err)) { + flags, &local_err)) {error_propagate (errp, local_err);
block/qcow2.c static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, · line 1362
Quoted 10 lines
block/qcow2.c +6
-0ret = -EINVAL ; goto fail; } +/* qcow2_read_extension may have setup FDE context if +* the crypt method needs a header region, some methods +* don't need header extensions, so must check here +*/ +if (s->crypt_method_header && + !s->fde) {
Unnecessary line break.
Quoted 264 lines
block/qcow2.c +106
-52+if (s->crypt_method_header ==QCOW_CRYPT_AES ) { +unsigned int cflags =0 ; +if (flags &BDRV_O_NO_IO ) { + cflags |=QCRYPTO_BLOCK_OPEN_NO_IO ; + } + s->fde =qcrypto_block_open (s->fde_opts, +NULL ,NULL , + cflags, + errp); +if (!s->fde) { +error_setg (errp,"Could not setup encryption layer" ); + ret = -EINVAL ; + goto fail; + } + }else if (!(flags &BDRV_O_NO_IO )) { +error_setg (errp,"Missing FDE header for crypt method %d" , + s->crypt_method_header); + ret = -EINVAL ; + goto fail; + } + } +/* read the backing file name */ if (header.backing_file_offset !=0 ) { len = header.backing_file_size;@@ -1199,41 +1471,6 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp) bs->bl.write_zeroes_alignment = s->cluster_sectors; } -static int qcow2_set_key (BlockDriverState *bs,const char *key) -{ -BDRVQcow2State *s = bs->opaque; -uint8_t keybuf[16 ]; -int len, i; -Error *err =NULL ; - -memset (keybuf,0 ,16 ); - len =strlen (key); -if (len >16 ) - len =16 ; -/* XXX: we could compress the chars to 7 bits to increase -entropy */ -for (i =0 ;i < len;i++) { - keybuf[i] = key[i]; - } -assert (bs->encrypted); - -qcrypto_cipher_free (s->cipher); - s->cipher =qcrypto_cipher_new ( -QCRYPTO_CIPHER_ALG_AES_128 , -QCRYPTO_CIPHER_MODE_CBC , - keybuf,G_N_ELEMENTS (keybuf), - &err); - -if (!s->cipher) { -/* XXX would be nice if errors in this method could -* be properly propagate to the caller. Would need -* the bdrv_set_key() API signature to be fixed. */ -error_free (err); -return -1 ; - } -return 0 ; -} -static int qcow2_reopen_prepare (BDRVReopenState *state,BlockReopenQueue *queue,Error **errp) {@@ -1345,7 +1582,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;@@ -1395,7 +1632,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); }@@ -1467,7 +1704,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 @@ -1501,10 +1738,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 ;@@ -1588,7 +1827,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@@ -1603,8 +1842,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;@@ -1715,8 +1957,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);@@ -1734,7 +1976,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;@@ -1744,8 +1986,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);@@ -1770,7 +2012,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,@@ -1893,6 +2135,23 @@ int qcow2_update_header(BlockDriverState *bs) buflen -= ret; } +/* Full disk encryption header pointer extension */ +if (s->fde_header.offset !=0 ) { +cpu_to_be64 (s->fde_header.offset); +cpu_to_be64 (s->fde_header.length); + ret =header_ext_add (buf,QCOW2_EXT_MAGIC_FDE_HEADER , + &s->fde_header, +sizeof (s->fde_header), + buflen); +be64_to_cpu (s->fde_header.offset); +be64_to_cpu (s->fde_header.length); +if (ret <0 ) { + goto fail; + } + buf += ret; + buflen -= ret; + } +/* Feature table */ Qcow2Feature features[] = { {@@ -2056,6 +2315,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);@@ -2179,7 +2442,48 @@ static int qcow2_create2(const char *filename, int64_t total_size, };if (flags &BLOCK_FLAG_ENCRYPT ) { - header->crypt_method =cpu_to_be32 (QCOW_CRYPT_AES ); +/* Default to LUKS if fde-format is not set */ + fdestr =qemu_opt_get_del (opts,QCOW2_OPT_ENC_FORMAT ); +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) { + ret = -EINVAL ; + goto out; + } +break ; + } + } +if (!fdeopts) { +error_setg (errp,"Unknown fde format %s" , fdestr); + ret = -EINVAL ; + goto out; + } + }else { + fdeopts =qcow2_fde_create_opts_init ( +Q_CRYPTO_BLOCK_FORMAT_LUKS , + opts, errp); +if (!fdeopts) { + ret = -EINVAL ; + 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); + ret = -EINVAL ; + goto out; + } }else { header->crypt_method =cpu_to_be32 (QCOW_CRYPT_NONE ); }@@ -2213,12 +2517,15 @@ static int qcow2_create2(const char *filename, int64_t total_size,/* * And now open the image and make it consistent first (i.e. increase the * refcount of the cluster that is occupied by the header and the refcount -* table) +* table). Using BDRV_O_NO_IO since we've not written any encryption
block/qcow2.c static int qcow2_create2(const char *filename, int64_t total_size, · line 2529
Quoted 10 lines
block/qcow2.c +4
-1+* header yet and thus should not read/write payload data or initialize +* the decryption context */ 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);
Somehow I feel that saying BDRV_O_NO_IO, but doing I/O will bite us sooner or later.
Why not leave header->crypt_method = 0 initially and only set up encryption after opening the image with the qcow2 driver?
block/qcow2.c static int qcow2_create2(const char *filename, int64_t total_size, · line 2563
Quoted 7 lines
block/qcow2.c +1
-0if (ret <0 ) {error_propagate (errp, local_err);@@ -2253,6 +2560,24 @@ static int qcow2_create2(const char *filename, int64_t total_size, } } +/* Want encryption ? There you go.*/
Move that space character from before '?' to after '.' and it will look right. ;-)
Quoted 132 lines
block/qcow2.c +57
-5+if (fdeopts) { + fde =qcrypto_block_create (fdeopts, + qcow2_fde_header_init_func, + qcow2_fde_header_write_func, + bs, + errp); +if (!fde) { + ret = -EINVAL ; + goto out; + } + ret =qcow2_update_header (bs); +if (ret <0 ) { +error_setg_errno (errp, -ret,"Could not write encryption header" ); + goto out; + } + } +/* And if we're supposed to preallocate metadata, do that now */ if (prealloc !=PREALLOC_MODE_OFF ) {BDRVQcow2State *s = bs->opaque;@@ -2272,7 +2597,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);@@ -3047,10 +3373,10 @@ 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) { -error_report ("Changing the encryption flag is not supported" ); +if (encrypt != !!s->fde) { +fprintf (stderr,"Changing the encryption flag is not supported" );return -ENOTSUP ; } }else if (!strcmp (desc->name,BLOCK_OPT_CLUSTER_SIZE )) {@@ -3285,6 +3611,41 @@ static QemuOptsList qcow2_create_opts = { .help = "Width of a reference count entry in bits", .def_value_str = "16" }, + { + .name = QCOW2_OPT_ENC_FORMAT, + .type = QEMU_OPT_STRING, + .help = "Disk encryption format, 'luks' (default) or 'qcowaes' (deprecated)", + }, + { + .name = QCOW2_OPT_KEY_ID, + .type = QEMU_OPT_STRING, + .help = "ID of the secret that provides the encryption key", + }, + { + .name = QCOW2_OPT_CIPHER_ALG, + .type = QEMU_OPT_STRING, + .help = "Name of encryption cipher algorithm", + }, + { + .name = QCOW2_OPT_CIPHER_MODE, + .type = QEMU_OPT_STRING, + .help = "Name of encryption cipher mode", + }, + { + .name = QCOW2_OPT_IVGEN_ALG, + .type = QEMU_OPT_STRING, + .help = "Name of IV generator algorithm", + }, + { + .name = QCOW2_OPT_IVGEN_HASH_ALG, + .type = QEMU_OPT_STRING, + .help = "Name of IV generator hash algorithm", + }, + { + .name = QCOW2_OPT_HASH_ALG, + .type = QEMU_OPT_STRING, + .help = "Name of encryption hash algorithm", + }, { /* end of list */ } } };@@ -3302,7 +3663,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,block/qcow2.h +11
-1@@ -25,7 +25,7 @@#ifndef BLOCK_QCOW2_H #define BLOCK_QCOW2_H -#include "crypto/cipher.h" +#include "crypto/block.h" #include "qemu/coroutine.h" //#define DEBUG_ALLOC @@ -36,6 +36,7 @@#define QCOW_CRYPT_NONE 0#define QCOW_CRYPT_AES 1 +#define QCOW_CRYPT_LUKS 2#define QCOW_MAX_CRYPT_CLUSTERS 32#define QCOW_MAX_SNAPSHOTS 65536@@ -98,6 +99,15 @@#define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"#define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval" +#define QCOW2_OPT_ENC_FORMAT "encryption-format" +#define QCOW2_OPT_KEY_ID "key-id" +#define QCOW2_OPT_CIPHER_ALG "cipher-alg" +#define QCOW2_OPT_CIPHER_MODE "cipher-mode" +#define QCOW2_OPT_IVGEN_ALG "ivgen-alg" +#define QCOW2_OPT_IVGEN_HASH_ALG "ivgen-hash-alg" +#define QCOW2_OPT_HASH_ALG "hash-alg" + + typedef struct QCowHeader { uint32_t magic; uint32_t version;
block/qcow2.h typedef struct QCowSnapshot { · line 179
Quoted 7 lines
block/qcow2.h +4
-0@@ -163,6 +173,11 @@ typedef struct QCowSnapshot {struct Qcow2Cache ;typedef struct Qcow2Cache Qcow2Cache ; +typedef struct Qcow2FDEHeaderExtension { +uint64_t offset; +uint64_t length; +}Qcow2FDEHeaderExtension ;
Packed? It seems to be read directly from the file.
Quoted 35 lines
block/qcow2.h +3
-1typedef struct Qcow2UnknownHeaderExtension { uint32_t magic; uint32_t len;@@ -256,7 +271,9 @@ typedef struct BDRVQcow2State {CoMutex lock; -QCryptoCipher *cipher;/* current cipher, NULL if no key yet */ +Qcow2FDEHeaderExtension fde_header;/* QCow2 header extension */ +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;docs/specs/qcow2.txt +2
-0@@ -45,6 +45,7 @@ The first cluster of a qcow2 image contains the file header: 32 - 35: crypt_method 0 for no encryption 1 for AES encryption + 2 for LUKS encryption 36 - 39: l1_size Number of entries in the active L1 table@@ -123,6 +124,7 @@ be stored. Each extension has a structure like the following: 0x00000000 - End of the header extension area 0xE2792ACA - Backing file format name 0x6803f857 - Feature name table + 0x4c554b53 - Full disk encryption header pointer other - Unknown header extension, can be safely ignored
docs/specs/qcow2.txt the header extension data. Each entry look like this: · line 175
Quoted 8 lines
docs/specs/qcow2.txt +5
-0@@ -166,6 +168,75 @@ the header extension data. Each entry look like this: terminated if it has full length) +== Full disk encryption (FDE) header pointer == + +For 'crypt_method' header values which require additional header metadata +to be stored (eg 'LUKS' header), the full disk encryption header pointer +extension is mandatory.
I think you want to make that "is present if, and only if, the 'crypt_method' header value requires metadata".
At least, we need to forbid it for the existing AES images, because old qemu-img version would stll fix the "leaked clusters", without removing the header extension that refers to them.
Quoted 96 lines
It provides the offset at which the crypt method
docs/specs/qcow2.txt +64
-0+can store its additional data, as well as the length of such data. + + Byte 0 - 7: Offset into the image file at which the encryption + header starts. Must be aligned to a cluster boundary. + Byte 8 - 16: Length of the encryption header. Must be a multiple + of the cluster size. + +While the header extension allocates the length as a multiple of the +cluster size, the encryption header may not consume the full permitted +allocation. + +For the LUKS crypt method, the encryption header works as follows. + +The first 592 bytes of the header clusters will contain the LUKS +partition header. This is then followed by the key material data areas. +The size of the key material data areas is determined by the number of +stripes in the key slot and key size. Refer to the LUKS format +specification ('docs/on-disk-format.pdf' in the cryptsetup source +package) for details of the LUKS partition header format. + +In the LUKS partition header, the "payload-offset" field does not refer +to the offset of the QCow2 payload. Instead it simply refers to the +total required length of the LUKS header plus key material regions. + +In the LUKS key slots header, the "key-material-offset" is relative +to the start of the LUKS header clusters in the qcow2 container, +not the start of the qcow2 file. + +Logically the layout looks like + + +-----------------------------+ + | QCow2 header | + +-----------------------------+ + | QCow2 header extension X | + +-----------------------------+ + | QCow2 header extension FDE | + +-----------------------------+ + | QCow2 header extension ... | + +-----------------------------+ + | QCow2 header extension Z | + +-----------------------------+ + | ....other QCow2 tables.... | + . . + . . + +-----------------------------+ + | +-------------------------+ | + | | LUKS partition header | | + | +-------------------------+ | + | | LUKS key material 1 | | + | +-------------------------+ | + | | LUKS key material 2 | | + | +-------------------------+ | + | | LUKS key material ... | | + | +-------------------------+ | + | | LUKS key material 8 | | + | +-------------------------+ | + +-----------------------------+ + | QCow2 cluster payload | + . . + . . + . . + | | + +-----------------------------+ + == Host cluster management == qcow2 manages the allocation of host clusters by maintaining a reference countqapi/block-core.json +4
-2@@ -1789,6 +1789,8 @@ # @cache-clean-interval: #optional clean unused entries in the L2 and refcount # caches. The interval is in seconds. The default value # is 0 and it disables this feature (since 2.5) +# @key-id: #optional the ID of a QCryptoSecret object providing +# the decryption key (since 2.6) # # Since: 1.7 ##@@ -1802,8 +1804,8 @@ '*cache-size': 'int', '*l2-cache-size': 'int', '*refcount-cache-size': 'int', - '*cache-clean-interval': 'int' } } - + '*cache-clean-interval': 'int', + '*key-id': 'str' } } ## # @BlockdevOptionsArchipelagoSignature · 1 line
2.5.0
Kevin
Re: [Qemu-devel] [Qemu-block] [PATCH v1 10/15] qcow2: convert QCow2 to use QCryptoBlock for encryption
Quoted 41 lines · Kevin Wolf
Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
Quoted 40 lines
This converts the qcow2 driver to make use of the QCryptoBlock APIs for encrypting image content. As well as continued support for the legacy QCow2 encryption format, the appealing benefit is that it enables support for the LUKS format inside qcow2.
With the LUKS format it is neccessary to store the LUKS partition header and key material in the QCow2 file. This data can be many MB in size, so cannot go into the QCow2 header region directly. Thus the spec is defines a FDE (Full Disk Encryption) header extension that specifies the offset of a set of clusters to hold the FDE headers, as well as the length of that region. The LUKS header is thus stored in these extra allocated clusters before the main image payload.
With this change it is now required to use the QCryptoSecret object for providing passwords, instead of the current block password APIs / interactive prompting.
$QEMU \ -object secret,id=sec0,filename=/home/berrange/encrypted.pw \ -drive file=/home/berrange/encrypted.qcow2,key-id=sec0The new LUKS format is set as the new default format when creating encrypted images. ie
# qemu-img create --object secret,data=123456,id=sec0 \ -f qcow2 -o encryption,key-id=sec0 \ test.qcow2 10GResults in creation of an image using the LUKS format.
For compatibility the old qcow2 AES format can still be used via the 'encryption-format' parameter which accepts the values 'luks' or 'qcowaes'.
# qemu-img create --object secret,data=123456,id=sec0 \ -f qcow2 -o encryption,key-id=sec0,encryption-format=qcowaes \ test.qcow2 10G
Quoted 6 lines
Quoted 1 line
Signed-off-by Daniel P. Berrange <[email protected]>I think for your additional pointer to some clusters you need to change some function(s) called by
qcow2_check_refcounts(). Otherwise 'qemu-img check' won't count the reference and helpfully free the "leaked" cluster.
Opps, yes, having those garbage collected would be a very bad thing for your ability to decrypt your data :-)
Quoted 9 lines
block/qcow2.c typedef struct { · line 65
Quoted 5 lines
block/qcow2.c +1
-0@@ -60,6 +62,7 @@ typedef struct {#define QCOW2_EXT_MAGIC_END 0#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857 +#define QCOW2_EXT_MAGIC_FDE_HEADER 0x4c554b53General naming comment on this series: I would prefer avoiding "FDE" in favour of "encryption" or "crypt" in the block layer parts. With all image formats having their own terminology, "FDE" could mean anything.
Ok, will rename this - wasn't too happy with FDE myself either.
Quoted 15 lines
Quoted 15 lines
block/qcow2.c +9
-0static int qcow2_probe (const uint8_t *buf,int buf_size,const char *filename) {@@ -74,6 +77,83 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) } +static ssize_t qcow2_fde_header_read_func (QCryptoBlock *block, +size_t offset, +uint8_t *buf, +size_t buflen, +Error **errp, +void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque;
Quoted 8 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 93
Quoted 5 lines
block/qcow2.c +5
-0+ssize_t ret; + +if ((offset + buflen) > s->fde_header.length) { +error_setg_errno (errp,EINVAL , +"Request for data outside of extension header" );
error_setg_errno()with a constant errno doesn't look very useful. Better use plainerror_setg()in such cases.
I wasn't too sure - I figured since the block layer seems to propagate errno's around alot, that I ought to report an errno here, but will happiyl drop it.
Quoted 5 lines
Review static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 94
Quoted 1 line
(quoted diff) +1
-0+ return -1;Here returning
-EINVALcould be useful, I'm not sure what your crypto API requires. At least you seem to be returning -errno below and mixing -1 and -errno is probably a bad idea.
The crypto API doesn't deal with errno's at all - it uses the Error object exclusively, so yeah, I can drop it from the place below.
Quoted 27 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 103
Quoted 10 lines
block/qcow2.c +9
-0+ } + + ret =bdrv_pread (bs->file->bs, + s->fde_header.offset + offset, buf, buflen); +if (ret <0 ) { +error_setg_errno (errp, -ret,"Could not read encryption header" ); +return ret; + } +return ret;return 0? You already processed ret in the if block and two 'return ret' in a row look odd.
Quoted 13 lines
block/qcow2.c +13
-0+} + + +static ssize_t qcow2_fde_header_init_func (QCryptoBlock *block, +size_t headerlen, +Error **errp, +void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque; +int64_t ret; + + s->fde_header.length = headerlen + (headerlen % s->cluster_size);
Quoted 8 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 122
Quoted 6 lines
block/qcow2.c +6
-0+ + ret =qcow2_alloc_clusters (bs, s->fde_header.length); +if (ret <0 ) { + s->fde_header.length =0 ; +error_setg (errp,"Cannot allocate cluster for LUKS header size %zu" , + headerlen);I think ret is -errno on failure, so use
error_setg_errno()?
Ok.
Quoted 27 lines
Quoted 13 lines
block/qcow2.c +12
-0+return -1 ; + } + + s->fde_header.offset = ret; +return 0 ; +} + + +static ssize_t qcow2_fde_header_write_func(QCryptoBlock *block, + size_t offset, + const uint8_t *buf, + size_t buflen,block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 144
Quoted 10 lines
block/qcow2.c +10
-0+ Error **errp, + void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque; +ssize_t ret; + +if ((offset + buflen) > s->fde_header.length) { +error_setg_errno (errp,EINVAL , +"Request for data outside of extension header" );
error_setg(). Probably worth checking all error paths whether there is a useful errno or not. I won't comment on additional instances.
Quoted 12 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 154
Quoted 10 lines
block/qcow2.c +10
-0+return -1 ; + } + + ret =bdrv_pwrite (bs->file->bs, s->fde_header.offset + offset, buf, buflen); +if (ret <0 ) { +error_setg_errno (errp, -ret,"Could not read encryption header" ); +return ret; + } +return ret; +}Mixing -1 and -errno again.
Will fix as per the read_func() above.
Quoted 7 lines
Quoted 7 lines
block/qcow2.c +1
-0@@ -83,12 +163,14 @@ static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)*/ static int qcow2_read_extensions (BlockDriverState *bs,uint64_t start_offset,uint64_t end_offset,void **p_feature_table, +int flags,Error **errp) {
Quoted 8 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 173
Quoted 5 lines
block/qcow2.c +1
-0BDRVQcow2State *s = bs->opaque;QCowExtension ext;uint64_t offset;int ret; +unsigned int cflags =0 ;Should we create a block for
QCOW2_EXT_MAGIC_FDE_HEADERand declare it there?
Yep, I can do that.
Quoted 35 lines
Quoted 10 lines
block/qcow2.c +4
-0#ifdef DEBUG_EXT printf ("qcow2_read_extensions: start=%ld end=%ld\n" , start_offset, end_offset);@@ -159,6 +241,35 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, }break ; +case QCOW2_EXT_MAGIC_FDE_HEADER : +if (s->crypt_method_header !=QCOW_CRYPT_LUKS ) { +error_setg (errp,"FDE header extension only " +"expected with LUKS encryption method" );block/qcow2.c static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, · line 257
Quoted 10 lines
block/qcow2.c +10
-0+return -EINVAL ; + } +if (ext.len !=sizeof (Qcow2FDEHeaderExtension)) { +error_setg (errp,"LUKS header extension size %u, " +"but expected size %zu" , ext.len, +sizeof (Qcow2FDEHeaderExtension)); +return -EINVAL ; + } + + ret =bdrv_pread (bs->file->bs, offset, &s->fde_header, ext.len);No error check?
block/qcow2.c static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, · line 268
Quoted 11 lines
block/qcow2.c +11
-0+be64_to_cpu (s->fde_header.offset); +be64_to_cpu (s->fde_header.length); + +if (flags &BDRV_O_NO_IO ) { + cflags |=QCRYPTO_BLOCK_OPEN_NO_IO ; + } + s->fde =qcrypto_block_open (s->fde_opts, + qcow2_fde_header_read_func, + bs, + cflags, + errp);
Quoted 3 lines
The surrounding code doesn't put every parameter on its own line if the next parameter can fit on the same line. There are more instances of this in the patch, I won't comment on each one.
Ok, that's just my habit from libvirt - where we either put everything on one line, or everything on a separate line and don't mix.
Quoted 11 lines
Quoted 11 lines
block/qcow2.c +3
-1@@ -2213,12 +2517,15 @@ static int qcow2_create2(const char *filename, int64_t total_size,/* * And now open the image and make it consistent first (i.e. increase the * refcount of the cluster that is occupied by the header and the refcount -* table) +* table). Using BDRV_O_NO_IO since we've not written any encryption +* header yet and thus should not read/write payload data or initialize +* the decryption context */ options =qdict_new ();qdict_put (options,"driver" ,qstring_from_str ("qcow2" ));
Quoted 8 lines
block/qcow2.c static int qcow2_create2(const char *filename, int64_t total_size, · line 2529
Quoted 5 lines
block/qcow2.c +2
-1ret =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);Somehow I feel that saying
BDRV_O_NO_IO, but doing I/O will bite us sooner or later.
Indeed, once I add the assertions you suggested in the previous patch this will probably break horribly.
Quoted 2 lines
Why not leave
header->crypt_method= 0 initially and only set up encryption after opening the image with the qcow2 driver?
Oh yes, good idea.
Quoted 41 lines
Quoted 41 lines
block/qcow2.h +11
-1@@ -25,7 +25,7 @@#ifndef BLOCK_QCOW2_H #define BLOCK_QCOW2_H -#include "crypto/cipher.h" +#include "crypto/block.h" #include "qemu/coroutine.h" //#define DEBUG_ALLOC @@ -36,6 +36,7 @@#define QCOW_CRYPT_NONE 0#define QCOW_CRYPT_AES 1 +#define QCOW_CRYPT_LUKS 2#define QCOW_MAX_CRYPT_CLUSTERS 32#define QCOW_MAX_SNAPSHOTS 65536@@ -98,6 +99,15 @@#define QCOW2_OPT_REFCOUNT_CACHE_SIZE "refcount-cache-size"#define QCOW2_OPT_CACHE_CLEAN_INTERVAL "cache-clean-interval" +#define QCOW2_OPT_ENC_FORMAT "encryption-format" +#define QCOW2_OPT_KEY_ID "key-id" +#define QCOW2_OPT_CIPHER_ALG "cipher-alg" +#define QCOW2_OPT_CIPHER_MODE "cipher-mode" +#define QCOW2_OPT_IVGEN_ALG "ivgen-alg" +#define QCOW2_OPT_IVGEN_HASH_ALG "ivgen-hash-alg" +#define QCOW2_OPT_HASH_ALG "hash-alg" + + typedef struct QCowHeader { uint32_t magic; uint32_t version;@@ -163,6 +173,11 @@ typedef struct QCowSnapshot {struct Qcow2Cache ;typedef struct Qcow2Cache Qcow2Cache ;
Quoted 6 lines
block/qcow2.h typedef struct QCowSnapshot { · line 179
Quoted 4 lines
block/qcow2.h +4
-0+typedef struct Qcow2FDEHeaderExtension { +uint64_t offset; +uint64_t length; +}Qcow2FDEHeaderExtension ;Packed? It seems to be read directly from the file.
Yes
Quoted 13 lines
block/qcow2.h the header extension data. Each entry look like this: · line 175
Quoted 9 lines
block/qcow2.h +5
-0@@ -166,6 +168,75 @@ the header extension data. Each entry look like this: terminated if it has full length) +== Full disk encryption (FDE) header pointer == + +For 'crypt_method' header values which require additional header metadata +to be stored (eg 'LUKS' header), the full disk encryption header pointer +extension is mandatory.I think you want to make that "is present if, and only if, the '
crypt_method' header value requires metadata".
Quoted 3 lines
At least, we need to forbid it for the existing AES images, because old qemu-img version would stll fix the "leaked clusters", without removing the header extension that refers to them.
Yes, we shouldn't be using the header with the AES crypt method, only the LUKS method for now.
Regards,
Daniel
Signature · 4 lines
|: 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 :|
Re: [Qemu-devel] [Qemu-block] [PATCH v1 10/15] qcow2: convert QCow2 to use QCryptoBlock for encryption
Am 14.01.2016 um 13:14 hat Daniel P. Berrange geschrieben:
Quoted 20 lines · Kevin Wolf
Quoted 10 lines
Am 12.01.2016 um 19:56 hat Daniel P. Berrange geschrieben:
Quoted 9 lines
block/qcow2.c +9
-0+static ssize_t qcow2_fde_header_read_func (QCryptoBlock *block, +size_t offset, +uint8_t *buf, +size_t buflen, +Error **errp, +void *opaque) +{ +BlockDriverState *bs = opaque; +BDRVQcow2State *s = bs->opaque;Quoted 8 lines
block/qcow2.c static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) · line 93
Quoted 5 lines
block/qcow2.c +5
-0+ssize_t ret; + +if ((offset + buflen) > s->fde_header.length) { +error_setg_errno (errp,EINVAL , +"Request for data outside of extension header" );
error_setg_errno()with a constant errno doesn't look very useful. Better use plainerror_setg()in such cases.
Quoted 3 lines
I wasn't too sure - I figured since the block layer seems to propagate errno's around alot, that I ought to report an errno here, but will happiyl drop it.
error_setg_errno() doesn't keep the error code around for the callers to inspect, but just adds the error string to the message. And you already have a much more useful error message than "Invalid argument".
Quoted 9 lines
Quoted 5 lines
Quoted 1 line
+ return -1;
Here returning
-EINVALcould be useful, I'm not sure what your crypto API requires. At least you seem to be returning -errno below and mixing -1 and -errno is probably a bad idea.The crypto API doesn't deal with errno's at all - it uses the Error object exclusively, so yeah, I can drop it from the place below.
Then you could probably just make the function void. I genereally prefer to use only one mechanism to return errors instead of both an int return value and an Error** argument, but there are many places in qemu which use both. So whatever feels right to you.
Kevin
PATCH 11/15 qcow: make encrypt_sectors encrypt in place
In reply to Daniel P. BerrangeInstead of requiring separate input/output buffers for encrypting data, change encrypt_sectors() to assume use of a single buffer, encrypting in place. One current caller all uses the same buffer for input/output already and the other two callers are easily converted todo so.
--- block/qcow.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-)
block/qcow.c +10 -21
@@ -293,12 +293,9 @@ static int qcow_set_key(BlockDriverState *bs, const char *key)return 0 ; } -/* The crypt function is compatible with the linux cryptoloop -algorithm for < 4 GB images. NOTE: out_buf == in_buf is -supported */ static int encrypt_sectors (BDRVQcowState *s,int64_t sector_num, -uint8_t *out_buf,const uint8_t *in_buf, -int nb_sectors,bool enc,Error **errp) +uint8_t *buf,int nb_sectors,bool enc, +Error **errp) {union {uint64_t ll[2 ];
@@ -317,14 +314,12 @@ static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num, }if (enc) { ret =qcrypto_cipher_encrypt (s->cipher, - in_buf, - out_buf, + buf, buf,512 , errp); }else { ret =qcrypto_cipher_decrypt (s->cipher, - in_buf, - out_buf, + buf, buf,512 , errp); }
@@ -332,8 +327,7 @@ static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,return -1 ; } sector_num++; - in_buf +=512 ; - out_buf +=512 ; + buf +=512 ; }return 0 ; }
@@ -453,13 +447,12 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,uint64_t start_sect;assert (s->cipher); start_sect = (offset & ~(s->cluster_size -1 )) >>9 ; -memset (s->cluster_data +512 ,0x00 ,512 ); +memset (s->cluster_data,0x00 ,512 );for (i =0 ; i < s->cluster_sectors; i++) {if (i < n_start || i >= n_end) {Error *err =NULL ;if (encrypt_sectors (s, start_sect + i, - s->cluster_data, - s->cluster_data +512 ,1 , + s->cluster_data,1 , true, &err) <0 ) {error_free (err); errno =EIO ;
@@ -637,7 +630,7 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num, }if (bs->encrypted) {assert (s->cipher); -if (encrypt_sectors (s, sector_num, buf, buf, +if (encrypt_sectors (s, sector_num, buf, n, false, &err) <0 ) { goto fail; }
@@ -672,7 +665,6 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,BDRVQcowState *s = bs->opaque;int index_in_cluster;uint64_t cluster_offset; -const uint8_t *src_buf;int ret =0 , n;uint8_t *cluster_data =NULL ;struct iovec hd_iov;
@@ -715,18 +707,15 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,if (!cluster_data) { cluster_data =g_malloc0 (s->cluster_size); } -if (encrypt_sectors (s, sector_num, cluster_data, buf, +if (encrypt_sectors (s, sector_num, buf, n, true, &err) <0 ) {error_free (err); ret = -EIO ;break ; } - src_buf = cluster_data; - }else { - src_buf = buf; } - hd_iov.iov_base = (void *)src_buf; + hd_iov.iov_base = (void *)buf; hd_iov.iov_len = n *512 ;qemu_iovec_init_external (&hd_qiov, &hd_iov,1 );qemu_co_mutex_unlock (&s->lock);
Signature · 1 line
2.5.0
PATCH 12/15 qcow: convert QCow to use QCryptoBlock for encryption
In reply to Daniel P. BerrangeThis converts the qcow2 driver to make use of the QCryptoBlock APIs for encrypting image content. This is only wired up to permit use of the legacy QCow encryption format. Users who wish to have the strong LUKS format should switch to qcow2 instead.
With this change it is now required to use the QCryptoSecret object for providing passwords, instead of the current block password APIs / interactive prompting.
$QEMU \
-object secret,id=sec0,filename=/home/berrange/encrypted.pw \
-drive file=/home/berrange/encrypted.qcow,key-id=sec0--- block/qcow.c | 176 +++++++++++++++++++++++---------------------------- qapi/block-core.json | 17 ++++- 2 files changed, 96 insertions(+), 97 deletions(-)
block/qcow.c +80 -96
@@ -26,7 +26,9 @@#include "qemu/module.h" #include <zlib.h> #include "qapi/qmp/qerror.h" -#include "crypto/cipher.h" +#include "crypto/block.h" +#include "qapi/opts-visitor.h" +#include "qapi-visit.h" #include "migration/migration.h" /**************************************************************/
@@ -40,6 +42,8 @@#define QCOW_OFLAG_COMPRESSED (1LL << 63) +#define QCOW_OPT_KEY_ID "key-id" + typedef struct QCowHeader { uint32_t magic; uint32_t version;
@@ -72,7 +76,7 @@ typedef struct BDRVQcowState {uint8_t *cluster_cache;uint8_t *cluster_data;uint64_t cluster_cache_offset; -QCryptoCipher *cipher;/* NULL if no key yet */ +QCryptoBlock *fde;/* Disk encryption format driver */ uint32_t crypt_method_header;CoMutex lock;Error *migration_blocker;
@@ -92,6 +96,19 @@ static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename)return 0 ; } +static QemuOptsList qcow_runtime_opts = { + .name ="qcow" , + .head =QTAILQ_HEAD_INITIALIZER (qcow_runtime_opts.head), + .desc = { + { + .name =QCOW_OPT_KEY_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 +116,11 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,unsigned int len, i, shift;int ret;QCowHeader header; +QemuOpts *opts =NULL ; +Error *local_err =NULL ; +QCryptoBlockOpenOptions *fde_opts =NULL ; +unsigned int cflags =0 ; +OptsVisitor *ov; ret =bdrv_pread (bs->file->bs,0 , &header,sizeof (header));if (ret <0 ) {
@@ -147,18 +169,50 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags, goto fail; } -if (header.crypt_method >QCOW_CRYPT_AES ) { -error_setg (errp,"invalid encryption method in qcow header" ); - ret = -EINVAL ; - goto fail; - } -if (!qcrypto_cipher_supports (QCRYPTO_CIPHER_ALG_AES_128 )) { -error_setg (errp,"AES cipher not available" ); + opts =qemu_opts_create (&qcow_runtime_opts,NULL ,0 , &error_abort); +qemu_opts_absorb_qdict (opts, options, &local_err); +if (local_err) { +error_propagate (errp, local_err); ret = -EINVAL ; goto fail; } + s->crypt_method_header = header.crypt_method;if (s->crypt_method_header) { +if (s->crypt_method_header ==QCOW_CRYPT_AES ) { + ov =opts_visitor_new (opts); + + fde_opts =g_new0 (QCryptoBlockOpenOptions,1 ); + fde_opts->format =Q_CRYPTO_BLOCK_FORMAT_QCOWAES ; + fde_opts->u.qcowaes =g_new0 (QCryptoBlockOptionsQCowAES,1 ); +visit_type_QCryptoBlockOptionsQCowAES (opts_get_visitor (ov), + &fde_opts->u.qcowaes, +"qcowaes" , &local_err); +if (local_err) { +error_propagate (errp, local_err); +opts_visitor_cleanup (ov); + ret = -EINVAL ; + goto fail; + } +opts_visitor_cleanup (ov); + +if (flags &BDRV_O_NO_IO ) { + cflags |=QCRYPTO_BLOCK_OPEN_NO_IO ; + } + s->fde =qcrypto_block_open (fde_opts, +NULL ,NULL , + cflags, + errp); +if (!s->fde) { +error_setg (errp,"Could not setup encryption layer" ); + ret = -EINVAL ; + goto fail; + } + }else { +error_setg (errp,"invalid encryption method in qcow header" ); + ret = -EINVAL ; + goto fail; + } bs->encrypted =1 ; } s->cluster_bits = header.cluster_bits;
@@ -238,6 +292,7 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags, bdrv_get_device_or_node_name(bs));migrate_add_blocker (s->migration_blocker); +qapi_free_QCryptoBlockOpenOptions (fde_opts);qemu_co_mutex_init (&s->lock);return 0 ;
@@ -246,6 +301,7 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,qemu_vfree (s->l2_cache);g_free (s->cluster_cache);g_free (s->cluster_data); +qapi_free_QCryptoBlockOpenOptions (fde_opts);return ret; }
@@ -258,79 +314,6 @@ static int qcow_reopen_prepare(BDRVReopenState *state,return 0 ; } -static int qcow_set_key (BlockDriverState *bs,const char *key) -{ -BDRVQcowState *s = bs->opaque; -uint8_t keybuf[16 ]; -int len, i; -Error *err; - -memset (keybuf,0 ,16 ); - len =strlen (key); -if (len >16 ) - len =16 ; -/* XXX: we could compress the chars to 7 bits to increase -entropy */ -for (i =0 ;i < len;i++) { - keybuf[i] = key[i]; - } -assert (bs->encrypted); - -qcrypto_cipher_free (s->cipher); - s->cipher =qcrypto_cipher_new ( -QCRYPTO_CIPHER_ALG_AES_128 , -QCRYPTO_CIPHER_MODE_CBC , - keybuf,G_N_ELEMENTS (keybuf), - &err); - -if (!s->cipher) { -/* XXX would be nice if errors in this method could -* be properly propagate to the caller. Would need -* the bdrv_set_key() API signature to be fixed. */ -error_free (err); -return -1 ; - } -return 0 ; -} - -static int encrypt_sectors (BDRVQcowState *s,int64_t sector_num, -uint8_t *buf,int nb_sectors,bool enc, -Error **errp) -{ -union { -uint64_t ll[2 ]; -uint8_t b[16 ]; - } ivec; -int i; -int ret; - -for (i =0 ; i < nb_sectors; i++) { - ivec.ll[0 ] =cpu_to_le64 (sector_num); - ivec.ll[1 ] =0 ; -if (qcrypto_cipher_setiv (s->cipher, - ivec.b,G_N_ELEMENTS (ivec.b), - errp) <0 ) { -return -1 ; - } -if (enc) { - ret =qcrypto_cipher_encrypt (s->cipher, - buf, buf, -512 , - errp); - }else { - ret =qcrypto_cipher_decrypt (s->cipher, - buf, buf, -512 , - errp); - } -if (ret <0 ) { -return -1 ; - } - sector_num++; - buf +=512 ; - } -return 0 ; -}/* 'allocate' is: *
@@ -445,15 +428,16 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,if (bs->encrypted && (n_end - n_start) < s->cluster_sectors) {uint64_t start_sect; -assert (s->cipher); +assert (s->fde); start_sect = (offset & ~(s->cluster_size -1 )) >>9 ;memset (s->cluster_data,0x00 ,512 );for (i =0 ; i < s->cluster_sectors; i++) {if (i < n_start || i >= n_end) {Error *err =NULL ; -if (encrypt_sectors (s, start_sect + i, - s->cluster_data,1 , - true, &err) <0 ) { +if (qcrypto_block_encrypt (s->fde, + start_sect + i, + s->cluster_data,1 , + &err) <0 ) {error_free (err); errno =EIO ;return -1 ;
@@ -498,7 +482,7 @@ static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,if (!cluster_offset) {return 0 ; } -if ((cluster_offset &QCOW_OFLAG_COMPRESSED ) || s->cipher) { +if ((cluster_offset &QCOW_OFLAG_COMPRESSED ) || s->fde) {return BDRV_BLOCK_DATA ; } cluster_offset |= (index_in_cluster <<BDRV_SECTOR_BITS );
@@ -629,9 +613,10 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,break ; }if (bs->encrypted) { -assert (s->cipher); -if (encrypt_sectors (s, sector_num, buf, - n, false, &err) <0 ) { +assert (s->fde); +if (qcrypto_block_decrypt (s->fde, + sector_num, buf, + n, &err) <0 ) { goto fail; } }
@@ -703,12 +688,12 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num, }if (bs->encrypted) {Error *err =NULL ; -assert (s->cipher); +assert (s->fde);if (!cluster_data) { cluster_data =g_malloc0 (s->cluster_size); } -if (encrypt_sectors (s, sector_num, buf, - n, true, &err) <0 ) { +if (qcrypto_block_encrypt (s->fde, sector_num, buf, + n, &err) <0 ) {error_free (err); ret = -EIO ;break ;
@@ -746,8 +731,8 @@ static void qcow_close(BlockDriverState *bs) {BDRVQcowState *s = bs->opaque; -qcrypto_cipher_free (s->cipher); - s->cipher =NULL ; +qcrypto_block_free (s->fde); + s->fde =NULL ;g_free (s->l1_table);qemu_vfree (s->l2_cache);g_free (s->cluster_cache);
@@ -1012,7 +997,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,
qapi/block-core.json +16 -1
@@ -1756,6 +1756,21 @@ 'mode': 'Qcow2OverlapCheckMode' } } ## +# @BlockdevOptionsQcow +# +# Driver specific block device options for qcow. +# +# @key-id: #optional ID of the "secret" object providing the +# AES decryption key. +# +# Since: 2.6 +## +{ 'struct': 'BlockdevOptionsQcow', + 'base': 'BlockdevOptionsGenericCOWFormat', + 'data': { '*key-id': 'str' } } + + +## # @BlockdevOptionsQcow2 # # Driver specific block device options for qcow2.
@@ -2024,7 +2039,7 @@ 'null-co': 'BlockdevOptionsNull', 'parallels': 'BlockdevOptionsGenericFormat', 'qcow2': 'BlockdevOptionsQcow2', - 'qcow': 'BlockdevOptionsGenericCOWFormat', + 'qcow': 'BlockdevOptionsQcow', 'qed': 'BlockdevOptionsGenericCOWFormat', 'quorum': 'BlockdevOptionsQuorum', 'raw': 'BlockdevOptionsGenericFormat',
Signature · 1 line
2.5.0
PATCH 13/15 block: rip out all traces of password prompting
In reply to Daniel P. BerrangeNow that qcow & qcow2 are wired up to get encryption keys via the QCryptoSecret object, nothing is relying on the interactive prompting for passwords. All the code related to password prompting can thus be ripped out.
--- hmp.c | 31 -------------------- hw/usb/dev-storage.c | 34 ---------------------- include/monitor/monitor.h | 7 ----- include/qemu/osdep.h | 2 -- monitor.c | 69 -------------------------------------------- qemu-img.c | 14 --------- 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 --------------- 15 files changed, 36 insertions(+), 292 deletions(-)
hmp.c +0 -31
@@ -970,37 +970,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)
@@ -1387,12 +1362,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);
hw/usb/dev-storage.c +0 -34
@@ -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);
include/monitor/monitor.h +0 -7
@@ -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);
include/qemu/osdep.h +0 -2
@@ -299,8 +299,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: *
monitor.c +0 -69
75 lines not on this page · raw message
qemu-img.c +0 -14
@@ -270,8 +270,6 @@ static BlockBackend *img_open_file(const char *id, const char *filename, bool quiet) {BlockBackend *blk; -BlockDriverState *bs; -char password[256 ];Error *local_err =NULL ;QDict *options =NULL ;
@@ -288,18 +286,6 @@ static BlockBackend *img_open_file(const char *id, const char *filename, goto fail; } - bs =blk_bs (blk); -if (bdrv_is_encrypted (bs) && !(flags &BDRV_O_NO_IO )) { -qprintf (quiet,"Disk image '%s' is encrypted.\n" , filename); -if (qemu_read_password (password,sizeof (password)) <0 ) { -error_report ("No password given" ); - goto fail; - } -if (bdrv_set_key (bs, password) <0 ) { -error_report ("invalid password" ); - goto fail; - } - }return blk; fail:blk_unref (blk);
qemu-io.c +0 -21
@@ -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 )
qmp.c +0 -9
@@ -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 ;
tests/qemu-iotests/087 +20 -0
@@ -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"
tests/qemu-iotests/087.out +2 -0
@@ -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.
tests/qemu-iotests/134 +12 -5
@@ -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,fde_format=qcowaes" _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" --image-opts "$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" --image-opts "$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
tests/qemu-iotests/134.out +0 -8
@@ -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)
tests/qemu-iotests/common.rc +2 -2
@@ -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
util/oslib-posix.c +0 -66
72 lines not on this page · raw message
util/oslib-win32.c +0 -24
@@ -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 ;
Signature · 1 line
2.5.0
PATCH 14/15 block: remove all encryption handling APIs
In reply to Daniel P. BerrangeNow 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.
--- block.c | 82 ++-------------------------------------------- block/qapi.c | 2 +- blockdev.c | 40 ++-------------------- include/block/block.h | 4 --- include/block/block_int.h | 1 - tests/qemu-iotests/087.out | 4 +-- 6 files changed, 7 insertions(+), 126 deletions(-)
block.c +2 -80
@@ -1696,17 +1696,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);
@@ -2193,7 +2184,6 @@ void bdrv_close(BlockDriverState *bs) bs->backing_format[0 ] ='\0' ; bs->total_sectors =0 ; bs->encrypted =0 ; - bs->valid_key =0 ; bs->sg =0 ; bs->zero_beyond_eof = false;QDECREF (bs->options);
@@ -2777,74 +2767,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 ;
block/qapi.c +1 -1
@@ -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 ) {
blockdev.c +2 -38
@@ -623,10 +623,6 @@ static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,bdrv_set_io_limits (bs, &cfg); } -if (bdrv_key_required (bs)) { - autostart =0 ; - } -block_acct_init (blk_get_stats (blk), account_invalid, account_failed);if (!parse_stats_intervals (blk_get_stats (blk), interval_list, errp)) {
@@ -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);
@@ -3851,16 +3825,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); }
include/block/block.h +0 -4
@@ -414,10 +414,6 @@ bool bdrv_chain_contains(BlockDriverState *top, BlockDriverState *base);BlockDriverState *bdrv_next_node (BlockDriverState *bs);BlockDriverState *bdrv_next (BlockDriverState *bs);int bdrv_is_encrypted (BlockDriverState *bs); -int bdrv_key_required (BlockDriverState *bs); -int bdrv_set_key (BlockDriverState *bs,const char *key); -void bdrv_add_key (BlockDriverState *bs,const char *key,Error **errp); -int bdrv_query_missing_keys (void );void bdrv_iterate_format (void (*it)(void *opaque,const char *name),void *opaque);const char *bdrv_get_node_name (const BlockDriverState *bs);
include/block/block_int.h +0 -1
@@ -374,7 +374,6 @@ struct BlockDriverState {int read_only;/* if true, the media is read only */ int open_flags;/* flags used to open the file, re-used for re-open */ int encrypted;/* if true, the media is encrypted */ -int valid_key;/* if true, a valid encryption key has been set */ int sg;/* if true, the device is a /dev/sg* */ int copy_on_read;/* if true, copy read backing sectors into image note this is a reference count */
tests/qemu-iotests/087.out +2 -2
@@ -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"}
Signature · 1 line
2.5.0
PATCH 15/15 block: remove support for legecy AES qcow/qcow2 encryption
In reply to Daniel P. BerrangeRefuse to use images with the legacy AES-CBC encryption format in the system emulators. They are still fully supported in the qemu-img, qemu-io & qemu-nbd tools in order to allow data to be liberated and for compatibility with older QEMU versions. Continued support in these tools is not a notable burden with the new FDE framework.
--- block.c | 12 +++++------- block/qcow.c | 8 ++++++++ block/qcow2.c | 8 ++++++++ include/block/block.h | 1 + tests/qemu-iotests/087.out | 18 ------------------ tests/qemu-iotests/134.out | 18 ------------------ 6 files changed, 22 insertions(+), 43 deletions(-)
block.c +5 -7
@@ -312,6 +312,11 @@ static int bdrv_is_whitelisted(BlockDriver *drv, bool read_only)return 0 ; } +bool bdrv_uses_whitelist (void ) +{ +return use_bdrv_whitelist; +} + typedef struct CreateCo { BlockDriver *drv; char *filename;
@@ -1020,13 +1025,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" );
block/qcow.c +8 -0
@@ -180,6 +180,14 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags, s->crypt_method_header = header.crypt_method;if (s->crypt_method_header) {if (s->crypt_method_header ==QCOW_CRYPT_AES ) { +if (bdrv_uses_whitelist ()) { +error_setg (errp, +"Use of AES-CBC encrypted qcow images is no longer " +"supported. Please use the qcow2 LUKS format instead." ); + ret = -ENOSYS ; + goto fail; + } + ov =opts_visitor_new (opts); fde_opts =g_new0 (QCryptoBlockOpenOptions,1 );
block/qcow2.c +8 -0
@@ -1221,6 +1221,14 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, s->crypt_method_header = header.crypt_method;if (s->crypt_method_header) { +if (bdrv_uses_whitelist () && + s->crypt_method_header ==QCOW_CRYPT_AES ) { +error_setg (errp, +"Use of AES-CBC encrypted qcow2 images is no longer " +"supported. Please use the qcow2 LUKS format instead." ); + ret = -ENOSYS ; + goto fail; + } bs->encrypted =1 ; }
include/block/block.h +1 -0
@@ -190,6 +190,7 @@ void bdrv_io_limits_update_group(BlockDriverState *bs, const char *group);void bdrv_init (void );void bdrv_init_with_whitelist (void ); +bool bdrv_uses_whitelist (void );BlockDriver *bdrv_find_protocol (const char *filename,bool allow_protocol_prefix,Error **errp);
tests/qemu-iotests/087.out +0 -18
@@ -38,20 +38,11 @@ QMP_VERSION === Encrypted image === -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on Testing: -S QMP_VERSION {"return": {}} {"return": {}} -Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
@@ -60,9 +51,6 @@ Testing: QMP_VERSION {"return": {}} {"return": {}} -Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
@@ -70,12 +58,6 @@ You can use 'qemu-img convert' to convert your image to an unencrypted one. === Missing driver === -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on Testing: -S QMP_VERSION
tests/qemu-iotests/134.out +0 -18
@@ -1,37 +1,19 @@ QA output created by 134 -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. -qemu-img: Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on == reading whole image == -Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. read 134217728/134217728 bytes at offset 0 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == rewriting whole image == -Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. wrote 134217728/134217728 bytes at offset 0 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == verify pattern == -Encrypted images are deprecated -Support for them will be removed in a future release. -You can use 'qemu-img convert' to convert your image to an unencrypted one. read 134217728/134217728 bytes at offset 0 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == 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)
Signature · 1 line
2.5.0