[libvirt] [PATCH v4 2/3] qemu: Introduce new Secret AES API's

John Ferlan jferlan at redhat.com
Wed May 18 22:45:21 UTC 2016



On 05/18/2016 03:16 AM, Peter Krempa wrote:
> On Tue, May 17, 2016 at 12:36:08 -0400, John Ferlan wrote:
>> New APIs:
>>
>>   qemuDomainGetAESKeyAlias:
>>     Generate/return the secret object alias for an AES Secret Info type.
>>     This will be called from qemuDomainSecretAESSetup.
>>
>>   qemuDomainSecretHaveEncrypt: (private)
>>     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.
>>
>>   qemuDomainSecretAESSetup: (private)
>>     This API handles the details of the generation of the AES 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.
>>
>>     If the gnutls_cipher_init is not available, then an alternate API
>>     returning -1 has been created.
>>
>>   qemuDomainSecretSetup: (private)
>>     Shim to call either the AES or Plain Setup functions based upon
>>     whether AES secrets are possible (we have the encryption API) or not,
>>     we have secrets, and of course if the protocol source is RBD.
>>
>> Signed-off-by: John Ferlan <jferlan at redhat.com>
>> ---
>>  configure.ac           |   1 +
>>  src/qemu/qemu_alias.c  |  23 ++++++
>>  src/qemu/qemu_alias.h  |   2 +
>>  src/qemu/qemu_domain.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++-
>>  4 files changed, 217 insertions(+), 1 deletion(-)
>>
>> diff --git a/configure.ac b/configure.ac
>> index 378069d..10fbd20 100644
>> --- a/configure.ac
>> +++ b/configure.ac
>> @@ -1261,6 +1261,7 @@ if test "x$with_gnutls" != "xno"; then
>>    ]])
>>  
>>    AC_CHECK_FUNCS([gnutls_rnd])
>> +  AC_CHECK_FUNCS([gnutls_cipher_encrypt])
>>  
>>    CFLAGS="$old_CFLAGS"
>>    LIBS="$old_LIBS"
>> diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
>> index cb102ec..bd1a694 100644
>> --- a/src/qemu/qemu_alias.c
>> +++ b/src/qemu/qemu_alias.c
>> @@ -482,3 +482,26 @@ qemuDomainGetMasterKeyAlias(void)
>>  
>>      return alias;
>>  }
>> +
>> +
>> +/* qemuDomainGetAESKeyAlias:
>> + *
>> + * Generate and return an initialization vector alias
> 
> This comment doesn't make sense. Perhaps "encrypted secret key alias" ?
> 

(*forehead smack*) - I searched for all IV occurrences, but forgot to do
the same for 'iv' and 'initialization vector'

>> + *
>> + * Returns NULL or a string containing the AES key alias
>> + */
>> +char *
>> +qemuDomainGetAESKeyAlias(const char *srcalias)
>> +{
>> +    char *alias;
>> +
>> +    if (!srcalias) {
>> +        virReportError(VIR_ERR_INVALID_ARG, "%s",
>> +                       _("secret iv alias requires valid source alias"));
> 
> ... same here.
> 
>> +        return NULL;
>> +    }
>> +
>> +    ignore_value(virAsprintf(&alias, "%s-aesKey0", srcalias));
>> +
>> +    return alias;
>> +}
>> diff --git a/src/qemu/qemu_alias.h b/src/qemu/qemu_alias.h
>> index 2d7bc9b..c31643d 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 *qemuDomainGetAESKeyAlias(const char *srcalias);
>> +
>>  #endif /* __QEMU_ALIAS_H__*/
>> diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
>> index 0733872..edc3ac7 100644
>> --- a/src/qemu/qemu_domain.c
>> +++ b/src/qemu/qemu_domain.c
>> @@ -47,7 +47,6 @@
>>  #include "virprocess.h"
>>  #include "virrandom.h"
>>  #include "secret_util.h"
>> -#include "base64.h"
>>  #ifdef WITH_GNUTLS
>>  # include <gnutls/gnutls.h>
>>  # if HAVE_GNUTLS_CRYPTO_H
>> @@ -884,6 +883,197 @@ qemuDomainSecretPlainSetup(virConnectPtr conn,
>>  }
>>  
>>  
>> +/* qemuDomainSecretHaveEncrypt:
>> + *
>> + * Returns true if we can support the encryption code, false otherwise
>> + */
>> +static bool
>> +qemuDomainSecretHaveEncrypt(void)
> 
> [3]
> 
>> +{
>> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
>> +    return true;
>> +#else
>> +    return false;
>> +#endif
>> +}
>> +
>> +
>> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
>> +/* qemuDomainSecretAESSetup:
>> + * @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 AES specific information using the
>> + * gnutls_cipher_encrypt API algorithm for GNUTLS_CIPHER_AES_256_CBC
>> + *
>> + * Returns 0 on success, -1 on failure with error message
>> + */
>> +static int
>> +qemuDomainSecretAESSetup(virConnectPtr conn,
>> +                         qemuDomainObjPrivatePtr priv,
>> +                         qemuDomainSecretInfoPtr secinfo,
>> +                         const char *srcalias,
>> +                         virStorageNetProtocol protocol,
>> +                         virStorageAuthDefPtr authdef)
>> +{
>> +    int ret = -1;
>> +    int rc;
>> +    uint8_t *raw_iv = NULL;
>> +    uint8_t *secret = NULL;
>> +    uint8_t *ciphertext = NULL;
>> +    size_t secretlen = 0;
>> +    size_t ciphertextlen = 0;
>> +    size_t i;
>> +    int secretType = VIR_SECRET_USAGE_TYPE_ISCSI;
>> +    gnutls_cipher_hd_t handle = NULL;
>> +    gnutls_datum_t enc_key;
>> +    gnutls_datum_t iv_key;
> 
> If you split out the gnutls stuff into a util function (in
> util/vircrypto.c which sounds lile the right place ) that will do the
> AES encryption you will not need to taint the qemu code with a lot of
> conditionally compiled code.

OK - that's fine... patches forthcoming...

> 
> In addition you could also split out the helper that is using gnutls to
> generate better key since it seems that [2] is not really qemu
> specific. [3] can be added there too as a witness that we can encrypt
> stuff.
> 

[2] isn't qemu specific, but the base64 encoded value is required to be
supplied on the command line so this code will need to know what it is.

>> +
>> +    secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES;
>> +    if (VIR_STRDUP(secinfo->s.aes.username, authdef->username) < 0)
>> +        return -1;
>> +
>> +    switch ((virStorageNetProtocol)protocol) {
>> +    case VIR_STORAGE_NET_PROTOCOL_RBD:
>> +        secretType = VIR_SECRET_USAGE_TYPE_CEPH;
>> +        break;
>> +
>> +    case VIR_STORAGE_NET_PROTOCOL_NONE:
>> +    case VIR_STORAGE_NET_PROTOCOL_NBD:
>> +    case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
>> +    case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
>> +    case VIR_STORAGE_NET_PROTOCOL_ISCSI:
>> +    case VIR_STORAGE_NET_PROTOCOL_HTTP:
>> +    case VIR_STORAGE_NET_PROTOCOL_HTTPS:
>> +    case VIR_STORAGE_NET_PROTOCOL_FTP:
>> +    case VIR_STORAGE_NET_PROTOCOL_FTPS:
>> +    case VIR_STORAGE_NET_PROTOCOL_TFTP:
>> +    case VIR_STORAGE_NET_PROTOCOL_LAST:
>> +        virReportError(VIR_ERR_INTERNAL_ERROR,
>> +                       _("unsupported protocol '%s' for initialization vector"),
> 
> This error message doesn't make sense at all. "protocol %s can't be used
> with encrypted secrets" perhaps.
> 

Right ... changed...

>> +                       virStorageNetProtocolTypeToString(protocol));
>> +        return -1;
>> +    }
>> +
>> +    if (!(secinfo->s.aes.alias = qemuDomainGetAESKeyAlias(srcalias)))
>> +        return -1;
>> +
>> +    /* Create a random initialization vector */
> 
> [1]
> 
>> +    if (!(raw_iv = qemuDomainGenerateRandomKey(QEMU_DOMAIN_AES_IV_LEN)))
> 
> [2]
> 
>> +        return -1;
>> +
>> +    /* Encode the IV and save that since qemu will need it */
> 
> [1]
> 
>> +    if (!(secinfo->s.aes.iv = virStringEncodeBase64(raw_iv,
>> +                                                    QEMU_DOMAIN_AES_IV_LEN)))
>> +        goto cleanup;
>> +
>> +    /* Grab the unencoded secret */
> 
> [1]
> 
>> +    if (virSecretGetSecretString(conn, authdef, secretType,
>> +                                 &secret, &secretlen) < 0)
>> +        goto cleanup;
>> +
>> +    /* Allocate a padded buffer */
>> +    ciphertextlen = VIR_ROUND_UP(secretlen, 16);
>> +    if (VIR_ALLOC_N(ciphertext, ciphertextlen) < 0)
>> +        goto cleanup;
>> +    memcpy(ciphertext, secret, secretlen);
>> +    for (i = secretlen; i < ciphertextlen; i++)
>> +        ciphertext[i] = ciphertextlen - secretlen;
> 
> The memset here was okay along with this. What I've asked for is to add
> a comment for one of the few non-obvious parts of the code once you
> didn't spare comments for the obvious stuff [1].
> 

So the comment "/* this is black magic */" is sufficient, right? ;-)

