| /* |
| 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; |
| |
| /* Initialise a new cipher with the provided key (gnutls does not seem to |
| have a function to change the key directly) */ |
| 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; |
| } |
| |
| /* Replace the previous cipher */ |
| 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; |
| } |