On 05/19/2016 05:00 AM, Peter Krempa wrote: > 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@xxxxxxxxxx> >> --- >> 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 > Fine - >> +{ >> + 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. > I was more focused on getting the framework right. I'll add #ifdef HAVE_GNUTLS_CIPHER_ENCRYPT around the virCryptoEncryptDataAESgnutls function and around the calling spot where the #else will be a VIR_ERR_OPERATION_UNSUPPORTED error. I did point out in my cover that this probably should have been an RFC, but seeing as it's the setup for the other AES patches - I just went with a v1. >> + 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. > It was, but I'll move it to make it more obvious. >> + 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. > Fine >> + >> + /* 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 > OK >> + 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. > Sure and the symbols change toe _CIPHER_ as well. That's fine. >> + >> 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. > Well I was *hoping* to get the mock to work... Something covered between patch 2 and the cover (which IIRC you don't read covers). >> + 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. > OK - so I took the other route - passing in an expected cipher value. >> + 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. > I forgot the STRNEQ_NULLABLE too.. hrmph. >> + 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. > OK >> + 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. > I didn't expect v1 to be the last - figured it was more important to get the structure as expected. John -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list