[RFC PATCH V1 4/5] tpm: Add support for encrypted TPM block storage

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds support for encrypted block storage for the TPM's
block storage. Specifically it

- extends the XML parser and generator to handle the extended TPM XML
  referring to a 'secret'. It can handle the following XML

    <tpm type='built-in'>
      <storage>
        <encryption format='qcow'>
          <secret type='passphrase' uuid='13ea49f7-2553-7308-5168-e337ade36232'/>
        </encryption>
      </storage>
    </tpm>

  For this it relies on existing functions originally written for storage
  encryption.

- extends the code creating the block storage file to create an encrypted
  QCoW2

- extends the montior to pass the encryption key to Qemu so it can access
  the encrypted data;

- handles the secret associated with the TPM storage; much of the code around
  the handling of the secret has been recycled from existing storage
  handling code

Signed-off-by: Stefan Berger <stefanb@xxxxxxxxxxxxxxxxxx>

---
 docs/schemas/domain.rng      |    3 
 src/conf/domain_conf.c       |   53 ++++++++++++
 src/conf/domain_conf.h       |    1 
 src/qemu/qemu_command.c      |   24 ++++-
 src/qemu/qemu_driver.c       |    2 
 src/qemu/qemu_monitor.c      |   16 +++
 src/qemu/qemu_monitor.h      |   13 +++
 src/qemu/qemu_monitor_text.c |    6 +
 src/qemu/qemu_process.c      |  176 +++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 285 insertions(+), 9 deletions(-)

Index: libvirt-acl/src/qemu/qemu_monitor.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_monitor.c
+++ libvirt-acl/src/qemu/qemu_monitor.c
@@ -797,6 +797,22 @@ int qemuMonitorGetDiskSecret(qemuMonitor
 }
 
 
