[libvirt] [PATCH 3/3] util: Introduce encryption APIs

Peter Krempa pkrempa at redhat.com
Thu May 19 09:00:06 UTC 2016


On Wed, May 18, 2016 at 19:52:32 -0400, John Ferlan wrote:
> Introduce virCryptoHaveEncrypt and virCryptoEncryptSecret to handle
> performing encryption.
> 
>  virCryptoHaveEncrypt:
>    Boolean function to determine whether the requested encryption
>    API is available. It's expected this API will be called prior to
>    virCryptoEncryptSecret. It will return true/false.
> 
>  virCryptoEncryptSecret:
>    Based on the requested encryption type, call the specific encryption
>    API to encrypt the data.
> 
> Currently the only algorithm support is the AES 256 CBC encryption.
> 
> Adjust tests for the API's
> 
> Signed-off-by: John Ferlan <jferlan at redhat.com>
> ---
>  configure.ac             |   1 +
>  src/libvirt_private.syms |   2 +
>  src/util/vircrypto.c     | 189 ++++++++++++++++++++++++++++++++++++++++++++++-
>  src/util/vircrypto.h     |  20 ++++-
>  tests/vircryptotest.c    |  86 +++++++++++++++++++++
>  5 files changed, 296 insertions(+), 2 deletions(-)
> 

[...]

> diff --git a/src/util/vircrypto.c b/src/util/vircrypto.c
> index 39a479a..05bd167 100644
> --- a/src/util/vircrypto.c
> +++ b/src/util/vircrypto.c

[...]

> @@ -76,3 +85,181 @@ virCryptoHashString(virCryptoHash hash,
>  
>      return 0;
>  }
> +
> +
> +/* virCryptoHaveEncrypt:
> + * @algorithm: Specific encryption algorithm desired
> + *
> + * Expected to be called prior to virCryptoEncryptData in order
> + * to determine whether the requested encryption option is available,
> + * so that "other" alternatives can be taken if the algorithm is
> + * not available.
> + *
> + * Returns true if we can support the encryption.
> + */
> +bool
> +virCryptoHaveEncrypt(virCryptoEncrypt algorithm)

Cipher rather than Encrypt

