Adds `virCryptoDecryptDataAESgnutls` and `virCryptoDecryptData`
as wrapper functions for GnuTLS decryption.
These functions are the inverse of the existing GnuTLS encryption wrappers.
This commit also includes a corresponding test case to validate data decryption.
Signed-off-by: Arun Menon <armenon@redhat.com>
---
src/libvirt_private.syms | 1 +
src/util/vircrypto.c | 128 ++++++++++++++++++++++++++++++++++++++-
src/util/vircrypto.h | 8 +++
tests/vircryptotest.c | 65 ++++++++++++++++++++
4 files changed, 201 insertions(+), 1 deletion(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index fb482fff40..fc5fdb00f4 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2252,6 +2252,7 @@ virConfWriteMem;
# util/vircrypto.h
+virCryptoDecryptData;
virCryptoEncryptData;
virCryptoHashBuf;
virCryptoHashString;
diff --git a/src/util/vircrypto.c b/src/util/vircrypto.c
index 3ce23264ca..fedb39b167 100644
--- a/src/util/vircrypto.c
+++ b/src/util/vircrypto.c
@@ -98,7 +98,7 @@ virCryptoHashString(virCryptoHash hash,
}
-/* virCryptoEncryptDataAESgntuls:
+/* virCryptoEncryptDataAESgnutls:
*
* Performs the AES gnutls encryption
*
@@ -233,3 +233,129 @@ virCryptoEncryptData(virCryptoCipher algorithm,
_("algorithm=%1$d is not supported"), algorithm);
return -1;
}
+
+/* virCryptoDecryptDataAESgnutls:
+ *
+ * Performs the AES gnutls decryption
+ *
+ * Same input as virCryptoDecryptData, except the algorithm is replaced
+ * by the specific gnutls algorithm.
+ *
+ * Decrypts the @data buffer using the @deckey and if available the @iv
+ *
+ * Returns 0 on success with the plaintext being filled. It is the
+ * caller's responsibility to clear and free it. Returns -1 on failure
+ * w/ error set.
+ */
+static int
+virCryptoDecryptDataAESgnutls(gnutls_cipher_algorithm_t gnutls_dec_alg,
+ uint8_t *deckey,
+ size_t deckeylen,
+ uint8_t *iv,
+ size_t ivlen,
+ uint8_t *data,
+ size_t datalen,
+ uint8_t **plaintextret,
+ size_t *plaintextlenret)
+{
+ int rc;
+ size_t i;
+ gnutls_cipher_hd_t handle = NULL;
+ gnutls_datum_t dec_key = { .data = deckey, .size = deckeylen };
+ gnutls_datum_t iv_buf = { .data = iv, .size = ivlen };
+ g_autofree uint8_t *plaintext = NULL;
+ size_t plaintextlen;
+
+ if ((rc = gnutls_cipher_init(&handle, gnutls_dec_alg,
+ &dec_key, &iv_buf)) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to initialize cipher: '%1$s'"),
+ gnutls_strerror(rc));
+ return -1;
+ }
+
+ plaintext = g_memdup2(data, datalen);
+ plaintextlen = datalen;
+
+ rc = gnutls_cipher_decrypt(handle, plaintext, plaintextlen);
+ gnutls_cipher_deinit(handle);
+ if (rc < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to decrypt the data: '%1$s'"),
+ gnutls_strerror(rc));
+ goto error;
+ }
+ if (plaintextlen == 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("decrypted data has zero length"));
+ goto error;
+ }
+ i = plaintext[plaintextlen - 1];
+ if (i > plaintextlen) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("decrypted data has invalid padding"));
+ goto error;
+ }
+ *plaintextlenret = plaintextlen - i;
+ *plaintextret = g_steal_pointer(&plaintext);
+ return 0;
+ error:
+ virSecureErase(plaintext, plaintextlen);
+ return -1;
+}
+
+/* virCryptoDecryptData:
+ * @algorithm: algorithm desired for decryption
+ * @deckey: decryption key
+ * @deckeylen: decryption key length
+ * @iv: initialization vector
+ * @ivlen: length of initialization vector
+ * @data: data to decrypt
+ * @datalen: length of data
+ * @plaintext: stream of bytes allocated to store plaintext
+ * @plaintextlen: size of the stream of bytes
+ * Returns 0 on success, -1 on failure with error set
+ */
+int
+virCryptoDecryptData(virCryptoCipher algorithm,
+ uint8_t *deckey,
+ size_t deckeylen,
+ uint8_t *iv,
+ size_t ivlen,
+ uint8_t *data,
+ size_t datalen,
+ uint8_t **plaintext,
+ size_t *plaintextlen)
+{
+ switch (algorithm) {
+ case VIR_CRYPTO_CIPHER_AES256CBC:
+ if (deckeylen != 32) {
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("AES256CBC decryption invalid keylen=%1$zu"),
+ deckeylen);
+ return -1;
+ }
+ if (ivlen != 16) {
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("AES256CBC initialization vector invalid len=%1$zu"),
+ ivlen);
+ return -1;
+ }
+ /*
+ * Decrypt the data buffer using a decryption key and
+ * initialization vector via the gnutls_cipher_decrypt API
+ * for GNUTLS_CIPHER_AES_256_CBC.
+ */
+ return virCryptoDecryptDataAESgnutls(GNUTLS_CIPHER_AES_256_CBC,
+ deckey, deckeylen, iv, ivlen,
+ data, datalen,
+ plaintext, plaintextlen);
+ case VIR_CRYPTO_CIPHER_NONE:
+ case VIR_CRYPTO_CIPHER_LAST:
+ break;
+ }
+
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("algorithm=%1$d is not supported"), algorithm);
+ return -1;
+}
diff --git a/src/util/vircrypto.h b/src/util/vircrypto.h
index 5f079ac335..2e8557839d 100644
--- a/src/util/vircrypto.h
+++ b/src/util/vircrypto.h
@@ -61,3 +61,11 @@ int virCryptoEncryptData(virCryptoCipher algorithm,
uint8_t **ciphertext, size_t *ciphertextlen)
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(6)
ATTRIBUTE_NONNULL(8) ATTRIBUTE_NONNULL(9) G_GNUC_WARN_UNUSED_RESULT;
+
+int virCryptoDecryptData(virCryptoCipher algorithm,
+ uint8_t *deckey, size_t deckeylen,
+ uint8_t *iv, size_t ivlen,
+ uint8_t *data, size_t datalen,
+ uint8_t **plaintext, size_t *plaintextlen)
+ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(6)
+ ATTRIBUTE_NONNULL(8) ATTRIBUTE_NONNULL(9) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/tests/vircryptotest.c b/tests/vircryptotest.c
index 9ffe70756e..864fa8838d 100644
--- a/tests/vircryptotest.c
+++ b/tests/vircryptotest.c
@@ -62,6 +62,14 @@ struct testCryptoEncryptData {
size_t ciphertextlen;
};
+struct testCryptoDecryptData {
+ virCryptoCipher algorithm;
+ uint8_t *input;
+ size_t inputlen;
+ uint8_t *plaintext;
+ size_t plaintextlen;
+};
+
static int
testCryptoEncrypt(const void *opaque)
{
@@ -101,6 +109,44 @@ testCryptoEncrypt(const void *opaque)
return 0;
}
+static int
+testCryptoDecrypt(const void *opaque)
+{
+ const struct testCryptoDecryptData *data = opaque;
+ g_autofree uint8_t *deckey = NULL;
+ size_t deckeylen = 32;
+ g_autofree uint8_t *iv = NULL;
+ size_t ivlen = 16;
+ g_autofree uint8_t *plaintext = NULL;
+ size_t plaintextlen = 0;
+
+ deckey = g_new0(uint8_t, deckeylen);
+ iv = g_new0(uint8_t, ivlen);
+
+ if (virRandomBytes(deckey, deckeylen) < 0 ||
+ virRandomBytes(iv, ivlen) < 0) {
+ fprintf(stderr, "Failed to generate random bytes\n");
+ return -1;
+ }
+
+ if (virCryptoDecryptData(data->algorithm, deckey, deckeylen, iv, ivlen,
+ data->input, data->inputlen,
+ &plaintext, &plaintextlen) < 0)
+ return -1;
+
+ if (data->plaintextlen != plaintextlen) {
+ fprintf(stderr, "Expected plaintexlen(%zu) doesn't match (%zu)\n",
+ data->plaintextlen, plaintextlen);
+ return -1;
+ }
+
+ if (memcmp(data->plaintext, plaintext, plaintextlen)) {
+ fprintf(stderr, "Expected plaintext doesn't match\n");
+ return -1;
+ }
+
+ return 0;
+}
static int
mymain(void)
@@ -155,7 +201,26 @@ mymain(void)
#undef VIR_CRYPTO_ENCRYPT
+#define VIR_CRYPTO_DECRYPT(a, n, i, il, c, cl) \
+ do { \
+ struct testCryptoDecryptData data = { \
+ .algorithm = a, \
+ .input = i, \
+ .inputlen = il, \
+ .plaintext = c, \
+ .plaintextlen = cl, \
+ }; \
+ if (virTestRun("Decrypt " n, testCryptoDecrypt, &data) < 0) \
+ ret = -1; \
+ } while (0)
+
+ VIR_CRYPTO_DECRYPT(VIR_CRYPTO_CIPHER_AES256CBC, "aes256cbc",
+ expected_ciphertext, 16, secretdata, 7);
+
+#undef VIR_CRYPTO_DECRYPT
+
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
}
/* Forces usage of not so random virRandomBytes */
--
2.51.1
On Thu, Nov 20, 2025 at 22:23:42 +0530, Arun Menon via Devel wrote:
> Adds `virCryptoDecryptDataAESgnutls` and `virCryptoDecryptData`
> as wrapper functions for GnuTLS decryption.
>
> These functions are the inverse of the existing GnuTLS encryption wrappers.
> This commit also includes a corresponding test case to validate data decryption.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> ---
> src/libvirt_private.syms | 1 +
> src/util/vircrypto.c | 128 ++++++++++++++++++++++++++++++++++++++-
> src/util/vircrypto.h | 8 +++
> tests/vircryptotest.c | 65 ++++++++++++++++++++
> 4 files changed, 201 insertions(+), 1 deletion(-)
> @@ -233,3 +233,129 @@ virCryptoEncryptData(virCryptoCipher algorithm,
> _("algorithm=%1$d is not supported"), algorithm);
> return -1;
> }
> +
> +/* virCryptoDecryptDataAESgnutls:
> + *
> + * Performs the AES gnutls decryption
> + *
> + * Same input as virCryptoDecryptData, except the algorithm is replaced
> + * by the specific gnutls algorithm.
> + *
> + * Decrypts the @data buffer using the @deckey and if available the @iv
> + *
> + * Returns 0 on success with the plaintext being filled. It is the
> + * caller's responsibility to clear and free it. Returns -1 on failure
> + * w/ error set.
> + */
> +static int
> +virCryptoDecryptDataAESgnutls(gnutls_cipher_algorithm_t gnutls_dec_alg,
> + uint8_t *deckey,
> + size_t deckeylen,
> + uint8_t *iv,
> + size_t ivlen,
> + uint8_t *data,
> + size_t datalen,
> + uint8_t **plaintextret,
> + size_t *plaintextlenret)
> +{
> + int rc;
> + size_t i;
The 'i' variable isn't used as an iterator in a loop. Please give it a
proper name.
> + gnutls_cipher_hd_t handle = NULL;
> + gnutls_datum_t dec_key = { .data = deckey, .size = deckeylen };
> + gnutls_datum_t iv_buf = { .data = iv, .size = ivlen };
> + g_autofree uint8_t *plaintext = NULL;
> + size_t plaintextlen;
> +
> + if ((rc = gnutls_cipher_init(&handle, gnutls_dec_alg,
> + &dec_key, &iv_buf)) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("failed to initialize cipher: '%1$s'"),
> + gnutls_strerror(rc));
> + return -1;
> + }
> +
> + plaintext = g_memdup2(data, datalen);
> + plaintextlen = datalen;
So 'plaintextlen' is assigned from 'datalen'. 'datalen' wasn't modified
so is the same as the argument.
> + rc = gnutls_cipher_decrypt(handle, plaintext, plaintextlen);
'plaintextlen' is passed by value so it can't be modified by
'gnutls_cipher_decrypt'.
> + gnutls_cipher_deinit(handle);
> + if (rc < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("failed to decrypt the data: '%1$s'"),
> + gnutls_strerror(rc));
> + goto error;
> + }
> + if (plaintextlen == 0) {
'plaintextlen' still wasn't modified and the value you are checking
against was known from the beginning, so do this check right at the
beginning.
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("decrypted data has zero length"));
> + goto error;
> + }
> + i = plaintext[plaintextlen - 1];
So 'i' should be called 'paddinglen' and also be of 'uint8_t' type.
Also here it'd be worth commenting why we're looking at the last byte to
determine the padding lenght.
> + if (i > plaintextlen) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
Failed padding isn't IMO an internal error any more since it's data that
can originate from somewhere else.
> + _("decrypted data has invalid padding"));
> + goto error;
> + }
> + *plaintextlenret = plaintextlen - i;
This at the first glance looked like plaintextlen - 1. So 'i' is not
only wrong semantically but also can look confusing.
> + *plaintextret = g_steal_pointer(&plaintext);
> + return 0;
> + error:
> + virSecureErase(plaintext, plaintextlen);
> + return -1;
> +}
> +
> +/* virCryptoDecryptData:
> + * @algorithm: algorithm desired for decryption
> + * @deckey: decryption key
> + * @deckeylen: decryption key length
> + * @iv: initialization vector
> + * @ivlen: length of initialization vector
> + * @data: data to decrypt
> + * @datalen: length of data
> + * @plaintext: stream of bytes allocated to store plaintext
> + * @plaintextlen: size of the stream of bytes
> + * Returns 0 on success, -1 on failure with error set
> + */
> +int
> +virCryptoDecryptData(virCryptoCipher algorithm,
> + uint8_t *deckey,
> + size_t deckeylen,
> + uint8_t *iv,
> + size_t ivlen,
> + uint8_t *data,
> + size_t datalen,
> + uint8_t **plaintext,
> + size_t *plaintextlen)
> +{
> + switch (algorithm) {
> + case VIR_CRYPTO_CIPHER_AES256CBC:
> + if (deckeylen != 32) {
> + virReportError(VIR_ERR_INVALID_ARG,
> + _("AES256CBC decryption invalid keylen=%1$zu"),
> + deckeylen);
> + return -1;
> + }
> + if (ivlen != 16) {
> + virReportError(VIR_ERR_INVALID_ARG,
> + _("AES256CBC initialization vector invalid len=%1$zu"),
> + ivlen);
> + return -1;
> + }
> + /*
> + * Decrypt the data buffer using a decryption key and
> + * initialization vector via the gnutls_cipher_decrypt API
> + * for GNUTLS_CIPHER_AES_256_CBC.
> + */
This comment doesn't really explain anything which wouldn't be obvious
from what you're calling here. In contrast e.g. the padding lenght
determination may not be obvious to the reader which would make a much
better use of a comment.
> + return virCryptoDecryptDataAESgnutls(GNUTLS_CIPHER_AES_256_CBC,
> + deckey, deckeylen, iv, ivlen,
> + data, datalen,
> + plaintext, plaintextlen);
> + case VIR_CRYPTO_CIPHER_NONE:
> + case VIR_CRYPTO_CIPHER_LAST:
> + break;
> + }
> +
> + virReportError(VIR_ERR_INVALID_ARG,
> + _("algorithm=%1$d is not supported"), algorithm);
> + return -1;
> +}
© 2016 - 2025 Red Hat, Inc.