+int qemuMonitorGetTPMSecret(qemuMonitorPtr mon,
+                            virConnectPtr conn,
+                            const char *path,
+                            char **secret,
+                            size_t *secretLen)
+{
+    int ret = -1;
+    *secret = NULL;
+    *secretLen = 0;
+
+    QEMU_MONITOR_CALLBACK(mon, ret, tpmSecretLookup, conn, mon->vm,
+                          path, secret, secretLen);
+    return ret;
+}
+
+
 int qemuMonitorEmitShutdown(qemuMonitorPtr mon)
 {
     int ret = -1;
Index: libvirt-acl/src/qemu/qemu_monitor.h
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_monitor.h
+++ libvirt-acl/src/qemu/qemu_monitor.h
@@ -81,6 +81,13 @@ struct _qemuMonitorCallbacks {
                             char **secret,
                             size_t *secretLen);
 
+    int (*tpmSecretLookup)(qemuMonitorPtr mon,
+                           virConnectPtr conn,
+                           virDomainObjPtr vm,
+                           const char *path,
+                           char **secret,
+                           size_t *secretLen);
+
     int (*domainShutdown)(qemuMonitorPtr mon,
                           virDomainObjPtr vm);
     int (*domainReset)(qemuMonitorPtr mon,
@@ -152,6 +159,12 @@ int qemuMonitorGetDiskSecret(qemuMonitor
                              char **secret,
                              size_t *secretLen);
 
+int qemuMonitorGetTPMSecret(qemuMonitorPtr mon,
+                            virConnectPtr conn,
+                            const char *path,
+                            char **secret,
+                            size_t *secretLen);
+
 int qemuMonitorEmitShutdown(qemuMonitorPtr mon);
 int qemuMonitorEmitReset(qemuMonitorPtr mon);
 int qemuMonitorEmitPowerdown(qemuMonitorPtr mon);
Index: libvirt-acl/src/qemu/qemu_monitor_text.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_monitor_text.c
+++ libvirt-acl/src/qemu/qemu_monitor_text.c
@@ -315,6 +315,12 @@ qemuMonitorSendDiskPassphrase(qemuMonito
                                    path,
                                    &passphrase,
                                    &passphrase_len);
+    if (res < 0)
+        res = qemuMonitorGetTPMSecret(mon,
+                                      conn,
+                                      path,
+                                      &passphrase,
+                                      &passphrase_len);
     VIR_FREE(path);
     if (res < 0)
         return -1;
Index: libvirt-acl/src/qemu/qemu_driver.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_driver.c
+++ libvirt-acl/src/qemu/qemu_driver.c
@@ -187,6 +187,8 @@ qemuAutostartDomains(struct qemud_driver
         virConnectClose(conn);
 }
 
+
+
 static int
 qemuSecurityInit(struct qemud_driver *driver)
 {
Index: libvirt-acl/src/conf/domain_conf.h
===================================================================
--- libvirt-acl.orig/src/conf/domain_conf.h
+++ libvirt-acl/src/conf/domain_conf.h
@@ -1024,6 +1024,7 @@ struct _virDomainTPMDef {
     union {
         struct {
             char *storage;
+            virStorageEncryptionPtr encryption;
         } builtin;
     } data;
 };
Index: libvirt-acl/src/conf/domain_conf.c
===================================================================
--- libvirt-acl.orig/src/conf/domain_conf.c
+++ libvirt-acl/src/conf/domain_conf.c
@@ -790,6 +790,7 @@ void virDomainTPMDefFree(virDomainTPMDef
     switch (def->type) {
     case VIR_DOMAIN_TPM_TYPE_BUILTIN:
         VIR_FREE(def->data.builtin.storage);
+        virStorageEncryptionFree(def->data.builtin.encryption);
         break;
     default:
         break;
@@ -3513,7 +3514,12 @@ virDomainTPMGetStorageFilename(virDomain
  * The XML we're dealing with looks like
  *
  * <tpm type="built-in">
- *    <storage file='path/to/QCoW2/state/file' />
+ *    <storage file='path_to_state_file' >
+ *      <encryption format='qcow'>
+ *        <secret type='passphrase'
+ *                uuid='13ea49f7-2553-7308-5168-e337ade36232'/>
+ *      </encryption>
+ *    </storage>
  * </tpm>
  * The 'storage' node is optional. If none is provided,
  * libvirt is going to create the necessary storage using
@@ -3522,12 +3528,13 @@ virDomainTPMGetStorageFilename(virDomain
  */
 static virDomainTPMDefPtr
 virDomainTPMDefParseXML(xmlNodePtr node) {
-    xmlNodePtr cur;
+    xmlNodePtr cur, stor;
     char *type = NULL;
     char *path = NULL;
     virDomainTPMDefPtr def;
     char *tpmStateDir = NULL;
     int err;
+    virStorageEncryptionPtr encryption = NULL;
 
     if (VIR_ALLOC(def) < 0) {
         virReportOOMError();
@@ -3553,6 +3560,30 @@ virDomainTPMDefParseXML(xmlNodePtr node)
                 default:
                     break;
                 }
+                stor = cur->children;
+                while (stor != NULL) {
+                    if (stor->type == XML_ELEMENT_NODE) {
+                        if (encryption == NULL &&
+                            xmlStrEqual(stor->name, BAD_CAST "encryption")) {
+                            encryption = virStorageEncryptionParseNode(
+                                                       cur->doc,
+                                                       stor);
+                            if (encryption == NULL)
+                                goto error;
+
+                            if (encryption->format !=
+                                VIR_STORAGE_ENCRYPTION_FORMAT_QCOW) {
+                                virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+                                         _("encryption format type must be "
+                                           "'qcow'"));
+                                virStorageEncryptionFree(encryption);
+                                encryption = NULL;
+                                goto error;
+                            }
+                        }
+                    }
+                    stor = stor->next;
+                }
             }
         }
         cur = cur->next;
@@ -3577,6 +3608,8 @@ virDomainTPMDefParseXML(xmlNodePtr node)
     case VIR_DOMAIN_TPM_TYPE_BUILTIN:
         def->data.builtin.storage = path;
         path = NULL;
+        def->data.builtin.encryption = encryption;
+        encryption = NULL;
         break;
 
     default:
@@ -3587,6 +3620,7 @@ cleanup:
     VIR_FREE(type);
     VIR_FREE(path);
     VIR_FREE(tpmStateDir);
+    virStorageEncryptionFree(encryption);
 
     return def;
 
@@ -7546,6 +7580,7 @@ virDomainTPMDefFormat(virBufferPtr buf,
                       virDomainTPMDefPtr def,
                       const char *name)
 {
+    virStorageEncryptionPtr enc;
     const char *type = virDomainTPMTypeToString(def->type);
 
     if (!type) {
@@ -7558,9 +7593,21 @@ virDomainTPMDefFormat(virBufferPtr buf,
                       name, type);
     switch (def->type) {
     case VIR_DOMAIN_TPM_TYPE_BUILTIN:
+        enc = def->data.builtin.encryption;
+
+        virBufferVSprintf(buf, "      <storage");
+
         if (def->data.builtin.storage)
-            virBufferEscapeString(buf, "      <storage file='%s'/>\n",
+            virBufferEscapeString(buf, " file='%s'",
                                   def->data.builtin.storage);
+
+        virBufferVSprintf(buf, "%s>\n", enc ? "" : "/");
+
+        if (enc) {
+            if (virStorageEncryptionFormat(buf, enc, 8) < 0)
+                return -1;
+            virBufferVSprintf(    buf, "      </storage>\n");
+        }
         break;
 
         default:
Index: libvirt-acl/src/qemu/qemu_command.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_command.c
+++ libvirt-acl/src/qemu/qemu_command.c
@@ -912,7 +912,8 @@ int qemuDomainPCIAddressSetNextAddr(qemu
 
 
 static int qemudTPMCreateBlockStore(const char *tpmstorefile,
-                                    const char *qemu_cmd)
+                                    const char *qemu_cmd,
+                                    bool encrypted)
 {
     int rc = 0;
     struct stat statbuf;
@@ -920,12 +921,13 @@ static int qemudTPMCreateBlockStore(cons
     int status;
     char filesize[10];
     unsigned int bssize;
-    const char *argv[] = { qemuimg, "create", "-f", "qcow2", tpmstorefile,
+    const char *argv[] = { qemuimg, "create", "-f", "qcow2",
+                           "-o", "encryption", tpmstorefile,
                            filesize, NULL};
 
     if (stat(tpmstorefile, &statbuf) == -1 || statbuf.st_size == 0) {
         if (errno == ENOENT || statbuf.st_size == 0) {
-            /* determine size of qcow2 */
+            /* determine necessary size; qemu -tpm ? tells us */
             if (qemuCapsGetTPMBuiltinBSSize(qemu_cmd,
                                             &bssize) < 0) {
                 qemuReportError(VIR_ERR_INTERNAL_ERROR,
@@ -944,9 +946,16 @@ static int qemudTPMCreateBlockStore(cons
             argv[0] = qemuimg;
             snprintf(filesize, sizeof(filesize),"%dk", bssize);
 
-            VIR_DEBUG("Creating BS file %s of size %dkb\n",
+            VIR_DEBUG("Creating BS file %s of size %dkb; encrypted: %d\n",
                       tpmstorefile,
-                      bssize);
+                      bssize,
+                      encrypted);
+
+            if (!encrypted) {
+                argv[4] = argv[6];
+                argv[5] = argv[7];
+                argv[6] = NULL;
+            }
 
             if (virRun(argv, &status) != 0 || status != 0) {
                 qemuReportError(VIR_ERR_INTERNAL_ERROR,
@@ -985,7 +994,10 @@ static char * qemudBuildCommandLineTPMDe
             rc = -1;
             goto err_exit;
         }
-        if (qemudTPMCreateBlockStore(tmp, qemu_cmd)) {
+        if (qemudTPMCreateBlockStore(tmp, qemu_cmd,
+                                     tpm->data.builtin.encryption
+                                      ? true
+                                      : false)) {
             rc = -1;
             goto err_exit;
         }
Index: libvirt-acl/src/qemu/qemu_process.c
===================================================================
--- libvirt-acl.orig/src/qemu/qemu_process.c
+++ libvirt-acl/src/qemu/qemu_process.c
@@ -297,6 +297,144 @@ cleanup:
 }
 
 
+static virDomainTPMDefPtr
+findDomainTPMByPath(virDomainObjPtr vm,
+                    const char *path)
+{
+    virDomainTPMDefPtr res = vm->def->tpm;
+#if 0
+    char *tmp;
+
+    if (res) {
+        tmp = virDomainTPMGetStorageFilename(res, vm->def->uuid);
+        if (!STREQ(tmp, path))
+            res = NULL;
+
+        VIR_FREE(tmp);
+    }
+#else
+    (void)path;
+#endif
+
+    return res;
+}
+
+static int
+getTPMPassphrase(virConnectPtr conn,
+                 virDomainTPMDefPtr tpm,
+                 char **secretRet,
+                 size_t *secretLen)
+{
+    virSecretPtr secret;
+    char *passphrase;
+    unsigned char *data;
+    size_t size;
+    int ret = -1;
+    virStorageEncryptionPtr enc;
+
+    switch (tpm->type) {
+    case  VIR_DOMAIN_TPM_TYPE_BUILTIN:
+        if (!tpm->data.builtin.encryption) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("tpm does not have any encryption information"));
+            return -1;
+        }
+        enc = tpm->data.builtin.encryption;
+        break;
+
+    default:
+        return -1;
+    }
+
+    if (!conn) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("cannot find secrets without a connection"));
+        goto cleanup;
+    }
+
+    if (conn->secretDriver == NULL ||
+        conn->secretDriver->lookupByUUID == NULL ||
+        conn->secretDriver->getValue == NULL) {
+        qemuReportError(VIR_ERR_NO_SUPPORT, "%s",
+                        _("secret storage not supported"));
+        goto cleanup;
+    }
+
+    if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW ||
+        enc->nsecrets != 1 ||
+        enc->secrets[0]->type !=
+        VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) {
+        qemuReportError(VIR_ERR_XML_ERROR,
+                        _("invalid <encryption>"));
+        goto cleanup;
+    }
+
+    secret = conn->secretDriver->lookupByUUID(conn,
+                                              enc->secrets[0]->uuid);
+    if (secret == NULL)
+        goto cleanup;
+    data = conn->secretDriver->getValue(secret, &size,
+                                        VIR_SECRET_GET_VALUE_INTERNAL_CALL);
+    virUnrefSecret(secret);
+    if (data == NULL)
+        goto cleanup;
+
+    if (memchr(data, '\0', size) != NULL) {
+        memset(data, 0, size);
+        VIR_FREE(data);
+        qemuReportError(VIR_ERR_XML_ERROR,
+                        _("format='qcow' passphrase for %s must not contain a "
+                          "'\\0'"), "TPM");
+        goto cleanup;
+    }
+
+    if (VIR_ALLOC_N(passphrase, size + 1) < 0) {
+        memset(data, 0, size);
+        VIR_FREE(data);
+        virReportOOMError();
+        goto cleanup;
+    }
+    memcpy(passphrase, data, size);
+    passphrase[size] = '\0';
+
+    memset(data, 0, size);
+    VIR_FREE(data);
+
+    *secretRet = passphrase;
+    *secretLen = size;
+
+    ret = 0;
+
+cleanup:
+    return ret;
+}
+
+static int
+qemuProcessFindTPMPassphrase(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
+                             virConnectPtr conn,
+                             virDomainObjPtr vm,
+                             const char *path,
+                             char **secretRet,
+                             size_t *secretLen)
+{
+    virDomainTPMDefPtr tpm;
+    int ret = -1;
+
+    virDomainObjLock(vm);
+    tpm = findDomainTPMByPath(vm, path);
+
+    if (!tpm)
+        goto cleanup;
+
+    ret = getTPMPassphrase(conn, tpm, secretRet, secretLen);
+
+cleanup:
+    virDomainObjUnlock(vm);
+    return ret;
+}
+
+
+
 static int
 qemuProcessHandleReset(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
                        virDomainObjPtr vm)
@@ -613,6 +751,7 @@ static qemuMonitorCallbacks monitorCallb
     .destroy = qemuProcessHandleMonitorDestroy,
     .eofNotify = qemuProcessHandleMonitorEOF,
     .diskSecretLookup = qemuProcessFindVolumeQcowPassphrase,
+    .tpmSecretLookup = qemuProcessFindTPMPassphrase,
     .domainShutdown = qemuProcessHandleShutdown,
     .domainStop = qemuProcessHandleStop,
     .domainReset = qemuProcessHandleReset,
@@ -1268,6 +1407,43 @@ qemuProcessInitPasswords(virConnectPtr c
         }
     }
 
+    while (qemuCapsGet(qemuCaps, QEMU_CAPS_TPM)) {
+        virDomainTPMDefPtr tpm = vm->def->tpm;
+        virStorageEncryptionPtr enc = NULL;
+        char *secret;
+        size_t secretLen;
+
+        if (!tpm)
+            break;
+
+        switch (tpm->type) {
+        case VIR_DOMAIN_TPM_TYPE_BUILTIN:
+            enc = tpm->data.builtin.encryption;
+            break;
+
+        default:
+            break;
+        }
+
+        if (!enc)
+            break;
+
+        if (getTPMPassphrase(conn, tpm,
+                             &secret, &secretLen) < 0)
+            goto cleanup;
+
+        qemuDomainObjEnterMonitorWithDriver(driver, vm);
+        ret = qemuMonitorSetDrivePassphrase(priv->mon,
+                                            "vtpm-nvram",
+                                            secret);
+        VIR_FREE(secret);
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+        if (ret < 0)
+            goto cleanup;
+
+        break;
+    }
+
 cleanup:
     return ret;
 }
Index: libvirt-acl/docs/schemas/domain.rng
===================================================================
--- libvirt-acl.orig/docs/schemas/domain.rng
+++ libvirt-acl/docs/schemas/domain.rng
@@ -1731,6 +1731,9 @@
           <ref name="filePath"/>
         </attribute>
       </optional>
+      <optional>
+        <ref name="encryption"/>
+      </optional>
     </element>
   </define>
   <define name="tpm">

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list


[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]