I can be less verbose on the [1] comments though. They are derived when
I'm putting together the code so I don't forget a step...

>> +    /* Initialize the gnutls cipher */
>> +    enc_key.size = QEMU_DOMAIN_MASTER_KEY_LEN;
>> +    enc_key.data = priv->masterKey;
>> +    iv_key.size = QEMU_DOMAIN_AES_IV_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 encode the ciphertext and store to be passed to qemu */
>> +    if (!(secinfo->s.aes.ciphertext = virStringEncodeBase64(ciphertext,
>> +                                                            ciphertextlen)))
>> +        goto cleanup;
>> +
>> +    ret = 0;
>> +
>> + cleanup:
>> +    VIR_FREE(raw_iv);
> 
> Since you are wiping the ciphertext you can wipe the IV too.
> 

I think I tried that, but VIR_DISPOSE_N didn't seem to be happy with
receiving QEMU_DOMAIN_AES_IV_LEN

>> +    VIR_DISPOSE_N(secret, secretlen);
>> +    VIR_DISPOSE_N(ciphertext, ciphertextlen);
>> +
>> +    return ret;
>> +}
>> +#else
>> +static int
>> +qemuDomainSecretAESSetup(virConnectPtr conn ATTRIBUTE_UNUSED,
>> +                         qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
>> +                         qemuDomainSecretInfoPtr secinfo ATTRIBUTE_UNUSED,
>> +                         const char *srcalias ATTRIBUTE_UNUSED,
>> +                         virStorageNetProtocol protocol ATTRIBUTE_UNUSED,
>> +                         virStorageAuthDefPtr authdef ATTRIBUTE_UNUSED)
>> +{
>> +    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> +                   _("gnutls_cipher_encrypt unsupported"));
>> +    return -1;
>> +}
>> +#endif
>> +
>> +
>> +/* 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 AES secret; otherwise, build the Plain secret. This is the magic
>> + * decision point for utilizing the AES secrets for an RBD disk. For now iSCSI
>> + * disks and hostdevs will not be able to utilize this mechanism.
>> + *
>> + * Returns 0 on success, -1 on failure
>> + */
>> +static int ATTRIBUTE_UNUSED
> 
> If you add this function in a separate patch and it will call just
> qemuDomainSecretPlainSetup at that point you will not have to make it
> unused here and the next patch can merge the few simpler functions in
> one take.
> 

Sure - just a different way of getting there...

John

>> +qemuDomainSecretSetup(virConnectPtr conn,
>> +                      qemuDomainObjPrivatePtr priv,
>> +                      qemuDomainSecretInfoPtr secinfo,
>> +                      const char *srcalias,
>> +                      virStorageNetProtocol protocol,
>> +                      virStorageAuthDefPtr authdef)
>> +{
>> +    if (qemuDomainSecretHaveEncrypt() &&
>> +        virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) &&
>> +        protocol == VIR_STORAGE_NET_PROTOCOL_RBD) {
>> +        if (qemuDomainSecretAESSetup(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
>>   *
>> -- 
>> 2.5.5
>>
>> --
>> libvir-list mailing list
>> libvir-list at redhat.com
>> https://www.redhat.com/mailman/listinfo/libvir-list




More information about the libvir-list mailing list