diff options
Diffstat (limited to 'lib/crypto-api.c')
-rw-r--r-- | lib/crypto-api.c | 2232 |
1 files changed, 2232 insertions, 0 deletions
diff --git a/lib/crypto-api.c b/lib/crypto-api.c new file mode 100644 index 0000000..7f81011 --- /dev/null +++ b/lib/crypto-api.c @@ -0,0 +1,2232 @@ +/* + * Copyright (C) 2000-2016 Free Software Foundation, Inc. + * Copyright (C) 2016 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS 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 program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "errors.h" +#include <cipher_int.h> +#include <datum.h> +#include <gnutls/crypto.h> +#include <algorithms.h> +#include <random.h> +#include <crypto.h> +#include <fips.h> +#include "crypto-api.h" +#include "iov.h" +#include "intprops.h" + +typedef struct api_cipher_hd_st { + cipher_hd_st ctx_enc; + cipher_hd_st ctx_dec; +} api_cipher_hd_st; + +/** + * gnutls_cipher_init: + * @handle: is a #gnutls_cipher_hd_t type + * @cipher: the encryption algorithm to use + * @key: the key to be used for encryption/decryption + * @iv: the IV to use (if not applicable set NULL) + * + * This function will initialize the @handle context to be usable + * for encryption/decryption of data. This will effectively use the + * current crypto backend in use by gnutls or the cryptographic + * accelerator in use. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_cipher_init(gnutls_cipher_hd_t * handle, + gnutls_cipher_algorithm_t cipher, + const gnutls_datum_t * key, const gnutls_datum_t * iv) +{ + api_cipher_hd_st *h; + int ret; + const cipher_entry_st* e; + bool not_approved = false; + + if (!is_cipher_algo_allowed(cipher)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_cipher_algo_approved_in_fips(cipher)) { + not_approved = true; + } + + e = cipher_to_entry(cipher); + if (e == NULL || (e->flags & GNUTLS_CIPHER_FLAG_ONLY_AEAD)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + h = gnutls_calloc(1, sizeof(api_cipher_hd_st)); + if (h == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return GNUTLS_E_MEMORY_ERROR; + } + + ret = + _gnutls_cipher_init(&h->ctx_enc, e, key, + iv, 1); + if (ret < 0) { + gnutls_free(h); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return ret; + } + + if (_gnutls_cipher_type(e) == CIPHER_BLOCK) { + ret = + _gnutls_cipher_init(&h->ctx_dec, e, key, iv, 0); + if (ret < 0) { + gnutls_free(h); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return ret; + } + } + + *handle = h; + + if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + + return ret; +} + +/** + * gnutls_cipher_tag: + * @handle: is a #gnutls_cipher_hd_t type + * @tag: will hold the tag + * @tag_size: the length of the tag to return + * + * This function operates on authenticated encryption with + * associated data (AEAD) ciphers and will return the + * output tag. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.0 + **/ +int +gnutls_cipher_tag(gnutls_cipher_hd_t handle, void *tag, size_t tag_size) +{ + api_cipher_hd_st *h = handle; + + if (_gnutls_cipher_is_aead(&h->ctx_enc) == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + _gnutls_cipher_tag(&h->ctx_enc, tag, tag_size); + + return 0; +} + +/** + * gnutls_cipher_add_auth: + * @handle: is a #gnutls_cipher_hd_t type + * @ptext: the data to be authenticated + * @ptext_size: the length of the data + * + * This function operates on authenticated encryption with + * associated data (AEAD) ciphers and authenticate the + * input data. This function can only be called once + * and before any encryption operations. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.0 + **/ +int +gnutls_cipher_add_auth(gnutls_cipher_hd_t handle, const void *ptext, + size_t ptext_size) +{ + api_cipher_hd_st *h = handle; + int ret; + + if (_gnutls_cipher_is_aead(&h->ctx_enc) == 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = _gnutls_cipher_auth(&h->ctx_enc, ptext, ptext_size); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + return ret; +} + +/** + * gnutls_cipher_set_iv: + * @handle: is a #gnutls_cipher_hd_t type + * @iv: the IV to set + * @ivlen: the length of the IV + * + * This function will set the IV to be used for the next + * encryption block. + * + * Since: 3.0 + **/ +void +gnutls_cipher_set_iv(gnutls_cipher_hd_t handle, void *iv, size_t ivlen) +{ + api_cipher_hd_st *h = handle; + + if (_gnutls_cipher_setiv(&h->ctx_enc, iv, ivlen) < 0) { + _gnutls_switch_lib_state(LIB_STATE_ERROR); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + + if (_gnutls_cipher_type(h->ctx_enc.e) == CIPHER_BLOCK) { + if (_gnutls_cipher_setiv(&h->ctx_dec, iv, ivlen) < 0) { + _gnutls_switch_lib_state(LIB_STATE_ERROR); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + } +} + +/*- + * _gnutls_cipher_get_iv: + * @handle: is a #gnutls_cipher_hd_t type + * @iv: the IV to set + * @ivlen: the length of the IV + * + * This function will retrieve the internally calculated IV value. It is + * intended to be used for modes like CFB. @iv must have @ivlen length + * at least. + * + * This is solely for validation purposes of our crypto + * implementation. For other purposes, the IV can be typically + * calculated from the initial IV value and the subsequent ciphertext + * values. As such, this function only works with the internally + * registered ciphers. + * + * Returns: The length of IV or a negative error code on error. + * + * Since: 3.6.8 + -*/ +int +_gnutls_cipher_get_iv(gnutls_cipher_hd_t handle, void *iv, size_t ivlen) +{ + api_cipher_hd_st *h = handle; + + return _gnutls_cipher_getiv(&h->ctx_enc, iv, ivlen); +} + +/*- + * _gnutls_cipher_set_key: + * @handle: is a #gnutls_cipher_hd_t type + * @key: the key to set + * @keylen: the length of the key + * + * This function will set the key used by the cipher + * + * This is solely for validation purposes of our crypto + * implementation. For other purposes, the key should be set at the time of + * cipher setup. As such, this function only works with the internally + * registered ciphers. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.14 + -*/ +int +_gnutls_cipher_set_key(gnutls_cipher_hd_t handle, void *key, size_t keylen) +{ + api_cipher_hd_st *h = handle; + int ret; + + ret = _gnutls_cipher_setkey(&h->ctx_enc, key, keylen); + + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + return ret; +} + +/** + * gnutls_cipher_encrypt: + * @handle: is a #gnutls_cipher_hd_t type + * @ptext: the data to encrypt + * @ptext_len: the length of data to encrypt + * + * This function will encrypt the given data using the algorithm + * specified by the context. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_cipher_encrypt(gnutls_cipher_hd_t handle, void *ptext, + size_t ptext_len) +{ + api_cipher_hd_st *h = handle; + int ret; + + ret = _gnutls_cipher_encrypt(&h->ctx_enc, ptext, ptext_len); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_cipher_decrypt: + * @handle: is a #gnutls_cipher_hd_t type + * @ctext: the data to decrypt + * @ctext_len: the length of data to decrypt + * + * This function will decrypt the given data using the algorithm + * specified by the context. + * + * Note that in AEAD ciphers, this will not check the tag. You will + * need to compare the tag sent with the value returned from gnutls_cipher_tag(). + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_cipher_decrypt(gnutls_cipher_hd_t handle, void *ctext, + size_t ctext_len) +{ + api_cipher_hd_st *h = handle; + int ret; + + if (_gnutls_cipher_type(h->ctx_enc.e) != CIPHER_BLOCK) { + ret = _gnutls_cipher_decrypt(&h->ctx_enc, ctext, + ctext_len); + } else { + ret = _gnutls_cipher_decrypt(&h->ctx_dec, ctext, + ctext_len); + } + + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_cipher_encrypt2: + * @handle: is a #gnutls_cipher_hd_t type + * @ptext: the data to encrypt + * @ptext_len: the length of data to encrypt + * @ctext: the encrypted data + * @ctext_len: the available length for encrypted data + * + * This function will encrypt the given data using the algorithm + * specified by the context. For block ciphers the @ptext_len must be + * a multiple of the block size. For the supported ciphers the encrypted + * data length will equal the plaintext size. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.12.0 + **/ +int +gnutls_cipher_encrypt2(gnutls_cipher_hd_t handle, const void *ptext, + size_t ptext_len, void *ctext, + size_t ctext_len) +{ + api_cipher_hd_st *h = handle; + int ret; + + ret = _gnutls_cipher_encrypt2(&h->ctx_enc, ptext, ptext_len, + ctext, ctext_len); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_cipher_decrypt2: + * @handle: is a #gnutls_cipher_hd_t type + * @ctext: the data to decrypt + * @ctext_len: the length of data to decrypt + * @ptext: the decrypted data + * @ptext_len: the available length for decrypted data + * + * This function will decrypt the given data using the algorithm + * specified by the context. For block ciphers the @ctext_len must be + * a multiple of the block size. For the supported ciphers the plaintext + * data length will equal the ciphertext size. + * + * Note that in AEAD ciphers, this will not check the tag. You will + * need to compare the tag sent with the value returned from gnutls_cipher_tag(). + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.12.0 + **/ +int +gnutls_cipher_decrypt2(gnutls_cipher_hd_t handle, const void *ctext, + size_t ctext_len, void *ptext, size_t ptext_len) +{ + api_cipher_hd_st *h = handle; + int ret; + + if (_gnutls_cipher_type(h->ctx_enc.e) != CIPHER_BLOCK) { + ret = _gnutls_cipher_decrypt2(&h->ctx_enc, ctext, + ctext_len, ptext, + ptext_len); + } else { + ret = _gnutls_cipher_decrypt2(&h->ctx_dec, ctext, + ctext_len, ptext, + ptext_len); + } + + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_cipher_encrypt3: + * @handle: is a #gnutls_cipher_hd_t type + * @ptext: the data to encrypt + * @ptext_len: the length of data to encrypt + * @ctext: the encrypted data + * @ctext_len: the length of encrypted data (initially must hold the maximum available size) + * @flags: flags for padding + * + * This function will encrypt the given data using the algorithm + * specified by the context. For block ciphers, @ptext_len is + * typically a multiple of the block size. If not, the caller can + * instruct the function to pad the last block according to @flags. + * Currently, the only available padding scheme is + * %GNUTLS_CIPHER_PADDING_PKCS7. + * + * If @ctext is not %NULL, it must hold enough space to store + * resulting cipher text. To check the required size, this function + * can be called with @ctext set to %NULL. Then @ctext_len will be + * updated without performing actual encryption. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.7.7 + **/ +int +gnutls_cipher_encrypt3(gnutls_cipher_hd_t handle, + const void *ptext, size_t ptext_len, + void *ctext, size_t *ctext_len, + unsigned flags) +{ + api_cipher_hd_st *h = handle; + const cipher_entry_st *e = h->ctx_enc.e; + int block_size = _gnutls_cipher_get_block_size(e); + int ret = 0; + + if (unlikely(ctext_len == NULL)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (_gnutls_cipher_type(e) == CIPHER_BLOCK && + (flags & GNUTLS_CIPHER_PADDING_PKCS7)) { + size_t n, r; + uint8_t last_block[MAX_CIPHER_BLOCK_SIZE]; + const uint8_t *p = ptext; + uint8_t *c = ctext; + + if (!INT_ADD_OK(ptext_len, block_size, &n)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + n = (n / block_size) * block_size; + + if (!ctext) { + *ctext_len = n; + return 0; + } + + if (*ctext_len < n) { + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + } + + /* Encrypt up to the last complete block */ + r = ptext_len % block_size; + + ret = _gnutls_cipher_encrypt2(&h->ctx_enc, + ptext, ptext_len - r, + ctext, ptext_len - r); + if (ret < 0) { + goto error; + } + + /* Encrypt the last block with padding */ + gnutls_memset(last_block, block_size - r, sizeof(last_block)); + if (r > 0) { + memcpy(last_block, &p[ptext_len - r], r); + } + ret = _gnutls_cipher_encrypt2(&h->ctx_enc, + last_block, block_size, + &c[ptext_len - r], block_size); + if (ret < 0) { + goto error; + } + *ctext_len = n; + } else { + if (!ctext) { + *ctext_len = ptext_len; + return 0; + } + + ret = _gnutls_cipher_encrypt2(&h->ctx_enc, ptext, ptext_len, + ctext, *ctext_len); + if (ret < 0) { + goto error; + } + *ctext_len = ptext_len; + } + + error: + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_cipher_decrypt3: + * @handle: is a #gnutls_cipher_hd_t type + * @ctext: the data to decrypt + * @ctext_len: the length of data to decrypt + * @ptext: the decrypted data + * @ptext_len: the available length for decrypted data + * @flags: flags for padding + * + * This function will decrypt the given data using the algorithm + * specified by the context. If @flags is specified, padding for the + * decrypted data will be removed accordingly and @ptext_len will be + * updated. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.7.7 + **/ +int +gnutls_cipher_decrypt3(gnutls_cipher_hd_t handle, + const void *ctext, size_t ctext_len, + void *ptext, size_t *ptext_len, + unsigned flags) +{ + api_cipher_hd_st *h = handle; + int ret; + + ret = gnutls_cipher_decrypt2(handle, + ctext, ctext_len, + ptext, *ptext_len); + if (ret < 0) { + return ret; + } + + if (_gnutls_cipher_type(h->ctx_enc.e) == CIPHER_BLOCK && + (flags & GNUTLS_CIPHER_PADDING_PKCS7)) { + uint8_t *p = ptext; + uint8_t padding = p[*ptext_len - 1]; + if (!padding || padding > _gnutls_cipher_get_block_size(h->ctx_enc.e)) { + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + /* Check that the prior bytes are all PADDING */ + for (size_t i = *ptext_len - padding; i < *ptext_len; i++) { + if (padding != p[*ptext_len - 1]) { + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + } + *ptext_len -= padding; + } + + return 0; +} + +/** + * gnutls_cipher_deinit: + * @handle: is a #gnutls_cipher_hd_t type + * + * This function will deinitialize all resources occupied by the given + * encryption context. + * + * Since: 2.10.0 + **/ +void gnutls_cipher_deinit(gnutls_cipher_hd_t handle) +{ + api_cipher_hd_st *h = handle; + + _gnutls_cipher_deinit(&h->ctx_enc); + if (_gnutls_cipher_type(h->ctx_enc.e) == CIPHER_BLOCK) + _gnutls_cipher_deinit(&h->ctx_dec); + gnutls_free(handle); +} + + +/* HMAC */ + + +/** + * gnutls_hmac_init: + * @dig: is a #gnutls_hmac_hd_t type + * @algorithm: the HMAC algorithm to use + * @key: the key to be used for encryption + * @keylen: the length of the key + * + * This function will initialize an context that can be used to + * produce a Message Authentication Code (MAC) of data. This will + * effectively use the current crypto backend in use by gnutls or the + * cryptographic accelerator in use. + * + * Note that despite the name of this function, it can be used + * for other MAC algorithms than HMAC. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_hmac_init(gnutls_hmac_hd_t * dig, + gnutls_mac_algorithm_t algorithm, + const void *key, size_t keylen) +{ + int ret; + bool not_approved = false; + + /* MD5 is only allowed internally for TLS */ + if (!is_mac_algo_allowed(algorithm)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_mac_algo_approved_in_fips(algorithm)) { + not_approved = true; + } + + /* Key lengths of less than 112 bits are not approved */ + if (keylen < 14) { + not_approved = true; + } + + *dig = gnutls_malloc(sizeof(mac_hd_st)); + if (*dig == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return GNUTLS_E_MEMORY_ERROR; + } + + ret = _gnutls_mac_init(((mac_hd_st *) * dig), + mac_to_entry(algorithm), key, keylen); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_hmac_set_nonce: + * @handle: is a #gnutls_hmac_hd_t type + * @nonce: the data to set as nonce + * @nonce_len: the length of data + * + * This function will set the nonce in the MAC algorithm. + * + * Since: 3.2.0 + **/ +void +gnutls_hmac_set_nonce(gnutls_hmac_hd_t handle, const void *nonce, + size_t nonce_len) +{ + _gnutls_mac_set_nonce((mac_hd_st *) handle, nonce, nonce_len); +} + +/** + * gnutls_hmac: + * @handle: is a #gnutls_hmac_hd_t type + * @ptext: the data to hash + * @ptext_len: the length of data to hash + * + * This function will hash the given data using the algorithm + * specified by the context. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int gnutls_hmac(gnutls_hmac_hd_t handle, const void *ptext, size_t ptext_len) +{ + int ret; + + ret = _gnutls_mac((mac_hd_st *) handle, ptext, ptext_len); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_hmac_output: + * @handle: is a #gnutls_hmac_hd_t type + * @digest: is the output value of the MAC + * + * This function will output the current MAC value + * and reset the state of the MAC. + * + * Since: 2.10.0 + **/ +void gnutls_hmac_output(gnutls_hmac_hd_t handle, void *digest) +{ + _gnutls_mac_output((mac_hd_st *) handle, digest); +} + +/** + * gnutls_hmac_deinit: + * @handle: is a #gnutls_hmac_hd_t type + * @digest: is the output value of the MAC + * + * This function will deinitialize all resources occupied by + * the given hmac context. + * + * Since: 2.10.0 + **/ +void gnutls_hmac_deinit(gnutls_hmac_hd_t handle, void *digest) +{ + _gnutls_mac_deinit((mac_hd_st *) handle, digest); + gnutls_free(handle); +} + +/** + * gnutls_hmac_get_len: + * @algorithm: the hmac algorithm to use + * + * This function will return the length of the output data + * of the given hmac algorithm. + * + * Returns: The length or zero on error. + * + * Since: 2.10.0 + **/ +unsigned gnutls_hmac_get_len(gnutls_mac_algorithm_t algorithm) +{ + return _gnutls_mac_get_algo_len(mac_to_entry(algorithm)); +} + +/** + * gnutls_hmac_get_key_size: + * @algorithm: the mac algorithm to use + * + * This function will return the size of the key to be used with this + * algorithm. On the algorithms which may accept arbitrary key sizes, + * the returned size is the MAC key size used in the TLS protocol. + * + * Returns: The key size or zero on error. + * + * Since: 3.6.12 + **/ +unsigned gnutls_hmac_get_key_size(gnutls_mac_algorithm_t algorithm) +{ + return _gnutls_mac_get_key_size(mac_to_entry(algorithm)); +} + +/** + * gnutls_hmac_fast: + * @algorithm: the hash algorithm to use + * @key: the key to use + * @keylen: the length of the key + * @ptext: the data to hash + * @ptext_len: the length of data to hash + * @digest: is the output value of the hash + * + * This convenience function will hash the given data and return output + * on a single call. Note, this call will not work for MAC algorithms + * that require nonce (like UMAC or GMAC). + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_hmac_fast(gnutls_mac_algorithm_t algorithm, + const void *key, size_t keylen, + const void *ptext, size_t ptext_len, void *digest) +{ + int ret; + bool not_approved = false; + + if (!is_mac_algo_allowed(algorithm)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_mac_algo_approved_in_fips(algorithm)) { + not_approved = true; + } + + /* Key lengths of less than 112 bits are not approved */ + if (keylen < 14) { + not_approved = true; + } + + ret = _gnutls_mac_fast(algorithm, key, keylen, ptext, ptext_len, + digest); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_hmac_copy: + * @handle: is a #gnutls_hmac_hd_t type + * + * This function will create a copy of MAC context, containing all its current + * state. Copying contexts for MACs registered using + * gnutls_crypto_register_mac() is not supported and will always result in an + * error. In addition to that, some of the MAC implementations do not support + * this operation. Applications should check the return value and provide a + * proper fallback. + * + * Returns: new MAC context or NULL in case of an error. + * + * Since: 3.6.9 + */ +gnutls_hmac_hd_t gnutls_hmac_copy(gnutls_hmac_hd_t handle) +{ + gnutls_hmac_hd_t dig; + + dig = gnutls_malloc(sizeof(mac_hd_st)); + if (dig == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return NULL; + } + + if (_gnutls_mac_copy((const mac_hd_st *) handle, (mac_hd_st *)dig) != GNUTLS_E_SUCCESS) { + gnutls_assert(); + gnutls_free(dig); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return NULL; + } + + return dig; +} + +/* HASH */ + +/** + * gnutls_hash_init: + * @dig: is a #gnutls_hash_hd_t type + * @algorithm: the hash algorithm to use + * + * This function will initialize an context that can be used to + * produce a Message Digest of data. This will effectively use the + * current crypto backend in use by gnutls or the cryptographic + * accelerator in use. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_hash_init(gnutls_hash_hd_t * dig, + gnutls_digest_algorithm_t algorithm) +{ + int ret; + bool not_approved = false; + + if (!is_mac_algo_allowed(DIG_TO_MAC(algorithm))) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_mac_algo_approved_in_fips(DIG_TO_MAC(algorithm))) { + not_approved = true; + } + + *dig = gnutls_malloc(sizeof(digest_hd_st)); + if (*dig == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return GNUTLS_E_MEMORY_ERROR; + } + + ret = _gnutls_hash_init(((digest_hd_st *) * dig), + hash_to_entry(algorithm)); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} + +/** + * gnutls_hash: + * @handle: is a #gnutls_hash_hd_t type + * @ptext: the data to hash + * @ptext_len: the length of data to hash + * + * This function will hash the given data using the algorithm + * specified by the context. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int gnutls_hash(gnutls_hash_hd_t handle, const void *ptext, size_t ptext_len) +{ + int ret; + + ret = _gnutls_hash((digest_hd_st *) handle, ptext, ptext_len); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + return ret; +} + +/** + * gnutls_hash_output: + * @handle: is a #gnutls_hash_hd_t type + * @digest: is the output value of the hash + * + * This function will output the current hash value + * and reset the state of the hash. + * + * Since: 2.10.0 + **/ +void gnutls_hash_output(gnutls_hash_hd_t handle, void *digest) +{ + _gnutls_hash_output((digest_hd_st *) handle, digest); +} + +/** + * gnutls_hash_deinit: + * @handle: is a #gnutls_hash_hd_t type + * @digest: is the output value of the hash + * + * This function will deinitialize all resources occupied by + * the given hash context. + * + * Since: 2.10.0 + **/ +void gnutls_hash_deinit(gnutls_hash_hd_t handle, void *digest) +{ + _gnutls_hash_deinit((digest_hd_st *) handle, digest); + gnutls_free(handle); +} + +/** + * gnutls_hash_get_len: + * @algorithm: the hash algorithm to use + * + * This function will return the length of the output data + * of the given hash algorithm. + * + * Returns: The length or zero on error. + * + * Since: 2.10.0 + **/ +unsigned gnutls_hash_get_len(gnutls_digest_algorithm_t algorithm) +{ + return _gnutls_hash_get_algo_len(hash_to_entry(algorithm)); +} + +/** + * gnutls_hash_fast: + * @algorithm: the hash algorithm to use + * @ptext: the data to hash + * @ptext_len: the length of data to hash + * @digest: is the output value of the hash + * + * This convenience function will hash the given data and return output + * on a single call. + * + * Returns: Zero or a negative error code on error. + * + * Since: 2.10.0 + **/ +int +gnutls_hash_fast(gnutls_digest_algorithm_t algorithm, + const void *ptext, size_t ptext_len, void *digest) +{ + int ret; + bool not_approved = false; + + if (!is_mac_algo_allowed(DIG_TO_MAC(algorithm))) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_mac_algo_approved_in_fips(DIG_TO_MAC(algorithm))) { + not_approved = true; + } + + ret = _gnutls_hash_fast(algorithm, ptext, ptext_len, digest); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } + + return ret; +} + +/** + * gnutls_hash_copy: + * @handle: is a #gnutls_hash_hd_t type + * + * This function will create a copy of Message Digest context, containing all + * its current state. Copying contexts for Message Digests registered using + * gnutls_crypto_register_digest() is not supported and will always result in + * an error. In addition to that, some of the Message Digest implementations do + * not support this operation. Applications should check the return value and + * provide a proper fallback. + * + * Returns: new Message Digest context or NULL in case of an error. + * + * Since: 3.6.9 + */ +gnutls_hash_hd_t gnutls_hash_copy(gnutls_hash_hd_t handle) +{ + gnutls_hash_hd_t dig; + + dig = gnutls_malloc(sizeof(digest_hd_st)); + if (dig == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return NULL; + } + + if (_gnutls_hash_copy((const digest_hd_st *) handle, (digest_hd_st *)dig) != GNUTLS_E_SUCCESS) { + gnutls_assert(); + gnutls_free(dig); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return NULL; + } + + return dig; +} + +/** + * gnutls_key_generate: + * @key: is a pointer to a #gnutls_datum_t which will contain a newly + * created key + * @key_size: the number of bytes of the key + * + * Generates a random key of @key_size bytes. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or an + * error code. + * + * Since: 3.0 + **/ +int gnutls_key_generate(gnutls_datum_t * key, unsigned int key_size) +{ + int ret; + + FAIL_IF_LIB_ERROR; + +#ifdef ENABLE_FIPS140 + /* The FIPS140 approved RNGs are not allowed to be used + * to extract key sizes longer than their original seed. + */ + if (_gnutls_fips_mode_enabled() != 0 && + key_size > FIPS140_RND_KEY_SIZE) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif + + key->size = key_size; + key->data = gnutls_malloc(key->size); + if (!key->data) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + + ret = gnutls_rnd(GNUTLS_RND_RANDOM, key->data, key->size); + if (ret < 0) { + gnutls_assert(); + _gnutls_free_datum(key); + return ret; + } + + return 0; +} + +/* AEAD API */ + +/** + * gnutls_aead_cipher_init: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @cipher: the authenticated-encryption algorithm to use + * @key: The key to be used for encryption + * + * This function will initialize an context that can be used for + * encryption/decryption of data. This will effectively use the + * current crypto backend in use by gnutls or the cryptographic + * accelerator in use. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.4.0 + **/ +int gnutls_aead_cipher_init(gnutls_aead_cipher_hd_t *handle, + gnutls_cipher_algorithm_t cipher, + const gnutls_datum_t *key) +{ + api_aead_cipher_hd_st *h; + const cipher_entry_st *e; + int ret; + bool not_approved = false; + + if (!is_cipher_algo_allowed(cipher)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_cipher_algo_approved_in_fips(cipher)) { + not_approved = true; + } + + e = cipher_to_entry(cipher); + if (e == NULL || e->type != CIPHER_AEAD) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + h = gnutls_calloc(1, sizeof(api_aead_cipher_hd_st)); + if (h == NULL) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return GNUTLS_E_MEMORY_ERROR; + } + + ret = _gnutls_aead_cipher_init(h, cipher, key); + if (ret < 0) { + gnutls_free(h); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return ret; + } + + *handle = h; + + if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + + return ret; +} + +/** + * gnutls_aead_cipher_set_key: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @key: The key to be used for encryption + * + * This function will set a new key without re-initializing the + * context. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.7.5 + **/ +int gnutls_aead_cipher_set_key(gnutls_aead_cipher_hd_t handle, + const gnutls_datum_t *key) +{ + const cipher_entry_st* e; + int ret; + + e = cipher_to_entry(handle->ctx_enc.e->id); + if (e == NULL || e->type != CIPHER_AEAD) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = handle->ctx_enc.setkey(handle->ctx_enc.handle, + key->data, key->size); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + + return ret; +} + +/** + * gnutls_aead_cipher_decrypt: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @nonce: the nonce to set + * @nonce_len: The length of the nonce + * @auth: additional data to be authenticated + * @auth_len: The length of the data + * @tag_size: The size of the tag to use (use zero for the default) + * @ctext: the data to decrypt (including the authentication tag) + * @ctext_len: the length of data to decrypt (includes tag size) + * @ptext: the decrypted data + * @ptext_len: the length of decrypted data (initially must hold the maximum available size) + * + * This function will decrypt the given data using the algorithm + * specified by the context. This function must be provided the complete + * data to be decrypted, including the authentication tag. On several + * AEAD ciphers, the authentication tag is appended to the ciphertext, + * though this is not a general rule. This function will fail if + * the tag verification fails. + * + * Returns: Zero or a negative error code on verification failure or other error. + * + * Since: 3.4.0 + **/ +int +gnutls_aead_cipher_decrypt(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const void *auth, size_t auth_len, + size_t tag_size, + const void *ctext, size_t ctext_len, + void *ptext, size_t *ptext_len) +{ + int ret; + api_aead_cipher_hd_st *h = handle; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(h->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(h->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (unlikely(ctext_len < tag_size)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + + ret = _gnutls_aead_cipher_decrypt(&h->ctx_enc, + nonce, nonce_len, + auth, auth_len, + tag_size, + ctext, ctext_len, + ptext, *ptext_len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + + /* That assumes that AEAD ciphers are stream */ + *ptext_len = ctext_len - tag_size; + + return 0; +} + + +/** + * gnutls_aead_cipher_encrypt: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @nonce: the nonce to set + * @nonce_len: The length of the nonce + * @auth: additional data to be authenticated + * @auth_len: The length of the data + * @tag_size: The size of the tag to use (use zero for the default) + * @ptext: the data to encrypt + * @ptext_len: The length of data to encrypt + * @ctext: the encrypted data including authentication tag + * @ctext_len: the length of encrypted data (initially must hold the maximum available size, including space for tag) + * + * This function will encrypt the given data using the algorithm + * specified by the context. The output data will contain the + * authentication tag. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.4.0 + **/ +int +gnutls_aead_cipher_encrypt(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const void *auth, size_t auth_len, + size_t tag_size, + const void *ptext, size_t ptext_len, + void *ctext, size_t *ctext_len) +{ + api_aead_cipher_hd_st *h = handle; + int ret; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(h->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(h->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (unlikely(*ctext_len < ptext_len + tag_size)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + } + + ret = _gnutls_aead_cipher_encrypt(&h->ctx_enc, + nonce, nonce_len, + auth, auth_len, + tag_size, + ptext, ptext_len, + ctext, *ctext_len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + + /* That assumes that AEAD ciphers are stream */ + *ctext_len = ptext_len + tag_size; + + return 0; +} + +struct iov_store_st { + void *data; + size_t length; + size_t capacity; +}; + +static void iov_store_free(struct iov_store_st *s) +{ + gnutls_free(s->data); +} + +static int iov_store_grow(struct iov_store_st *s, size_t length) +{ + void *new_data; + size_t new_capacity = s->capacity; + + if (INT_ADD_OVERFLOW(new_capacity, length)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + new_capacity += length; + new_data = gnutls_realloc(s->data, new_capacity); + if (!new_data) { + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + s->data = new_data; + s->capacity = new_capacity; + return 0; +} + +static int +append_from_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt) +{ + if (iovcnt > 0) { + int i; + uint8_t *p; + void *new_data; + size_t new_capacity = dst->capacity; + + for (i = 0; i < iovcnt; i++) { + if (INT_ADD_OVERFLOW(new_capacity, iov[i].iov_len)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + new_capacity += iov[i].iov_len; + } + new_data = gnutls_realloc(dst->data, new_capacity); + if (!new_data) { + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + dst->data = new_data; + dst->capacity = new_capacity; + + p = (uint8_t *) dst->data + dst->length; + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len > 0) { + memcpy(p, iov[i].iov_base, iov[i].iov_len); + } + p += iov[i].iov_len; + dst->length += iov[i].iov_len; + } + } + return 0; +} + +static int +copy_to_iov(const uint8_t *data, size_t size, + const giovec_t *iov, int iovcnt) +{ + size_t offset = 0; + int i; + + for (i = 0; i < iovcnt && size > 0; i++) { + size_t to_copy = MIN(size, iov[i].iov_len); + memcpy(iov[i].iov_base, (uint8_t *) data + offset, to_copy); + offset += to_copy; + size -= to_copy; + } + if (size > 0) + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + return 0; +} + +#define IOV_STORE_INIT { NULL, 0, 0 } + +static int +aead_cipher_encryptv_fallback(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + size_t tag_size, + const giovec_t *iov, int iovcnt, + void *ctext, size_t *ctext_len) +{ + struct iov_store_st auth = IOV_STORE_INIT; + struct iov_store_st ptext = IOV_STORE_INIT; + int ret; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(handle->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(handle->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = append_from_iov(&auth, auth_iov, auth_iovcnt); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = append_from_iov(&ptext, iov, iovcnt); + if (ret < 0) { + iov_store_free(&auth); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = gnutls_aead_cipher_encrypt(handle, nonce, nonce_len, + auth.data, auth.length, + tag_size, + ptext.data, ptext.length, + ctext, ctext_len); + iov_store_free(&auth); + iov_store_free(&ptext); + + /* FIPS operation state is set by gnutls_aead_cipher_encrypt */ + return ret; +} + +static int +aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + size_t tag_size, + const giovec_t *iov, int iovcnt, + void *ctext, size_t *ctext_len) +{ + int ret; + uint8_t *dst; + size_t dst_size, total = 0; + uint8_t *p; + size_t len; + size_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(handle->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(handle->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = _gnutls_cipher_setiv(&handle->ctx_enc, nonce, nonce_len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, ret); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + } + + dst = ctext; + dst_size = *ctext_len; + + ret = _gnutls_iov_iter_init(&iter, iov, iovcnt, blocksize); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + len = ret; + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, + p, len, + dst, dst_size); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + DECR_LEN(dst_size, len); + dst += len; + total += len; + } + + if (dst_size < tag_size) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + } + + _gnutls_cipher_tag(&handle->ctx_enc, dst, tag_size); + + total += tag_size; + *ctext_len = total; + + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + return 0; +} + +/** + * gnutls_aead_cipher_encryptv: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @nonce: the nonce to set + * @nonce_len: The length of the nonce + * @auth_iov: additional data to be authenticated + * @auth_iovcnt: The number of buffers in @auth_iov + * @tag_size: The size of the tag to use (use zero for the default) + * @iov: the data to be encrypted + * @iovcnt: The number of buffers in @iov + * @ctext: the encrypted data including authentication tag + * @ctext_len: the length of encrypted data (initially must hold the maximum available size, including space for tag) + * + * This function will encrypt the provided data buffers using the algorithm + * specified by the context. The output data will contain the + * authentication tag. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.3 + **/ +int +gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + size_t tag_size, + const giovec_t *iov, int iovcnt, + void *ctext, size_t *ctext_len) +{ + /* Limitation: this function provides an optimization under the internally registered + * AEAD ciphers. When an AEAD cipher is used registered with gnutls_crypto_register_aead_cipher(), + * then this becomes a convenience function as it missed the lower-level primitives + * necessary for piecemeal encryption. */ + if ((handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_ONLY_AEAD) || + handle->ctx_enc.encrypt == NULL) { + return aead_cipher_encryptv_fallback(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + tag_size, + iov, iovcnt, + ctext, ctext_len); + } else { + return aead_cipher_encryptv(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + tag_size, + iov, iovcnt, + ctext, ctext_len); + } +} + +static int +aead_cipher_encryptv2_fallback(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t *tag_size) +{ + struct iov_store_st auth = IOV_STORE_INIT; + struct iov_store_st ptext = IOV_STORE_INIT; + uint8_t *ptext_data; + size_t ptext_size; + uint8_t *ctext_data; + size_t ctext_size; + uint8_t *_tag; + size_t _tag_size; + int ret; + + if (tag_size == NULL || *tag_size == 0) + _tag_size = _gnutls_cipher_get_tag_size(handle->ctx_enc.e); + else + _tag_size = *tag_size; + + if (_tag_size > (unsigned)_gnutls_cipher_get_tag_size(handle->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = append_from_iov(&auth, auth_iov, auth_iovcnt); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + if (handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_TAG_PREFIXED) { + /* prepend space for tag */ + ret = iov_store_grow(&ptext, _tag_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + ptext.length = _tag_size; + + ret = append_from_iov(&ptext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + /* We must set ptext_data after the above + * grow/append opereations, otherwise it will point to an invalid pointer after realloc. + */ + ptext_data = (uint8_t *) ptext.data + _tag_size; + ptext_size = ptext.length - _tag_size; + } else { + ret = append_from_iov(&ptext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + /* append space for tag */ + ret = iov_store_grow(&ptext, _tag_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + /* We must set ptext_data after the above + * grow/append opereations, otherwise it will point to an invalid pointer after realloc. + */ + ptext_data = ptext.data; + ptext_size = ptext.length; + } + + ctext_size = ptext.capacity; + ret = gnutls_aead_cipher_encrypt(handle, nonce, nonce_len, + auth.data, auth.length, + _tag_size, + ptext_data, ptext_size, + ptext.data, &ctext_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + if (handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_TAG_PREFIXED) { + ctext_data = (uint8_t *)ptext.data + _tag_size; + _tag = ptext.data; + } else { + ctext_data = ptext.data; + _tag = (uint8_t *)ptext.data + ptext_size; + } + + ret = copy_to_iov(ctext_data, ptext_size, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + if (tag != NULL) { + memcpy(tag, _tag, _tag_size); + } + if (tag_size != NULL) { + *tag_size = _tag_size; + } + + error: + iov_store_free(&auth); + iov_store_free(&ptext); + + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + /* FIPS operation state is set by gnutls_aead_cipher_encrypt */ + return ret; +} + +static int +aead_cipher_encryptv2(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t *tag_size) +{ + api_aead_cipher_hd_st *h = handle; + int ret; + uint8_t *p; + size_t len; + size_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + size_t _tag_size; + + if (tag_size == NULL || *tag_size == 0) + _tag_size = _gnutls_cipher_get_tag_size(h->ctx_enc.e); + else + _tag_size = *tag_size; + + if (_tag_size > (unsigned)_gnutls_cipher_get_tag_size(h->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = _gnutls_cipher_setiv(&handle->ctx_enc, nonce, nonce_len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, ret); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + } + + ret = _gnutls_iov_iter_init(&iter, iov, iovcnt, blocksize); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + + len = ret; + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, p, len, p, len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = _gnutls_iov_iter_sync(&iter, p, len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + } + + if (tag != NULL) + _gnutls_cipher_tag(&handle->ctx_enc, tag, _tag_size); + if (tag_size != NULL) + *tag_size = _tag_size; + + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + return 0; +} + +/** + * gnutls_aead_cipher_encryptv2: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @nonce: the nonce to set + * @nonce_len: The length of the nonce + * @auth_iov: additional data to be authenticated + * @auth_iovcnt: The number of buffers in @auth_iov + * @iov: the data to be encrypted + * @iovcnt: The number of buffers in @iov + * @tag: The authentication tag + * @tag_size: The size of the tag to use (use zero for the default) + * + * This is similar to gnutls_aead_cipher_encrypt(), but it performs + * in-place encryption on the provided data buffers. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.10 + **/ +int +gnutls_aead_cipher_encryptv2(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t *tag_size) +{ + /* Limitation: this function provides an optimization under the internally registered + * AEAD ciphers. When an AEAD cipher is used registered with gnutls_crypto_register_aead_cipher(), + * then this becomes a convenience function as it missed the lower-level primitives + * necessary for piecemeal encryption. */ + if ((handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_ONLY_AEAD) || + handle->ctx_enc.encrypt == NULL) { + return aead_cipher_encryptv2_fallback(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + iov, iovcnt, + tag, tag_size); + } else { + return aead_cipher_encryptv2(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + iov, iovcnt, + tag, tag_size); + } +} + +static int +aead_cipher_decryptv2_fallback(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t tag_size) +{ + struct iov_store_st auth = IOV_STORE_INIT; + struct iov_store_st ctext = IOV_STORE_INIT; + uint8_t *ctext_data; + size_t ptext_size; + int ret; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(handle->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(handle->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = append_from_iov(&auth, auth_iov, auth_iovcnt); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + if (handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_TAG_PREFIXED) { + /* prepend tag */ + ret = iov_store_grow(&ctext, tag_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + memcpy(ctext.data, tag, tag_size); + ctext.length += tag_size; + + ret = append_from_iov(&ctext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + /* We must set ctext_data after the above + * grow/append opereations, otherwise it will point to an invalid pointer after realloc. + */ + ctext_data = (uint8_t *) ctext.data + tag_size; + } else { + ret = append_from_iov(&ctext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + /* append tag */ + ret = iov_store_grow(&ctext, tag_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + memcpy((uint8_t *) ctext.data + ctext.length, tag, tag_size); + ctext.length += tag_size; + + /* We must set ctext_data after the above + * grow/append opereations, otherwise it will point to an invalid pointer after realloc. + */ + ctext_data = ctext.data; + } + + ptext_size = ctext.capacity; + ret = gnutls_aead_cipher_decrypt(handle, nonce, nonce_len, + auth.data, auth.length, + tag_size, + ctext.data, ctext.length, + ctext_data, &ptext_size); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + ret = copy_to_iov(ctext.data, ptext_size, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + error: + iov_store_free(&auth); + iov_store_free(&ctext); + + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } + /* FIPS operation state is set by gnutls_aead_cipher_decrypt */ + return ret; +} + +static int +aead_cipher_decryptv2(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t tag_size) +{ + int ret; + uint8_t *p; + size_t len; + ssize_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + uint8_t _tag[MAX_HASH_SIZE]; + + if (tag_size == 0) + tag_size = _gnutls_cipher_get_tag_size(handle->ctx_enc.e); + else if (tag_size > (unsigned)_gnutls_cipher_get_tag_size(handle->ctx_enc.e)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = _gnutls_cipher_setiv(&handle->ctx_enc, nonce, nonce_len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, ret); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + } + + ret = _gnutls_iov_iter_init(&iter, iov, iovcnt, blocksize); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + if (ret == 0) + break; + + len = ret; + ret = _gnutls_cipher_decrypt2(&handle->ctx_enc, p, len, p, len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + + ret = _gnutls_iov_iter_sync(&iter, p, len); + if (unlikely(ret < 0)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(ret); + } + } + + if (tag != NULL) { + _gnutls_cipher_tag(&handle->ctx_enc, _tag, tag_size); + if (gnutls_memcmp(_tag, tag, tag_size) != 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + } + + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + return 0; +} + +/** + * gnutls_aead_cipher_decryptv2: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * @nonce: the nonce to set + * @nonce_len: The length of the nonce + * @auth_iov: additional data to be authenticated + * @auth_iovcnt: The number of buffers in @auth_iov + * @iov: the data to decrypt + * @iovcnt: The number of buffers in @iov + * @tag: The authentication tag + * @tag_size: The size of the tag to use (use zero for the default) + * + * This is similar to gnutls_aead_cipher_decrypt(), but it performs + * in-place encryption on the provided data buffers. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.10 + **/ +int +gnutls_aead_cipher_decryptv2(gnutls_aead_cipher_hd_t handle, + const void *nonce, size_t nonce_len, + const giovec_t *auth_iov, int auth_iovcnt, + const giovec_t *iov, int iovcnt, + void *tag, size_t tag_size) +{ + /* Limitation: this function provides an optimization under the internally registered + * AEAD ciphers. When an AEAD cipher is used registered with gnutls_crypto_register_aead_cipher(), + * then this becomes a convenience function as it missed the lower-level primitives + * necessary for piecemeal encryption. */ + if ((handle->ctx_enc.e->flags & GNUTLS_CIPHER_FLAG_ONLY_AEAD) || + handle->ctx_enc.encrypt == NULL) { + return aead_cipher_decryptv2_fallback(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + iov, iovcnt, + tag, tag_size); + } else { + return aead_cipher_decryptv2(handle, + nonce, nonce_len, + auth_iov, auth_iovcnt, + iov, iovcnt, + tag, tag_size); + } +} + +/** + * gnutls_aead_cipher_deinit: + * @handle: is a #gnutls_aead_cipher_hd_t type. + * + * This function will deinitialize all resources occupied by the given + * authenticated-encryption context. + * + * Since: 3.4.0 + **/ +void gnutls_aead_cipher_deinit(gnutls_aead_cipher_hd_t handle) +{ + _gnutls_aead_cipher_deinit(handle); + gnutls_free(handle); +} + +extern gnutls_crypto_kdf_st _gnutls_kdf_ops; + +/* Same as @gnutls_hkdf_extract but without changing FIPS context */ +int +_gnutls_hkdf_extract(gnutls_mac_algorithm_t mac, + const gnutls_datum_t *key, + const gnutls_datum_t *salt, + void *output) +{ + /* MD5 is only allowed internally for TLS */ + if (!is_mac_algo_allowed(mac)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } + + /* We don't check whether MAC is approved, because HKDF is + * only approved in TLS, which is handled separately. */ + + return _gnutls_kdf_ops.hkdf_extract(mac, key->data, key->size, + salt ? salt->data : NULL, + salt ? salt->size : 0, + output); +} + +/** + * gnutls_hkdf_extract: + * @mac: the mac algorithm used internally + * @key: the initial keying material + * @salt: the optional salt + * @output: the output value of the extract operation + * + * This function will derive a fixed-size key using the HKDF-Extract + * function as defined in RFC 5869. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.13 + */ +int +gnutls_hkdf_extract(gnutls_mac_algorithm_t mac, + const gnutls_datum_t *key, + const gnutls_datum_t *salt, + void *output) +{ + int ret; + + ret = _gnutls_hkdf_extract(mac, key, salt, output); + if (ret < 0) + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + else + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + + return ret; +} + +/* Same as @gnutls_hkdf_expand but without changing FIPS context */ +int +_gnutls_hkdf_expand(gnutls_mac_algorithm_t mac, + const gnutls_datum_t *key, + const gnutls_datum_t *info, + void *output, size_t length) +{ + /* MD5 is only allowed internally for TLS */ + if (!is_mac_algo_allowed(mac)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } + + /* We don't check whether MAC is approved, because HKDF is + * only approved in TLS, which is handled separately. */ + + return _gnutls_kdf_ops.hkdf_expand(mac, key->data, key->size, + info->data, info->size, + output, length); +} + +/** + * gnutls_hkdf_expand: + * @mac: the mac algorithm used internally + * @key: the pseudorandom key created with HKDF-Extract + * @info: the optional informational data + * @output: the output value of the expand operation + * @length: the desired length of the output key + * + * This function will derive a variable length keying material from + * the pseudorandom key using the HKDF-Expand function as defined in + * RFC 5869. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.13 + */ +int +gnutls_hkdf_expand(gnutls_mac_algorithm_t mac, + const gnutls_datum_t *key, + const gnutls_datum_t *info, + void *output, size_t length) +{ + int ret; + + ret = _gnutls_hkdf_expand(mac, key, info, output, length); + if (ret < 0) + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + else + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + + return ret; +} + +/** + * gnutls_pbkdf2: + * @mac: the mac algorithm used internally + * @key: the initial keying material + * @salt: the salt + * @iter_count: the iteration count + * @output: the output value + * @length: the desired length of the output key + * + * This function will derive a variable length keying material from + * a password according to PKCS #5 PBKDF2. + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.13 + */ +int +gnutls_pbkdf2(gnutls_mac_algorithm_t mac, + const gnutls_datum_t *key, + const gnutls_datum_t *salt, + unsigned iter_count, + void *output, size_t length) +{ + int ret; + bool not_approved = false; + + /* MD5 is only allowed internally for TLS */ + if (!is_mac_algo_allowed(mac)) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); + } else if (!is_mac_algo_approved_in_fips(mac)) { + not_approved = true; + } + + ret = _gnutls_kdf_ops.pbkdf2(mac, key->data, key->size, + salt->data, salt->size, iter_count, + output, length); + if (ret < 0) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + } else if (not_approved) { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + } else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED); + } + return ret; +} |