[libvirt] [PATCH v5 2/6] util: Introduce encryption APIs

John Ferlan jferlan at redhat.com
Fri May 20 14:03:27 UTC 2016



On 05/20/2016 08:56 AM, Peter Krempa wrote:
> On Thu, May 19, 2016 at 16:29:01 -0400, John Ferlan wrote:
>> Introduce virCryptoHaveCipher and virCryptoEncryptData to handle
>> performing encryption.
>>
>>  virCryptoHaveCipher:
>>    Boolean function to determine whether the requested cipher algorithm
>>    is available. It's expected this API will be called prior to
>>    virCryptoEncryptdata. It will return true/false.
>>
>>  virCryptoEncryptData:
>>    Based on the requested cipher 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     | 192 ++++++++++++++++++++++++++++++++++++++++++++++-
>>  src/util/vircrypto.h     |  20 ++++-
>>  tests/vircryptotest.c    | 100 +++++++++++++++++++++++-
>>  5 files changed, 312 insertions(+), 3 deletions(-)
> 
> [...]
> 
>> diff --git a/src/util/vircrypto.c b/src/util/vircrypto.c
>> index 39a479a..b8f5554 100644
>> --- a/src/util/vircrypto.c
>> +++ b/src/util/vircrypto.c
> 
> [...]
> 
>> @@ -76,3 +85,184 @@ virCryptoHashString(virCryptoHash hash,
>>  
>>      return 0;
>>  }
>> +
>> +
>> +/* virCryptoHaveCipher:
>> + * @algorithm: Specific cipher 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
>> +virCryptoHaveCipher(virCryptoCipher algorithm)
>> +{
>> +    switch (algorithm) {
>> +
>> +    case VIR_CRYPTO_CIPHER_AES256CBC:
>> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
>> +    return true;
>> +#else
>> +    return false;
>> +#endif
>> +
>> +    case VIR_CRYPTO_CIPHER_NONE:
>> +    case VIR_CRYPTO_CIPHER_LAST:
>> +        break;
>> +    };
>> +
>> +    return false;
>> +}
>> +
>> +
>> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
>> +/* virCryptoEncryptDataAESgntuls:
>> + *
>> + * Performs the AES gnutls encryption
>> + *
>> + * Same input as virCryptoEncryptData, except the algoritm is replaced
>> + * by the specific gnutls algorithm.
>> + *
>> + * 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_buf;
>> +    uint8_t *ciphertext;
>> +    size_t ciphertextlen;
>> +
>> +    /* Allocate a padded buffer, copy in the data */
>> +    ciphertextlen = VIR_ROUND_UP(datalen, 16);
>> +    if (VIR_ALLOC_N(ciphertext, ciphertextlen) < 0)
>> +        return -1;
>> +    memcpy(ciphertext, data, datalen);
>> +
>> +     /* Fill in the padding of the buffer with the size of the padding
>> +      * which is required for decryption. */
>> +    for (i = datalen; i < ciphertextlen; i++)
>> +        ciphertext[i] = ciphertextlen - datalen;
>> +
>> +    /* Initialize the gnutls cipher */
>> +    enc_key.size = enckeylen;
>> +    enc_key.data = enckey;
>> +    if (iv) {
>> +        iv_buf.size = ivlen;
>> +        iv_buf.data = iv;
>> +    }
>> +    if ((rc = gnutls_cipher_init(&handle, gnutls_enc_alg,
>> +                                 &enc_key, &iv_buf)) < 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);

    memset(&enc_key, 0, sizeof(gnutls_datum_t));
    memset(&iv_key, 0, sizeof(gnutls_datum_t));