> +{
> +    switch (algorithm) {
> +
> +    case VIR_CRYPTO_ENCRYPT_AES256CBC:
> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
> +    return true;
> +#else
> +    return false;
> +#endif
> +
> +    case VIR_CRYPTO_ENCRYPT_NONE:
> +    case VIR_CRYPTO_ENCRYPT_LAST:
> +        break;
> +    };
> +
> +    return false;
> +}
> +
> +
> +/* virCryptoEncryptDataAESgntuls:
> + *
> + * Performs the AES gnutls encryption - parameters essentially the
> + * same as virCryptoEncryptSecret, except the libvirt algorithm is
> + * converted to the gnutls_cipher_algorithm_t
> + *
> + * Same input as virCryptoEncryptData, but ensures encryption key and
> + * initialization vector lengths are correct.
> + *
> + * The data buffer will be cleared as soon as it has been prepared for
> + * encryption.
> + *
> + * Encrypts the @data buffer using the @enckey and if available the @iv
> + *
> + * Returns 0 on success with the ciphertext being filled. It is the
> + * caller's responsibility to clear and free it. Returns -1 on failure
> + * w/ error set.
> + */
> +static int
> +virCryptoEncryptDataAESgnutls(gnutls_cipher_algorithm_t gnutls_enc_alg,
> +                              uint8_t *enckey,
> +                              size_t enckeylen,
> +                              uint8_t *iv,
> +                              size_t ivlen,
> +                              uint8_t *data,
> +                              size_t datalen,
> +                              uint8_t **ciphertextret,
> +                              size_t *ciphertextlenret)
> +{
> +    int rc;
> +    size_t i;
> +    gnutls_cipher_hd_t handle = NULL;
> +    gnutls_datum_t enc_key;
> +    gnutls_datum_t iv_key;

As pointed out last time, even here it won't compile with gnutls.

> +    uint8_t *ciphertext;
> +    size_t ciphertextlen;
> +
> +    /* Allocate a padded buffer, copy in the data, padding the buffer with
> +     * the size of the padding size which is required for decryption, then
> +     * clear the data buffer when we're done. */
> +    ciphertextlen = VIR_ROUND_UP(datalen, 16);
> +    if (VIR_ALLOC_N(ciphertext, ciphertextlen) < 0)
> +        return -1;
> +    memcpy(ciphertext, data, datalen);
> +    for (i = datalen; i < ciphertextlen; i++)

And this still isn't documented.

> +        ciphertext[i] = ciphertextlen - datalen;
> +    memset(data, 0, datalen);

The caller should ensure sanitization. Especially sinc its _NOT_ done if
the above allocation fails. In such case the caller still needs to do
it.

> +
> +    /* Initialize the gnutls cipher */
> +    enc_key.size = enckeylen;
> +    enc_key.data = enckey;
> +    if (iv) {
> +        iv_key.size = ivlen;
> +        iv_key.data = iv;
> +    }
> +    if ((rc = gnutls_cipher_init(&handle, gnutls_enc_alg,
> +                                 &enc_key, &iv_key)) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR,
> +                       _("failed to initialize cipher: '%s'"),
> +                       gnutls_strerror(rc));
> +        goto error;
> +    }
> +
> +    /* Encrypt the data 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 data: '%s'"),
> +                       gnutls_strerror(rc));
> +        goto error;
> +    }
> +
> +    *ciphertextret = ciphertext;
> +    *ciphertextlenret = ciphertextlen;
> +    return 0;
> +
> + error:
> +    VIR_DISPOSE_N(ciphertext, ciphertextlen);
> +    return -1;
> +}
> +
> +
> +/* virCryptoEncryptData:
> + * @algorithm: algoritm desired for encryption
> + * @enckey: encryption key
> + * @enckeylen: encription key length
> + * @iv: initialization vector
> + * @ivlen: length of initialization vector
> + * @data: data to encrypt
> + * @datalen: length of data
> + * @ciphertext: stream of bytes allocated to store ciphertext
> + * @ciphertextlen: size of the stream of bytes
> + *
> + * If available, attempt and return the requested encryption type
> + * using the parameters passed.
> + *
> + * Returns 0 on success, -1 on failure with error set
> + */
> +int
> +virCryptoEncryptData(virCryptoEncrypt algorithm,
> +                     uint8_t *enckey,
> +                     size_t enckeylen,
> +                     uint8_t *iv,
> +                     size_t ivlen,
> +                     uint8_t *data,
> +                     size_t datalen,
> +                     uint8_t **ciphertext,
> +                     size_t *ciphertextlen)
> +{
> +    switch (algorithm) {
> +    case VIR_CRYPTO_ENCRYPT_AES256CBC:
> +        if (enckeylen != 32) {
> +            virReportError(VIR_ERR_INVALID_ARG,
> +                           _("AES256CBC encryption invalid keylen=%zu"),
> +                           enckeylen);
> +            return -1;
> +        }
> +
> +        if (ivlen != 16) {
> +            virReportError(VIR_ERR_INVALID_ARG,
> +                           _("AES246CBC initialization vector invalid len=%zu"),

AES256CBC

> +                           ivlen);
> +            return -1;
> +        }
> +
> +        /*
> +         * Encrypt the data buffer using an encryption key and
> +         * initialization vector via the gnutls_cipher_encrypt API
> +         * for GNUTLS_CIPHER_AES_256_CBC.
> +         */
> +        return virCryptoEncryptDataAESgnutls(GNUTLS_CIPHER_AES_256_CBC,
> +                                             enckey, enckeylen, iv, ivlen,
> +                                             data, datalen,
> +                                             ciphertext, ciphertextlen);
> +
> +    case VIR_CRYPTO_ENCRYPT_NONE:
> +    case VIR_CRYPTO_ENCRYPT_LAST:
> +        break;
> +    }
> +
> +    virReportError(VIR_ERR_INVALID_ARG,
> +                   _("algorithm=%d is not supported"), algorithm);
> +    return -1;
> +}
> diff --git a/src/util/vircrypto.h b/src/util/vircrypto.h
> index f67d49d..7d0829e 100644
> --- a/src/util/vircrypto.h
> +++ b/src/util/vircrypto.h

