diff --git a/configure.ac b/configure.ac index 378069d3e9..10fbd20e65 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/libvirt_private.syms b/src/libvirt_private.syms index 868bcfa2dd..6c02b10eff 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1394,7 +1394,9 @@ virConfWriteMem; # util/vircrypto.h +virCryptoEncryptData; virCryptoHashString; +virCryptoHaveCipher; # util/virdbus.h diff --git a/src/util/vircrypto.c b/src/util/vircrypto.c index 39a479af48..ed8675d96f 100644 --- a/src/util/vircrypto.c +++ b/src/util/vircrypto.c @@ -1,7 +1,7 @@ /* * vircrypto.c: cryptographic helper APIs * - * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014, 2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,11 +21,20 @@ #include #include "vircrypto.h" +#include "virlog.h" #include "virerror.h" #include "viralloc.h" #include "md5.h" #include "sha256.h" +#ifdef WITH_GNUTLS +# include +# if HAVE_GNUTLS_CRYPTO_H +# include +# endif +#endif + +VIR_LOG_INIT("util.crypto"); #define VIR_FROM_THIS VIR_FROM_CRYPTO @@ -76,3 +85,186 @@ 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_buf, 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); + memset(&enc_key, 0, sizeof(gnutls_datum_t)); + memset(&iv_buf, 0, sizeof(gnutls_datum_t)); + return -1; +} +#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 + break; +#endif + + case VIR_CRYPTO_CIPHER_NONE: + case VIR_CRYPTO_CIPHER_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 f67d49da47..5d6d37c497 100644 --- a/src/util/vircrypto.h +++ b/src/util/vircrypto.h @@ -1,7 +1,7 @@ /* * vircrypto.h: cryptographic helper APIs * - * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014, 2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,6 +30,14 @@ typedef enum { VIR_CRYPTO_HASH_LAST } virCryptoHash; + +typedef enum { + VIR_CRYPTO_CIPHER_NONE = 0, + VIR_CRYPTO_CIPHER_AES256CBC, + + VIR_CRYPTO_CIPHER_LAST +} virCryptoCipher; + int virCryptoHashString(virCryptoHash hash, const char *input, @@ -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_NONNULL(9) ATTRIBUTE_RETURN_CHECK; + #endif /* __VIR_CRYPTO_H__ */ diff --git a/tests/vircryptotest.c b/tests/vircryptotest.c index bfc47db7fc..72265d949a 100644 --- a/tests/vircryptotest.c +++ b/tests/vircryptotest.c @@ -21,9 +21,11 @@ #include #include "vircrypto.h" +#include "virrandom.h" #include "testutils.h" +#define VIR_FROM_THIS VIR_FROM_NONE struct testCryptoHashData { virCryptoHash hash; @@ -56,10 +58,74 @@ 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; + 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, "cipher algorithm=%d unavailable\n", data->algorithm); + return EXIT_AM_SKIP; + } + + 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"); + goto cleanup; + } + + ret = 0; + cleanup: + VIR_FREE(enckey); + VIR_FREE(iv); + VIR_FREE(ciphertext); + + return ret; +} + + static int mymain(void) { int ret = 0; + uint8_t secretdata[8]; + uint8_t expected_ciphertext[16] = {0x48, 0x8e, 0x9, 0xb9, + 0x6a, 0xa6, 0x24, 0x5f, + 0x1b, 0x8c, 0x3f, 0x48, + 0x27, 0xae, 0xb6, 0x7a}; #define VIR_CRYPTO_HASH(h, i, o) \ do { \ @@ -84,7 +150,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); + +#undef VIR_CRYPTO_ENCRYPT + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -VIRT_TEST_MAIN(mymain) +/* Forces usage of not so random virRandomBytes */ +VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virrandommock.so")