For a luks device, allow the configuration of a specific cipher to be used for encrypting the volume. Signed-off-by: John Ferlan <jferlan@xxxxxxxxxx> --- docs/formatstorageencryption.html.in | 78 ++++++++++++- docs/schemas/storagecommon.rng | 44 ++++++- src/conf/domain_conf.c | 11 ++ src/util/virstorageencryption.c | 126 +++++++++++++++++++++ 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, 402 insertions(+), 4 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 ae2e815..dd2f990 100644 --- a/docs/formatstorageencryption.html.in +++ b/docs/formatstorageencryption.html.in @@ -71,6 +71,58 @@ be used as the key to decrypt the volume. <span class="since">Since 1.3.6</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 master key 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 key. 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,7 +136,11 @@ </encryption></pre> <p> - Here is a simple example, specifying use of the <code>luks</code> format: + Assuming a <a href="formatsecret.html#luksUsageType"> + <code>luks secret</code></a> is already defined, the following is + 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'> @@ -92,5 +148,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='key' 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 44d4315..b6e8de4 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> @@ -137,4 +147,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 10e61da..27f71a5 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -7786,6 +7786,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 9cca3e8..608a57b 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) @@ -269,6 +390,11 @@ virStorageEncryptionFormat(virBufferPtr buf, return -1; } + virBufferAdjustIndent(buf, 2); + if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && enc->cipher.name) + virStorageEncryptionInfoDefFormat(buf, enc); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</encryption>\n"); return 0; diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h index b966770..45c5260 100644 --- a/src/util/virstorageencryption.h +++ b/src/util/virstorageencryption.h @@ -45,6 +45,16 @@ struct _virStorageEncryptionSecret { virSecretLookupTypeDef secdef; }; +/* 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, @@ -62,6 +72,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..29ea38a --- /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='key' 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='key' 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..c04506a --- /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='key' 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='key' 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 fed8351..147eff8 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..6c7ad82 --- /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='key' 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..22a7383 --- /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='key' 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list