mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-29 17:15:23 +00:00
e59b8f96f7
Signed-off-by: Ján Tomko <jtomko@redhat.com> Reviewed-by: Erik Skultety <eskultet@redhat.com>
278 lines
8.0 KiB
C
278 lines
8.0 KiB
C
/*
|
|
* vircrypto.c: cryptographic helper APIs
|
|
*
|
|
* 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
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "vircrypto.h"
|
|
#include "virlog.h"
|
|
#include "virerror.h"
|
|
#include "viralloc.h"
|
|
#include "virrandom.h"
|
|
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/crypto.h>
|
|
|
|
VIR_LOG_INIT("util.crypto");
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_CRYPTO
|
|
|
|
static const char hex[] = "0123456789abcdef";
|
|
|
|
#define VIR_CRYPTO_LARGEST_DIGEST_SIZE VIR_CRYPTO_HASH_SIZE_SHA256
|
|
|
|
|
|
struct virHashInfo {
|
|
gnutls_digest_algorithm_t algorithm;
|
|
size_t hashlen;
|
|
} hashinfo[] = {
|
|
{ GNUTLS_DIG_MD5, VIR_CRYPTO_HASH_SIZE_MD5 },
|
|
{ GNUTLS_DIG_SHA256, VIR_CRYPTO_HASH_SIZE_SHA256 },
|
|
};
|
|
|
|
|
|
G_STATIC_ASSERT(G_N_ELEMENTS(hashinfo) == VIR_CRYPTO_HASH_LAST);
|
|
|
|
ssize_t
|
|
virCryptoHashBuf(virCryptoHash hash,
|
|
const char *input,
|
|
unsigned char *output)
|
|
{
|
|
int rc;
|
|
if (hash >= VIR_CRYPTO_HASH_LAST) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("Unknown crypto hash %d"), hash);
|
|
return -1;
|
|
}
|
|
|
|
rc = gnutls_hash_fast(hashinfo[hash].algorithm, input, strlen(input), output);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to compute hash of data: %s"),
|
|
gnutls_strerror(rc));
|
|
return -1;
|
|
}
|
|
|
|
return hashinfo[hash].hashlen;
|
|
}
|
|
|
|
|
|
int
|
|
virCryptoHashString(virCryptoHash hash,
|
|
const char *input,
|
|
char **output)
|
|
{
|
|
unsigned char buf[VIR_CRYPTO_LARGEST_DIGEST_SIZE];
|
|
ssize_t rc;
|
|
size_t hashstrlen;
|
|
size_t i;
|
|
|
|
if ((rc = virCryptoHashBuf(hash, input, buf)) < 0)
|
|
return -1;
|
|
|
|
hashstrlen = (rc * 2) + 1;
|
|
|
|
*output = g_new0(char, hashstrlen);
|
|
|
|
for (i = 0; i < rc; i++) {
|
|
(*output)[i * 2] = hex[(buf[i] >> 4) & 0xf];
|
|
(*output)[(i * 2) + 1] = hex[buf[i] & 0xf];
|
|
}
|
|
|
|
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:
|
|
return true;
|
|
|
|
case VIR_CRYPTO_CIPHER_NONE:
|
|
case VIR_CRYPTO_CIPHER_LAST:
|
|
break;
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* virCryptoEncryptDataAESgntuls:
|
|
*
|
|
* Performs the AES gnutls encryption
|
|
*
|
|
* Same input as virCryptoEncryptData, except the algorithm 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.
|
|
*
|
|
* NB, we must *always* have at least 1 byte of
|
|
* padding - we can't skip it on multiples of
|
|
* 16, otherwise decoder can't distinguish padded
|
|
* data from non-padded data. Hence datalen + 1
|
|
*/
|
|
ciphertextlen = VIR_ROUND_UP(datalen + 1, 16);
|
|
ciphertext = g_new0(uint8_t, ciphertextlen);
|
|
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;
|
|
}
|
|
|
|
|
|
/* virCryptoEncryptData:
|
|
* @algorithm: algorithm desired for encryption
|
|
* @enckey: encryption key
|
|
* @enckeylen: encryption 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;
|
|
}
|
|
|
|
/*
|
|
* 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_CIPHER_NONE:
|
|
case VIR_CRYPTO_CIPHER_LAST:
|
|
break;
|
|
}
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("algorithm=%d is not supported"), algorithm);
|
|
return -1;
|
|
}
|