>> +    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;
> 
> In both cases you should clear the stack'd copy of the encryption key.
> 
>> +}
>> +#endif
>> +
>> +
>> +/* 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(virCryptoCipher 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_CIPHER_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,
>> +                           _("AES256CBC initialization vector invalid len=%zu"),
>> +                           ivlen);
>> +            return -1;
>> +        }
>> +
>> +#ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
>> +        /*
>> +         * 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);
>> +#else
>> +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
>> +                       _("no encryption algorithm exists"));
> 
> That is quite a strong message. I'd say that the chosen algorithm is not
> supported.
> 

OK - I'll replace with a break; to fall into message below

>> +        return -1;
>> +#endif
>> +
>> +    case VIR_CRYPTO_CIPHER_NONE:
>> +    case VIR_CRYPTO_CIPHER_LAST:
>> +        break;
>> +    }
>> +
>> +    virReportError(VIR_ERR_INVALID_ARG,
>> +                   _("algorithm=%d is not supported"), algorithm);
> 
> e.g. as you do here.
> 
> [...]
> 
>> --- a/src/util/vircrypto.h
>> +++ b/src/util/vircrypto.h
> 
> [...]
> 
>> @@ -37,4 +45,14 @@ virCryptoHashString(virCryptoHash hash,
>>      ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
>>      ATTRIBUTE_RETURN_CHECK;
>>  
>> +bool virCryptoHaveCipher(virCryptoCipher algorithm);
>> +
>> +int virCryptoEncryptData(virCryptoCipher 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)
>> +    ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(6)
>> +    ATTRIBUTE_NONNULL(8) ATTRIBUTE_RETURN_CHECK;
> 
> ciphertext and ciphertextlen must be nonnull too.
> 

OK - ciphertext is (8)... added (9) for ciphertextlen

>> +
>>  #endif /* __VIR_CRYPTO_H__ */
>> diff --git a/tests/vircryptotest.c b/tests/vircryptotest.c
>> index bfc47db..1d719d9 100644
>> --- a/tests/vircryptotest.c
>> +++ b/tests/vircryptotest.c
> 
> [...]
> 
>> @@ -56,10 +58,82 @@ testCryptoHash(const void *opaque)
>>  }
>>  
>>  
>> +struct testCryptoEncryptData {
>> +    virCryptoCipher algorithm;
>> +    uint8_t *input;
>> +    size_t inputlen;
>> +    uint8_t *ciphertext;
>> +    size_t ciphertextlen;
>> +};
>> +
>> +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;
>> +    int ret = -1;
>> +
>> +    if (!virCryptoHaveCipher(data->algorithm)) {
>> +        fprintf(stderr, "Invalid cipher algorithm=%d\n", data->algorithm);

Would adding "return EXIT_AM_SKIP;" be acceptable?

>> +        return -1;
>> +    }
>> +
>> +    if (VIR_ALLOC_N(enckey, enckeylen) < 0 ||
>> +        VIR_ALLOC_N(iv, ivlen) < 0)
>> +        goto cleanup;
>> +
>> +    if (virRandomBytes(enckey, enckeylen) < 0 ||
>> +        virRandomBytes(iv, ivlen) < 0)
>> +        goto cleanup;
>> +
>> +    if (virCryptoEncryptData(data->algorithm, enckey, enckeylen, iv, ivlen,
>> +                             data->input, data->inputlen,
>> +                             &ciphertext, &ciphertextlen) < 0)
>> +        goto cleanup;
>> +
>> +    if (data->ciphertextlen != ciphertextlen) {
>> +        fprintf(stderr, "Expected ciphertextlen(%zu) doesn't match (%zu)\n",
>> +                data->ciphertextlen, ciphertextlen);
>> +        goto cleanup;
>> +    }
>> +
>> +    if (memcmp(data->ciphertext, ciphertext, ciphertextlen)) {
>> +        fprintf(stderr, "Expected ciphertext doesn't match\n");
>> +        for (i = 0; i < ciphertextlen; i++) {
>> +            if (data->ciphertext[i] != ciphertext[i]) {
>> +                fprintf(stderr, "expected[%zu]=%x ciphertext[%zu]=%x\n",
>> +                        i, data->ciphertext[i],
>> +                        i, ciphertext[i]);
> 
> Last time I was pointing out that it doesn't make much sense to output
> the difference whether encoded to base64 or not.
> 
> [...]
> 

I used it mainly for "testing", but OK I'll remove it.

>> @@ -84,7 +158,31 @@ mymain(void)
>>      VIR_CRYPTO_HASH(VIR_CRYPTO_HASH_MD5, "The quick brown fox", "a2004f37730b9445670a738fa0fc9ee5");
>>      VIR_CRYPTO_HASH(VIR_CRYPTO_HASH_SHA256, "The quick brown fox", "5cac4f980fedc3d3f1f99b4be3472c9b30d56523e632d151237ec9309048bda9");
> 
> 
>>  
>> +#undef VIR_CRYPTO_HASH
>> +
>> +#define VIR_CRYPTO_ENCRYPT(a, n, i, il, c, cl)   \
>> +    do {                                         \
>> +        struct testCryptoEncryptData data = {    \
>> +            .algorithm = a,                      \
>> +            .input = i,                          \
>> +            .inputlen = il,                      \
>> +            .ciphertext = c,                     \
>> +            .ciphertextlen = cl,                 \
>> +        };                                       \
>> +        if (virtTestRun("Encrypt " n, testCryptoEncrypt, &data) < 0) \
>> +            ret = -1;                                                \
>> +    } while (0)
>> +
>> +    memset(&secretdata, 0, 8);
>> +    memcpy(&secretdata, "letmein", 7);
>> +
>> +    VIR_CRYPTO_ENCRYPT(VIR_CRYPTO_CIPHER_AES256CBC, "aes265cbc",
>> +                       secretdata, 7, expected_ciphertext, 16);
>> +
> 
> This test will fail on builds without gnutls.

<sigh>

Thanks

John

> 
> ACK with the nits fixed.
> 




More information about the libvir-list mailing list