diff options
Diffstat (limited to 'siv_gnutls.c')
-rw-r--r-- | siv_gnutls.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/siv_gnutls.c b/siv_gnutls.c new file mode 100644 index 0000000..aba2bab --- /dev/null +++ b/siv_gnutls.c @@ -0,0 +1,271 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + SIV ciphers using the GnuTLS library + */ + +#include "config.h" + +#include "sysincl.h" + +#include <gnutls/crypto.h> + +#include "logging.h" +#include "memory.h" +#include "siv.h" + +struct SIV_Instance_Record { + gnutls_cipher_algorithm_t algorithm; + gnutls_aead_cipher_hd_t cipher; +}; + +/* ================================================== */ + +static int instance_counter = 0; +static int gnutls_initialised = 0; + +/* ================================================== */ + +static void +init_gnutls(void) +{ + int r; + + if (gnutls_initialised) + return; + + r = gnutls_global_init(); + if (r < 0) + LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r)); + + DEBUG_LOG("Initialised"); + gnutls_initialised = 1; +} + +/* ================================================== */ + +static void +deinit_gnutls(void) +{ + assert(gnutls_initialised); + gnutls_global_deinit(); + gnutls_initialised = 0; + DEBUG_LOG("Deinitialised"); +} + +/* ================================================== */ + +static gnutls_cipher_algorithm_t +get_cipher_algorithm(SIV_Algorithm algorithm) +{ + switch (algorithm) { + case AEAD_AES_SIV_CMAC_256: + return GNUTLS_CIPHER_AES_128_SIV; + default: + return 0; + } +} + +/* ================================================== */ + +SIV_Instance +SIV_CreateInstance(SIV_Algorithm algorithm) +{ + gnutls_cipher_algorithm_t calgo; + SIV_Instance instance; + + calgo = get_cipher_algorithm(algorithm); + if (calgo == 0) + return NULL; + + if (instance_counter == 0) + init_gnutls(); + + /* Check if the cipher is actually supported */ + if (gnutls_cipher_get_tag_size(calgo) == 0) { + if (instance_counter == 0) + deinit_gnutls(); + return NULL; + } + + instance = MallocNew(struct SIV_Instance_Record); + instance->algorithm = calgo; + instance->cipher = NULL; + + instance_counter++; + + return instance; +} + +/* ================================================== */ + +void +SIV_DestroyInstance(SIV_Instance instance) +{ + if (instance->cipher) + gnutls_aead_cipher_deinit(instance->cipher); + Free(instance); + + instance_counter--; + if (instance_counter == 0) + deinit_gnutls(); +} + +/* ================================================== */ + +int +SIV_GetKeyLength(SIV_Algorithm algorithm) +{ + gnutls_cipher_algorithm_t calgo = get_cipher_algorithm(algorithm); + int len; + + if (calgo == 0) + return 0; + + len = gnutls_cipher_get_key_size(calgo); + + if (len < 1 || len > SIV_MAX_KEY_LENGTH) + LOG_FATAL("Invalid key length"); + + return len; +} + +/* ================================================== */ + +int +SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length) +{ + gnutls_aead_cipher_hd_t cipher; + gnutls_datum_t datum; + int r; + + if (length <= 0 || length != gnutls_cipher_get_key_size(instance->algorithm)) + return 0; + + datum.data = (unsigned char *)key; + datum.size = length; + +#ifdef HAVE_GNUTLS_AEAD_CIPHER_SET_KEY + if (instance->cipher) { + r = gnutls_aead_cipher_set_key(instance->cipher, &datum); + if (r < 0) { + DEBUG_LOG("Could not set cipher key : %s", gnutls_strerror(r)); + return 0; + } + + return 1; + } +#endif + + /* Initialise a new cipher with the provided key */ + r = gnutls_aead_cipher_init(&cipher, instance->algorithm, &datum); + if (r < 0) { + DEBUG_LOG("Could not initialise %s : %s", "cipher", gnutls_strerror(r)); + return 0; + } + + /* Destroy the previous cipher (if its key could not be changed directly) */ + if (instance->cipher) + gnutls_aead_cipher_deinit(instance->cipher); + + instance->cipher = cipher; + + return 1; +} + +/* ================================================== */ + +int +SIV_GetTagLength(SIV_Instance instance) +{ + int len; + + len = gnutls_cipher_get_tag_size(instance->algorithm); + + if (len < 1 || len > SIV_MAX_TAG_LENGTH) + LOG_FATAL("Invalid tag length"); + + return len; +} + +/* ================================================== */ + +int +SIV_Encrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const void *plaintext, int plaintext_length, + unsigned char *ciphertext, int ciphertext_length) +{ + size_t clen = ciphertext_length; + + if (!instance->cipher) + return 0; + + if (nonce_length < 1 || assoc_length < 0 || + plaintext_length < 0 || ciphertext_length < 0) + return 0; + + assert(assoc && plaintext); + + if (gnutls_aead_cipher_encrypt(instance->cipher, + nonce, nonce_length, assoc, assoc_length, 0, + plaintext, plaintext_length, ciphertext, &clen) < 0) + return 0; + + if (clen != ciphertext_length) + return 0; + + return 1; +} + +/* ================================================== */ + +int +SIV_Decrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const unsigned char *ciphertext, int ciphertext_length, + void *plaintext, int plaintext_length) +{ + size_t plen = plaintext_length; + + if (!instance->cipher) + return 0; + + if (nonce_length < 1 || assoc_length < 0 || + plaintext_length < 0 || ciphertext_length < 0) + return 0; + + assert(assoc && plaintext); + + if (gnutls_aead_cipher_decrypt(instance->cipher, + nonce, nonce_length, assoc, assoc_length, 0, + ciphertext, ciphertext_length, plaintext, &plen) < 0) + return 0; + + if (plen != plaintext_length) + return 0; + + return 1; +} |