New APIs:
qemuDomainGetIVKeyAlias:
Generate/return the secret object alias for an initialization
vector (IV) secret info type. This will be saved in the secret
info block. This will be called from qemuDomainSecretIVSetup.
qemuDomainSecretHaveEncrypt:
Boolean function to determine whether the underly encryption
API is available. This function will utilize a similar mechanism
as the 'gnutls_rnd' did in configure.ac. For this patch it just
returns false. This API is separate from the following one so that
it's possible for the caller to determine whether or not it's
possible to create an IV secret before trying and if not available
fall back to the plain secret mechanism.
qemuDomainSecretIVSetup: (private)
This API handles the details of the generation of the IV secret
and saves the pieces that need to be passed to qemu in order for
the secret to be decrypted. The encrypted secret based upon the
domain master key, an initialization vector (16 byte random value),
and the stored secret. Finally, the requirement from qemu is the IV
and encrypted secret are to be base64 encoded. They can be passed
either directly or within a file. This implementation chooses
to pass directly rather than a file.
qemuDomainSecretSetup: (private)
Shim to call either the IV or Plain Setup functions based upon
whether IV secrets are possible (we have the encryption API) or not.
For this patch, the call will still be to set up the Plain since
qemuDomainSecretHaveEncrypt hasn't been enabled yet.
Use the qemuDomainSecretSetup in qemuDomainSecretDiskPrepare and
qemuDomainSecretHostdevPrepare to add the secret rather than assuming plain.
Signed-off-by: John Ferlan <jferlan(a)redhat.com>
---
src/qemu/qemu_alias.c | 23 +++++++
src/qemu/qemu_alias.h | 2 +
src/qemu/qemu_domain.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 201 insertions(+), 7 deletions(-)
diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
index ade2033..de9a74f 100644
--- a/src/qemu/qemu_alias.c
+++ b/src/qemu/qemu_alias.c
@@ -556,3 +556,26 @@ qemuDomainGetMasterKeyAlias(void)
return alias;
}
+
+
+/* qemuDomainGetIVKeyAlias:
+ *
+ * Generate and return an initialization vector alias
+ *
+ * Returns NULL or a string containing the IV key alias
+ */
+char *
+qemuDomainGetIVKeyAlias(const char *srcalias)
+{
+ char *alias;
+
+ if (!srcalias) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("secret iv alias requires valid source alias"));
+ return NULL;
+ }
+
+ ignore_value(virAsprintf(&alias, "%s-ivKey0", srcalias));
+
+ return alias;
+}
diff --git a/src/qemu/qemu_alias.h b/src/qemu/qemu_alias.h
index 2d7bc9b..435747e 100644
--- a/src/qemu/qemu_alias.h
+++ b/src/qemu/qemu_alias.h
@@ -69,4 +69,6 @@ char *qemuAliasFromDisk(const virDomainDiskDef *disk);
char *qemuDomainGetMasterKeyAlias(void);
+char *qemuDomainGetIVKeyAlias(const char *srcalias);
+
#endif /* __QEMU_ALIAS_H__*/
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 750d32f..84be8d9 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -893,6 +893,175 @@ qemuDomainSecretPlainSetup(virConnectPtr conn,
}
+/* qemuDomainSecretHaveEncypt:
+ *
+ * Returns true if we can support the encryption code, false otherwise
+ */
+static bool
+qemuDomainSecretHaveEncrypt(void)
+{
+ return false;
+}
+
+
+/* qemuDomainSecretIVSetup:
+ * @conn: Pointer to connection
+ * @priv: pointer to domain private object
+ * @secinfo: Pointer to secret info
+ * @srcalias: Alias of the disk/hostdev used to generate the secret alias
+ * @protocol: Protocol for secret
+ * @authdef: Pointer to auth data
+ *
+ * Taking a secinfo, fill in the initialization vector information
+ *
+ * Returns 0 on success, -1 on failure with error message
+ */
+static int
+qemuDomainSecretIVSetup(virConnectPtr conn,
+ qemuDomainObjPrivatePtr priv,
+ qemuDomainSecretInfoPtr secinfo,
+ const char *srcalias,
+ virStorageNetProtocol protocol,
+ virStorageAuthDefPtr authdef)
+{
+ int ret = -1;
+ int rc;
+ uint8_t *raw_iv = NULL;
+ char *secret = NULL;
+ uint8_t *ciphertext = NULL;
+ size_t secretlen = 0, ciphertextlen = 0, paddinglen;
+ int secretType = VIR_SECRET_USAGE_TYPE_ISCSI;
+ const char *protocolstr = virStorageNetProtocolTypeToString(protocol);
+ gnutls_cipher_hd_t handle = NULL;
+ gnutls_datum_t enc_key;
+ gnutls_datum_t iv_key;
+
+ secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_IV;
+ if (VIR_STRDUP(secinfo->s.iv.username, authdef->username) < 0)
+ return -1;
+
+ if (protocol == VIR_STORAGE_NET_PROTOCOL_RBD)
+ secretType = VIR_SECRET_USAGE_TYPE_CEPH;
+
+ if (!(secinfo->s.iv.alias = qemuDomainGetIVKeyAlias(srcalias)))
+ return -1;
+
+ /* Create a random initialization vector */
+ if (!(raw_iv = qemuDomainGenerateRandomKey(QEMU_DOMAIN_IV_KEY_LEN)))
+ return -1;
+
+ /* Encode the IV and save that since qemu will need it */
+ base64_encode_alloc((const char *)raw_iv, QEMU_DOMAIN_IV_KEY_LEN,
+ &secinfo->s.iv.iv);
+ if (!secinfo->s.iv.iv) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to encode initialization vector"));
+ goto cleanup;
+ }
+
+ /* Grab the unencoded secret */
+ if (!(secret = virSecretGetSecretString(conn, protocolstr, false,
+ authdef, secretType)))
+ goto cleanup;
+
+ /* Allocate a padded buffer */
+ secretlen = strlen(secret);
+ paddinglen = 16 - (secretlen % 16);
+ ciphertextlen = secretlen + paddinglen;
+ if (VIR_ALLOC_N(ciphertext, ciphertextlen) < 0)
+ goto cleanup;
+ memcpy(ciphertext, secret, secretlen);
+ memset(ciphertext + secretlen, paddinglen, paddinglen);
+
+ /* clear secret so that it doesn't hang around */
+ memset(secret, 0, strlen(secret));
+
+ /* Initialize the gnutls cipher */
+ enc_key.size = QEMU_DOMAIN_MASTER_KEY_LEN;
+ enc_key.data = priv->masterKey;
+ iv_key.size = QEMU_DOMAIN_IV_KEY_LEN;
+ iv_key.data = raw_iv;
+ if ((rc = gnutls_cipher_init(&handle, GNUTLS_CIPHER_AES_256_CBC,
+ &enc_key, &iv_key)) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to initialize cipher: '%s'"),
+ gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ /* Encrypt the secret and free the memory for cipher operations */
+ rc = gnutls_cipher_encrypt(handle, ciphertext, ciphertextlen);
+ gnutls_cipher_deinit(handle);
+ if (rc < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to encrypt the secret: '%s'"),
+ gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ /* Now base64 encode the ciphertext and store to be passed to qemu */
+ base64_encode_alloc((const char *)ciphertext, ciphertextlen,
+ &secinfo->s.iv.ciphertext);
+ if (!secinfo->s.iv.ciphertext) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to encode ciphertext"));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+
+ /* Clear and free the raw_iv, plaintext secret, and ciphertext */
+ memset(raw_iv, 0, QEMU_DOMAIN_IV_KEY_LEN);
+ VIR_FREE(raw_iv);
+
+ memset(secret, 0, secretlen);
+ VIR_FREE(secret);
+
+ memset(ciphertext, 0, ciphertextlen);
+ VIR_FREE(ciphertext);
+
+ return ret;
+}
+
+
+/* qemuDomainSecretSetup:
+ * @conn: Pointer to connection
+ * @priv: pointer to domain private object
+ * @secinfo: Pointer to secret info
+ * @srcalias: Alias of the disk/hostdev used to generate the secret alias
+ * @protocol: Protocol for secret
+ * @authdef: Pointer to auth data
+ *
+ * If we have the encryption API present and can support a secret object, then
+ * build the IV secret; otherwise, build the Plain secret. This is the magic
+ * decision point for utilizing the IV secrets for a disk whether it's an
+ * iSCSI or an RBD disk.
+ *
+ * Returns 0 on success, -1 on failure
+ */
+static int
+qemuDomainSecretSetup(virConnectPtr conn,
+ qemuDomainObjPrivatePtr priv,
+ qemuDomainSecretInfoPtr secinfo,
+ const char *srcalias,
+ virStorageNetProtocol protocol,
+ virStorageAuthDefPtr authdef)
+{
+ if (qemuDomainSecretHaveEncrypt() &&
+ virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET)) {
+ if (qemuDomainSecretIVSetup(conn, priv, secinfo,
+ srcalias, protocol, authdef) < 0)
+ return -1;
+ } else {
+ if (qemuDomainSecretPlainSetup(conn, secinfo, protocol, authdef) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
/* qemuDomainSecretDiskDestroy:
* @disk: Pointer to a disk definition
*
@@ -921,7 +1090,7 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk)
*/
int
qemuDomainSecretDiskPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainDiskDefPtr disk)
{
virStorageSourcePtr src = disk->src;
@@ -938,8 +1107,8 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretPlainSetup(conn, secinfo, src->protocol,
- src->auth) < 0)
+ if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias,
+ src->protocol, src->auth) < 0)
goto error;
diskPriv->secinfo = secinfo;
@@ -982,7 +1151,7 @@ qemuDomainSecretHostdevDestroy(virDomainHostdevDefPtr hostdev)
*/
int
qemuDomainSecretHostdevPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainHostdevDefPtr hostdev)
{
virDomainHostdevSubsysPtr subsys = &hostdev->source.subsys;
@@ -1003,9 +1172,9 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretPlainSetup(conn, secinfo,
- VIR_STORAGE_NET_PROTOCOL_ISCSI,
- iscsisrc->auth) < 0)
+ if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias,
+ VIR_STORAGE_NET_PROTOCOL_ISCSI,
+ iscsisrc->auth) < 0)
goto error;
hostdevPriv->secinfo = secinfo;
--
2.5.5