For a luks device, allow the configuration of a specific cipher to be
used for encrypting the volume.
Signed-off-by: John Ferlan <jferlan(a)redhat.com>
---
docs/formatstorageencryption.html.in | 81 +++++++++++++-
docs/schemas/storagecommon.rng | 44 +++++++-
src/conf/domain_conf.c | 11 ++
src/util/virstorageencryption.c | 124 +++++++++++++++++++++
src/util/virstorageencryption.h | 13 +++
.../qemuxml2argv-luks-disk-cipher.xml | 41 +++++++
.../qemuxml2xmlout-luks-disk-cipher.xml | 45 ++++++++
tests/qemuxml2xmltest.c | 1 +
tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++++
tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++++
tests/storagevolxml2xmltest.c | 1 +
11 files changed, 401 insertions(+), 6 deletions(-)
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml
create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml
diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in
index 3a08192..80111e3 100644
--- a/docs/formatstorageencryption.html.in
+++ b/docs/formatstorageencryption.html.in
@@ -71,6 +71,58 @@
be used as the passphrase to decrypt the volume.
<span class="since">Since 2.0.0</span>.
</p>
+ <p>
+ For volume creation, it is possible to specify the encryption
+ algorithm used to encrypt the luks volume. The following two
+ optional elements may be provided for that purpose. It is hypervisor
+ dependent as to which algorithms are supported. The default algorithm
+ for QEMU is 'aes-256-cbc' using 'essiv' for initialization vector
+ generation and 'sha256' hash algorithm for both the cipher and the
+ initialization vector generation.
+ </p>
+
+ <dl>
+ <dt><code>cipher</code></dt>
+ <dd>This element describes the cipher algorithm to be used to either
+ encrypt or decrypt the luks volume. This element has the following
+ attributes:
+ <dl>
+ <dt><code>name</code></dt>
+ <dd>The name of the cipher algorithm used for data encryption,
+ such as 'aes', 'des', 'cast5', 'serpent',
'twofish', etc.
+ Support of the specific algorithm is hypervisor dependent.</dd>
+ <dt><code>size</code></dt>
+ <dd>The size of the cipher in bits, such as '256',
'192', '128',
+ etc. Support of the specific size for a specific cipher is
+ hypervisor dependent.</dd>
+ <dt><code>mode</code></dt>
+ <dd>An optional cipher algorithm mode such as 'cbc',
'xts',
+ 'ecb', etc. Support of the specific cipher mode is
+ hypervisor dependent.</dd>
+ <dt><code>hash</code></dt>
+ <dd>An optional master key hash algorithm such as 'md5',
'sha1',
+ 'sha256', etc. Support of the specific hash algorithm is
+ hypervisor dependent.</dd>
+ </dl>
+ </dd>
+ <dt><code>ivgen</code></dt>
+ <dd>This optional element describes the initialization vector
+ generation algorithm used in conjunction with the
+ <code>cipher</code>. If the <code>cipher</code> is not
provided,
+ then an error will be generated by the parser.
+ <dl>
+ <dt><code>name</code></dt>
+ <dd>The name of the algorithm, such as 'plain',
'plain64',
+ 'essiv', etc. Support of the specific algorithm is hypervisor
+ dependent.</dd>
+ <dt><code>hash</code></dt>
+ <dd>An optional hash algorithm such as 'md5', 'sha1',
'sha256',
+ etc. Support of the specific ivgen hash algorithm is hypervisor
+ dependent.</dd>
+ </dl>
+ </dd>
+ </dl>
+
<h2><a name="example">Examples</a></h2>
@@ -84,9 +136,12 @@
</encryption></pre>
<p>
- Here is a simple example, specifying use of the <code>luks</code>
format
- where it's assumed that a <code>secret</code> has been defined
using a
- <code>usage</code> element with a <code>id</code> of
"luks_example":
+ Assuming a <a href="formatsecret.html#luksUsageType">
+ <code>luks secret</code></a> is already defined using a
+ <code>usage</code> element with an <code>id</code> of
"luks_example",
+ a simple example specifying use of the <code>luks</code> format
+ for either volume creation without a specific cipher being defined or
+ as part of a domain volume definition:
</p>
<pre>
<encryption format='luks'>
@@ -94,5 +149,25 @@
</encryption>
</pre>
+ <p>
+ Here is an example, specifying use of the <code>luks</code> format for
+ a specific cipher algorihm for volume creation:
+ </p>
+ <pre>
+ <volume>
+ <name>twofish.luks</name>
+ <capacity unit='G'>5</capacity>
+ <target>
+ <path>/var/lib/libvirt/images/demo.luks</path>
+ <format type='luks'/>
+ <encryption format='luks'>
+ <secret type='passphrase'
usage='luks_example'/>
+ <cipher name='twofish' size='256' mode='cbc'
hash='sha256'/>
+ <ivgen name='plain64' hash='sha256'/>
+ </encryption>
+ </target>
+ </volume>
+ </pre>
+
</body>
</html>
diff --git a/docs/schemas/storagecommon.rng b/docs/schemas/storagecommon.rng
index 63b55b4..316fbae 100644
--- a/docs/schemas/storagecommon.rng
+++ b/docs/schemas/storagecommon.rng
@@ -15,9 +15,19 @@
<value>luks</value>
</choice>
</attribute>
- <zeroOrMore>
- <ref name='secret'/>
- </zeroOrMore>
+ <interleave>
+ <zeroOrMore>
+ <ref name='secret'/>
+ </zeroOrMore>
+ <optional>
+ <element name='cipher'>
+ <ref name='keycipher'/>
+ </element>
+ <element name='ivgen'>
+ <ref name='keyivgen'/>
+ </element>
+ </optional>
+ </interleave>
</element>
</define>
@@ -136,4 +146,32 @@
</optional>
</define>
+ <define name='keycipher'>
+ <attribute name='name'>
+ <text/>
+ </attribute>
+ <attribute name='size'>
+ <ref name="unsignedInt"/>
+ </attribute>
+ <optional>
+ <attribute name='mode'>
+ <text/>
+ </attribute>
+ <attribute name='hash'>
+ <text/>
+ </attribute>
+ </optional>
+ </define>
+
+ <define name='keyivgen'>
+ <attribute name='name'>
+ <text/>
+ </attribute>
+ <optional>
+ <attribute name='hash'>
+ <text/>
+ </attribute>
+ </optional>
+ </define>
+
</grammar>
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 9443281..1c3bdc4 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -7857,6 +7857,17 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
def->startupPolicy = val;
}
+ if (encryption) {
+ if (encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
+ encryption->cipher.name) {
+
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("supplying the <cipher> for a domain is "
+ "unnecessary"));
+ goto error;
+ }
+ }
+
def->dst = target;
target = NULL;
def->src->auth = authdef;
diff --git a/src/util/virstorageencryption.c b/src/util/virstorageencryption.c
index 8575416..6544123 100644
--- a/src/util/virstorageencryption.c
+++ b/src/util/virstorageencryption.c
@@ -35,6 +35,7 @@
#include "viruuid.h"
#include "virfile.h"
#include "virsecret.h"
+#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
@@ -46,6 +47,15 @@ VIR_ENUM_IMPL(virStorageEncryptionFormat,
"default", "qcow", "luks")
static void
+virStorageEncryptionInfoDefFree(virStorageEncryptionInfoDefPtr def)
+{
+ VIR_FREE(def->name);
+ VIR_FREE(def->mode);
+ VIR_FREE(def->hash);
+}
+
+
+static void
virStorageEncryptionSecretFree(virStorageEncryptionSecretPtr secret)
{
if (!secret)
@@ -63,6 +73,8 @@ virStorageEncryptionFree(virStorageEncryptionPtr enc)
for (i = 0; i < enc->nsecrets; i++)
virStorageEncryptionSecretFree(enc->secrets[i]);
+ virStorageEncryptionInfoDefFree(&enc->cipher);
+ virStorageEncryptionInfoDefFree(&enc->ivgen);
VIR_FREE(enc->secrets);
VIR_FREE(enc);
}
@@ -80,6 +92,20 @@ virStorageEncryptionSecretCopy(const virStorageEncryptionSecret *src)
return ret;
}
+
+static int
+virStorageEncryptionInfoDefCopy(const virStorageEncryptionInfoDef *src,
+ virStorageEncryptionInfoDefPtr dst)
+{
+ if (VIR_STRDUP(dst->name, src->name) < 0 ||
+ VIR_STRDUP(dst->mode, src->mode) < 0 ||
+ VIR_STRDUP(dst->hash, src->hash) < 0)
+ return -1;
+
+ return 0;
+}
+
+
virStorageEncryptionPtr
virStorageEncryptionCopy(const virStorageEncryption *src)
{
@@ -100,6 +126,12 @@ virStorageEncryptionCopy(const virStorageEncryption *src)
goto error;
}
+ if (virStorageEncryptionInfoDefCopy(&src->cipher, &ret->cipher) <
0)
+ goto error;
+
+ if (virStorageEncryptionInfoDefCopy(&src->ivgen, &ret->ivgen) < 0)
+ goto error;
+
return ret;
error:
@@ -153,6 +185,51 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt,
return NULL;
}
+
+static int
+virStorageEncryptionInfoParse(xmlNodePtr info_node,
+ virStorageEncryptionInfoDefPtr info,
+ bool require_size)
+{
+ int ret = -1;
+ char *size_str = NULL;
+
+ if (!(info->name = virXMLPropString(info_node, "name"))) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("missing secret key info name string"));
+ goto cleanup;
+ }
+
+ /* Check for a size string - it's required for cipher, but not for ivgen
+ * if provided for ivgen then just ignore */
+ if (require_size) {
+ if ((size_str = virXMLPropString(info_node, "size")) &&
+ virStrToLong_uip(size_str, NULL, 10, &info->size) < 0) {
+ virReportError(VIR_ERR_XML_ERROR,
+ _("cannot parse secret key info size string
'%s'"),
+ size_str);
+ goto cleanup;
+ }
+
+ if (!size_str) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("secret key info missing 'size'
attribute"));
+ goto cleanup;
+ }
+ }
+
+ /* Optional */
+ info->mode = virXMLPropString(info_node, "mode");
+ info->hash = virXMLPropString(info_node, "hash");
+
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(size_str);
+ return ret;
+}
+
+
static virStorageEncryptionPtr
virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
{
@@ -196,6 +273,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
VIR_FREE(nodes);
}
+ if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
+ xmlNodePtr tmpnode;
+
+ if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) {
+ if (virStorageEncryptionInfoParse(tmpnode, &ret->cipher, true) <
0)
+ goto cleanup;
+ }
+
+ if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) {
+ /* If no cipher node, then fail */
+ if (!ret->cipher.name) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("missing storage encryption cipher"));
+ goto cleanup;
+ }
+
+ if (virStorageEncryptionInfoParse(tmpnode, &ret->ivgen, false) <
0)
+ goto cleanup;
+ }
+ }
+
+
return ret;
cleanup:
@@ -250,6 +349,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf,
return 0;
}
+
+static void
+virStorageEncryptionInfoDefFormat(virBufferPtr buf,
+ const virStorageEncryption *enc)
+{
+ virBufferAsprintf(buf, "<cipher name='%s' size='%u'",
+ enc->cipher.name, enc->cipher.size);
+ if (enc->cipher.mode)
+ virBufferAsprintf(buf, " mode='%s'", enc->cipher.mode);
+ if (enc->cipher.hash)
+ virBufferAsprintf(buf, " hash='%s'", enc->cipher.hash);
+ virBufferAddLit(buf, "/>\n");
+
+ if (enc->ivgen.name) {
+ virBufferAsprintf(buf, "<ivgen name='%s'",
enc->ivgen.name);
+ if (enc->ivgen.hash)
+ virBufferAsprintf(buf, " hash='%s'", enc->ivgen.hash);
+ virBufferAddLit(buf, "/>\n");
+ }
+}
+
+
int
virStorageEncryptionFormat(virBufferPtr buf,
virStorageEncryptionPtr enc)
@@ -270,6 +391,9 @@ virStorageEncryptionFormat(virBufferPtr buf,
return -1;
}
+ if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
enc->cipher.name)
+ virStorageEncryptionInfoDefFormat(buf, enc);
+
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</encryption>\n");
diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h
index 5e1be3b..34bba03 100644
--- a/src/util/virstorageencryption.h
+++ b/src/util/virstorageencryption.h
@@ -44,6 +44,16 @@ struct _virStorageEncryptionSecret {
virSecretLookupTypeDef seclookupdef;
};
+/* For a key type it's possible to dictate the cipher and if necessary iv */
+typedef struct _virStorageEncryptionInfoDef virStorageEncryptionInfoDef;
+typedef virStorageEncryptionInfoDef *virStorageEncryptionInfoDefPtr;
+struct _virStorageEncryptionInfoDef {
+ unsigned int size;
+ char *name;
+ char *mode;
+ char *hash;
+};
+
typedef enum {
/* "default" is only valid for volume creation */
VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT = 0,
@@ -61,6 +71,9 @@ struct _virStorageEncryption {
size_t nsecrets;
virStorageEncryptionSecretPtr *secrets;
+
+ virStorageEncryptionInfoDef cipher;
+ virStorageEncryptionInfoDef ivgen;
};
virStorageEncryptionPtr virStorageEncryptionCopy(const virStorageEncryption *src)
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
new file mode 100644
index 0000000..00399cf
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
@@ -0,0 +1,41 @@
+<domain type='qemu'>
+ <name>encryptdisk</name>
+ <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid>
+ <memory unit='KiB'>1048576</memory>
+ <currentMemory unit='KiB'>524288</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc-i440fx-2.1'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu</emulator>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='luks'/>
+ <source file='/storage/guest_disks/encryptdisk'/>
+ <target dev='vda' bus='virtio'/>
+ <encryption format='luks'>
+ <secret type='passphrase'
uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/>
+ </encryption>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x0'/>
+ </disk>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='luks'/>
+ <source file='/storage/guest_disks/encryptdisk2'/>
+ <target dev='vdb' bus='virtio'/>
+ <encryption format='luks'>
+ <secret type='passphrase' usage='mycluster_myname'/>
+ </encryption>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x0'/>
+ </disk>
+ <controller type='usb' index='0'/>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
new file mode 100644
index 0000000..9ce15c0
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
@@ -0,0 +1,45 @@
+<domain type='qemu'>
+ <name>encryptdisk</name>
+ <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid>
+ <memory unit='KiB'>1048576</memory>
+ <currentMemory unit='KiB'>524288</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc-i440fx-2.1'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu</emulator>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='luks'/>
+ <source file='/storage/guest_disks/encryptdisk'/>
+ <target dev='vda' bus='virtio'/>
+ <encryption format='luks'>
+ <secret type='passphrase'
uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/>
+ </encryption>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x0'/>
+ </disk>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='luks'/>
+ <source file='/storage/guest_disks/encryptdisk2'/>
+ <target dev='vdb' bus='virtio'/>
+ <encryption format='luks'>
+ <secret type='passphrase' usage='mycluster_myname'/>
+ </encryption>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x05' function='0x0'/>
+ </disk>
+ <controller type='usb' index='0'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
+ </memballoon>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index 6c566b3..2056c03 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -503,6 +503,7 @@ mymain(void)
DO_TEST("encrypted-disk");
DO_TEST("encrypted-disk-usage");
DO_TEST("luks-disks");
+ DO_TEST("luks-disk-cipher");
DO_TEST("memtune");
DO_TEST("memtune-unlimited");
DO_TEST("blkiotune");
diff --git a/tests/storagevolxml2xmlin/vol-luks-cipher.xml
b/tests/storagevolxml2xmlin/vol-luks-cipher.xml
new file mode 100644
index 0000000..009246f
--- /dev/null
+++ b/tests/storagevolxml2xmlin/vol-luks-cipher.xml
@@ -0,0 +1,23 @@
+<volume>
+ <name>LuksDemo.img</name>
+ <key>/var/lib/libvirt/images/LuksDemo.img</key>
+ <source>
+ </source>
+ <capacity unit="G">5</capacity>
+ <allocation>294912</allocation>
+ <target>
+ <path>/var/lib/libvirt/images/LuksDemo.img</path>
+ <format type='luks'/>
+ <permissions>
+ <mode>0644</mode>
+ <owner>0</owner>
+ <group>0</group>
+ <label>unconfined_u:object_r:virt_image_t:s0</label>
+ </permissions>
+ <encryption format='luks'>
+ <secret type='passphrase' usage='mumblyfratz'/>
+ <cipher name='serpent' size='256' mode='cbc'
hash='sha256'/>
+ <ivgen name='plain64' hash='sha256'/>
+ </encryption>
+ </target>
+</volume>
diff --git a/tests/storagevolxml2xmlout/vol-luks-cipher.xml
b/tests/storagevolxml2xmlout/vol-luks-cipher.xml
new file mode 100644
index 0000000..9014849
--- /dev/null
+++ b/tests/storagevolxml2xmlout/vol-luks-cipher.xml
@@ -0,0 +1,23 @@
+<volume type='file'>
+ <name>LuksDemo.img</name>
+ <key>/var/lib/libvirt/images/LuksDemo.img</key>
+ <source>
+ </source>
+ <capacity unit='bytes'>5368709120</capacity>
+ <allocation unit='bytes'>294912</allocation>
+ <target>
+ <path>/var/lib/libvirt/images/LuksDemo.img</path>
+ <format type='luks'/>
+ <permissions>
+ <mode>0644</mode>
+ <owner>0</owner>
+ <group>0</group>
+ <label>unconfined_u:object_r:virt_image_t:s0</label>
+ </permissions>
+ <encryption format='luks'>
+ <secret type='passphrase' usage='mumblyfratz'/>
+ <cipher name='serpent' size='256' mode='cbc'
hash='sha256'/>
+ <ivgen name='plain64' hash='sha256'/>
+ </encryption>
+ </target>
+</volume>
diff --git a/tests/storagevolxml2xmltest.c b/tests/storagevolxml2xmltest.c
index a36a706..db82bea 100644
--- a/tests/storagevolxml2xmltest.c
+++ b/tests/storagevolxml2xmltest.c
@@ -106,6 +106,7 @@ mymain(void)
DO_TEST("pool-dir", "vol-qcow2-0.10-lazy");
DO_TEST("pool-dir", "vol-qcow2-nobacking");
DO_TEST("pool-dir", "vol-luks");
+ DO_TEST("pool-dir", "vol-luks-cipher");
DO_TEST("pool-disk", "vol-partition");
DO_TEST("pool-logical", "vol-logical");
DO_TEST("pool-logical", "vol-logical-backing");
--
2.5.5