[libvirt] [PATCH v3 05/10] encryption: Add <cipher> and <ivgen> to encryption

John Ferlan jferlan at redhat.com
Fri Jun 24 20:53:34 UTC 2016


For a luks device, allow the configuration of a specific cipher to be
used for encrypting the volume.

Signed-off-by: John Ferlan <jferlan at redhat.com>
---
 docs/formatstorageencryption.html.in               |  83 ++++++++++++-
 docs/schemas/storagecommon.rng                     |  44 ++++++-
 src/conf/domain_conf.c                             |  11 ++
 src/util/virstorageencryption.c                    | 138 +++++++++++++++++++++
 src/util/virstorageencryption.h                    |  14 +++
 .../qemuxml2argv-luks-disk-cipher.xml              |  45 +++++++
 .../qemuxml2xmlout-luks-disk-cipher.xml            |   1 +
 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, 378 insertions(+), 6 deletions(-)
 create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
 create mode 120000 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 be73054..0f8401e 100644
--- a/docs/formatstorageencryption.html.in
+++ b/docs/formatstorageencryption.html.in
@@ -68,6 +68,60 @@
       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
+      used by the storage driver backend when using qemu-img to create
+      the volume 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 storage driver
+            implementation 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>
 
@@ -81,9 +135,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>name</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'>
@@ -91,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='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 79d15c8..8d3a132 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->encinfo.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..754444d 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,17 @@ VIR_ENUM_IMPL(virStorageEncryptionFormat,
               "default", "qcow", "luks")
 
 static void
+virStorageEncryptionInfoDefFree(virStorageEncryptionInfoDefPtr def)
+{
+    VIR_FREE(def->cipher_name);
+    VIR_FREE(def->cipher_mode);
+    VIR_FREE(def->cipher_hash);
+    VIR_FREE(def->ivgen_name);
+    VIR_FREE(def->ivgen_hash);
+}
+
+
+static void
 virStorageEncryptionSecretFree(virStorageEncryptionSecretPtr secret)
 {
     if (!secret)
@@ -63,6 +75,7 @@ virStorageEncryptionFree(virStorageEncryptionPtr enc)
 
     for (i = 0; i < enc->nsecrets; i++)
         virStorageEncryptionSecretFree(enc->secrets[i]);
+    virStorageEncryptionInfoDefFree(&enc->encinfo);
     VIR_FREE(enc->secrets);
     VIR_FREE(enc);
 }
@@ -80,6 +93,23 @@ virStorageEncryptionSecretCopy(const virStorageEncryptionSecret *src)
     return ret;
 }
 
+
+static int
+virStorageEncryptionInfoDefCopy(const virStorageEncryptionInfoDef *src,
+                                virStorageEncryptionInfoDefPtr dst)
+{
+    dst->cipher_size = src->cipher_size;
+    if (VIR_STRDUP(dst->cipher_name, src->cipher_name) < 0 ||
+        VIR_STRDUP(dst->cipher_mode, src->cipher_mode) < 0 ||
+        VIR_STRDUP(dst->cipher_hash, src->cipher_hash) < 0 ||
+        VIR_STRDUP(dst->ivgen_name, src->ivgen_name) < 0 ||
+        VIR_STRDUP(dst->ivgen_hash, src->ivgen_hash) < 0)
+        return -1;
+
+    return 0;
+}
+
+
 virStorageEncryptionPtr
 virStorageEncryptionCopy(const virStorageEncryption *src)
 {
@@ -100,6 +130,9 @@ virStorageEncryptionCopy(const virStorageEncryption *src)
             goto error;
     }
 
+    if (virStorageEncryptionInfoDefCopy(&src->encinfo, &ret->encinfo) < 0)
+        goto error;
+
     return ret;
 
  error:
@@ -153,6 +186,63 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt,
     return NULL;
 }
 