[...]

> @@ -30,6 +30,14 @@ typedef enum {
>      VIR_CRYPTO_HASH_LAST
>  } virCryptoHash;
>  
> +
> +typedef enum {
> +    VIR_CRYPTO_ENCRYPT_NONE = 0,
> +    VIR_CRYPTO_ENCRYPT_AES256CBC,
> +
> +    VIR_CRYPTO_ENCRYPT_LAST
> +} virCryptoEncrypt;

This can be used for decryption too. Thus virCryptoCipher.

> +
>  int
>  virCryptoHashString(virCryptoHash hash,
>                      const char *input,

[...]

> diff --git a/tests/vircryptotest.c b/tests/vircryptotest.c
> index bfc47db..038eb7a 100644
> --- a/tests/vircryptotest.c
> +++ b/tests/vircryptotest.c

[...]

> @@ -56,10 +57,74 @@ testCryptoHash(const void *opaque)
>  }
>  
>  
> +struct testCryptoEncryptData {
> +    virCryptoEncrypt algorithm;
> +    uint8_t *input;
> +    size_t inputlen;
> +    const char *base64;
> +};
> +
> +static int
> +testCryptoEncrypt(const void *opaque)
> +{
> +    const struct testCryptoEncryptData *data = opaque;
> +    size_t i;
> +    uint8_t *enckey = NULL;
> +    size_t enckeylen = 32;
> +    uint8_t *iv = NULL;
> +    size_t ivlen = 16;
> +    uint8_t *ciphertext = NULL;
> +    size_t ciphertextlen = 0;
> +    char *actual = NULL;
> +    int ret = -1;
> +
> +    if (!virCryptoHaveEncrypt(data->algorithm)) {
> +        fprintf(stderr, "Invalid encryption algorithm=%d\n", data->algorithm);
> +        return -1;
> +    }
> +
> +    if (VIR_ALLOC_N(enckey, enckeylen) < 0 ||
> +        VIR_ALLOC_N(iv, ivlen) < 0)
> +        goto cleanup;
> +
> +    /* To be replaced by mock if I can get it to work */

Why not keep it this way? This is testing the encryption not the key
generation.

> +    for (i = 0; i < enckeylen; i++)
> +        enckey[i] = i;
> +    for (i = 0; i < ivlen; i++)
> +        iv[i] = i;
> +
> +    if (virCryptoEncryptData(data->algorithm, enckey, enckeylen, iv, ivlen,
> +                             data->input, data->inputlen,
> +                             &ciphertext, &ciphertextlen) < 0)
> +        goto cleanup;
> +
> +    /* Comparing the encoded ciphertext would be what is desired in
> +     * the long run and easier to read/format the expected ciphertext */

Read? nobody is going to read it if it doesn't match.

> +    if (!(actual = virStringEncodeBase64(ciphertext, ciphertextlen)))
> +        goto cleanup;
> +
> +    if (STRNEQ(data->base64, actual)) {
> +        fprintf(stderr, "Expected encoded encryption '%s' but got '%s'\n",
> +                data->base64, NULLSTR(actual));

and it definitely doesn't make sense to print it. It's just binary
garbage.

> +        goto cleanup;
> +    }
> +
> +    ret = 0;
> + cleanup:
> +    VIR_DISPOSE_N(enckey, enckeylen);
> +    VIR_DISPOSE_N(iv, ivlen);
> +    VIR_DISPOSE_N(ciphertext, ciphertextlen);

This isn't really necessary for demo keys. You can also lose the lenght
variables.

> +    VIR_FREE(actual);
> +

I won't review further versions until you fix the issue with compiling
without gnutls. I've pointed it out far too many times.

Peter
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20160519/b7c7b767/attachment-0001.sig>


More information about the libvir-list mailing list