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(a)linux.vnet.ibm.com>
---
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">