+
+static int
+virStorageEncryptionInfoParseCipher(xmlNodePtr info_node,
+                                    virStorageEncryptionInfoDefPtr info)
+{
+    int ret = -1;
+    char *size_str = NULL;
+
+    if (!(info->cipher_name = virXMLPropString(info_node, "name"))) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("missing cipher info name string"));
+        goto cleanup;
+    }
+
+    if ((size_str = virXMLPropString(info_node, "size")) &&
+        virStrToLong_uip(size_str, NULL, 10, &info->cipher_size) < 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("cannot parse cipher info size string '%s'"),
+                       size_str);
+        goto cleanup;
+    }
+
+    if (!size_str) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("cipher info missing 'size' attribute"));
+        goto cleanup;
+    }
+
+    /* Optional */
+    info->cipher_mode = virXMLPropString(info_node, "mode");
+    info->cipher_hash = virXMLPropString(info_node, "hash");
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(size_str);
+    return ret;
+}
+
+
+static int
+virStorageEncryptionInfoParseIvgen(xmlNodePtr info_node,
+                                   virStorageEncryptionInfoDefPtr info)
+{
+    if (!(info->ivgen_name = virXMLPropString(info_node, "name"))) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("missing ivgen info name string"));
+        return -1;
+    }
+
+    /* Optional */
+    info->ivgen_hash = virXMLPropString(info_node, "hash");
+
+    return 0;
+}
+
+
 static virStorageEncryptionPtr
 virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
 {
@@ -196,6 +286,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
         VIR_FREE(nodes);
     }
 
+    if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
+        xmlNodePtr tmpnode;
+
+        if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) {
+            if (virStorageEncryptionInfoParseCipher(tmpnode, &ret->encinfo) < 0)
+                goto cleanup;
+        }
+
+        if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) {
+            /* If no cipher node, then fail */
+            if (!ret->encinfo.cipher_name) {
+                virReportError(VIR_ERR_XML_ERROR, "%s",
+                                _("missing storage encryption cipher"));
+                goto cleanup;
+            }
+
+            if (virStorageEncryptionInfoParseIvgen(tmpnode, &ret->encinfo) < 0)
+                goto cleanup;
+        }
+    }
+
+
     return ret;
 
  cleanup:
@@ -250,6 +362,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf,
     return 0;
 }
 
+
+static void
+virStorageEncryptionInfoDefFormat(virBufferPtr buf,
+                                  const virStorageEncryptionInfoDef *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 +404,10 @@ virStorageEncryptionFormat(virBufferPtr buf,
             return -1;
     }
 
+    if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
+        enc->encinfo.cipher_name)
+        virStorageEncryptionInfoDefFormat(buf, &enc->encinfo);
+
     virBufferAdjustIndent(buf, -2);
     virBufferAddLit(buf, "</encryption>\n");
 
diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h
index 5e1be3b..fa439fb 100644
--- a/src/util/virstorageencryption.h
+++ b/src/util/virstorageencryption.h
@@ -44,6 +44,18 @@ struct _virStorageEncryptionSecret {
     virSecretLookupTypeDef seclookupdef;
 };
 
+/* It's possible to dictate the cipher and if necessary iv */
+typedef struct _virStorageEncryptionInfoDef virStorageEncryptionInfoDef;
+typedef virStorageEncryptionInfoDef *virStorageEncryptionInfoDefPtr;
+struct _virStorageEncryptionInfoDef {
+    unsigned int cipher_size;
+    char *cipher_name;
+    char *cipher_mode;
+    char *cipher_hash;
+    char *ivgen_name;
+    char *ivgen_hash;
+};
+
 typedef enum {
     /* "default" is only valid for volume creation */
     VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT = 0,
@@ -61,6 +73,8 @@ struct _virStorageEncryption {
 
     size_t nsecrets;
     virStorageEncryptionSecretPtr *secrets;
+
+    virStorageEncryptionInfoDef encinfo;
 };
 
 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..9ce15c0
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-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/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
new file mode 120000
index 0000000..fa55233
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml
@@ -0,0 +1 @@
+../qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml
\ No newline at end of file
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




More information about the libvir-list mailing list