diff options
Diffstat (limited to 'lib/cipher_int.c')
-rw-r--r-- | lib/cipher_int.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/lib/cipher_int.c b/lib/cipher_int.c new file mode 100644 index 0000000..6680442 --- /dev/null +++ b/lib/cipher_int.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2009-2013 Free Software Foundation, Inc. + * Copyright (C) 2013 Nikos Mavrogiannopoulos + * + * 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 <crypto.h> +#include <fips.h> +#include <algorithms.h> + +#define SR_FB(x, cleanup) ret=(x); if ( ret<0 ) { \ + if (ret == GNUTLS_E_NEED_FALLBACK) { \ + if (handle->handle) \ + handle->deinit(handle->handle); \ + goto fallback; \ + } \ + gnutls_assert(); \ + ret = GNUTLS_E_INTERNAL_ERROR; \ + goto cleanup; \ + } + +#define SR(x, cleanup) if ( (x)<0 ) { \ + gnutls_assert(); \ + ret = GNUTLS_E_INTERNAL_ERROR; \ + goto cleanup; \ + } + +/* Returns true(non-zero) or false(0) if the + * provided cipher exists + */ +int _gnutls_cipher_exists(gnutls_cipher_algorithm_t cipher) +{ + const gnutls_crypto_cipher_st *cc; + int ret; + + if (!is_cipher_algo_allowed(cipher)) + return 0; + + /* All the other ciphers are disabled on the back-end library. + * The NULL needs to be detected here as it is not a cipher + * that is provided by the back-end. + */ + if (cipher == GNUTLS_CIPHER_NULL) + return 1; + + cc = _gnutls_get_crypto_cipher(cipher); + if (cc != NULL) + return 1; + + ret = _gnutls_cipher_ops.exists(cipher); + return ret; +} + +int +_gnutls_cipher_init(cipher_hd_st *handle, const cipher_entry_st *e, + const gnutls_datum_t *key, const gnutls_datum_t *iv, + int enc) +{ + int ret = GNUTLS_E_INTERNAL_ERROR; + const gnutls_crypto_cipher_st *cc = NULL; + + if (unlikely(e == NULL || e->id == GNUTLS_CIPHER_NULL)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + FAIL_IF_LIB_ERROR; + + handle->e = e; + handle->handle = NULL; + + /* check if a cipher has been registered + */ + cc = _gnutls_get_crypto_cipher(e->id); + if (cc != NULL) { + handle->encrypt = cc->encrypt; + handle->decrypt = cc->decrypt; + handle->aead_encrypt = cc->aead_encrypt; + handle->aead_decrypt = cc->aead_decrypt; + handle->deinit = cc->deinit; + handle->auth = cc->auth; + handle->tag = cc->tag; + handle->setiv = cc->setiv; + handle->getiv = cc->getiv; + handle->setkey = cc->setkey; + + /* if cc->init() returns GNUTLS_E_NEED_FALLBACK we + * use the default ciphers */ + SR_FB(cc->init(e->id, &handle->handle, enc), cc_cleanup); + SR_FB(cc->setkey(handle->handle, key->data, key->size), + cc_cleanup); + if (iv) { + /* the API doesn't accept IV */ + if (unlikely(cc->setiv == NULL)) { + if (cc->aead_encrypt) { + if (handle->handle) + handle->deinit(handle->handle); + goto fallback; + } + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + SR(cc->setiv(handle->handle, iv->data, iv->size), + cc_cleanup); + } + + return 0; + } + + fallback: + handle->encrypt = _gnutls_cipher_ops.encrypt; + handle->decrypt = _gnutls_cipher_ops.decrypt; + handle->aead_encrypt = _gnutls_cipher_ops.aead_encrypt; + handle->aead_decrypt = _gnutls_cipher_ops.aead_decrypt; + handle->deinit = _gnutls_cipher_ops.deinit; + handle->auth = _gnutls_cipher_ops.auth; + handle->tag = _gnutls_cipher_ops.tag; + handle->setiv = _gnutls_cipher_ops.setiv; + handle->getiv = _gnutls_cipher_ops.getiv; + handle->setkey = _gnutls_cipher_ops.setkey; + + /* otherwise use generic cipher interface + */ + ret = _gnutls_cipher_ops.init(e->id, &handle->handle, enc); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + ret = + _gnutls_cipher_ops.setkey(handle->handle, key->data, + key->size); + if (ret < 0) { + gnutls_assert(); + goto cc_cleanup; + } + + if (iv) { + ret = + _gnutls_cipher_ops.setiv(handle->handle, iv->data, + iv->size); + if (ret < 0) { + gnutls_assert(); + goto cc_cleanup; + } + } + + return 0; + + cc_cleanup: + + if (handle->handle) + handle->deinit(handle->handle); + + return ret; +} + +/* Auth_cipher API + */ +int _gnutls_auth_cipher_init(auth_cipher_hd_st * handle, + const cipher_entry_st * e, + const gnutls_datum_t * cipher_key, + const gnutls_datum_t * iv, + const mac_entry_st * me, + const gnutls_datum_t * mac_key, + unsigned etm, +#ifdef ENABLE_SSL3 + unsigned ssl_hmac, +#endif + int enc) +{ + int ret; + + if (unlikely(e == NULL)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + FAIL_IF_LIB_ERROR; + + memset(handle, 0, sizeof(*handle)); + handle->etm = etm; + + if (e->id != GNUTLS_CIPHER_NULL) { + handle->non_null = 1; + ret = + _gnutls_cipher_init(&handle->cipher, e, cipher_key, iv, + enc); + if (ret < 0) + return gnutls_assert_val(ret); + } else + handle->non_null = 0; + + if (me->id != GNUTLS_MAC_AEAD) { + handle->is_mac = 1; +#ifdef ENABLE_SSL3 + handle->ssl_hmac = ssl_hmac; + + if (ssl_hmac) + ret = + _gnutls_mac_init_ssl3(&handle->mac.dig, me, + mac_key->data, + mac_key->size); + else +#endif + ret = + _gnutls_mac_init(&handle->mac.mac, me, + mac_key->data, mac_key->size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } +#ifdef ENABLE_GOST + handle->continuous_mac = !!(me->flags & GNUTLS_MAC_FLAG_CONTINUOUS_MAC); +#endif + + handle->tag_size = _gnutls_mac_get_algo_len(me); + } else if (_gnutls_cipher_algo_is_aead(e)) { + handle->tag_size = _gnutls_cipher_get_tag_size(e); + } else { + gnutls_assert(); + ret = GNUTLS_E_INVALID_REQUEST; + goto cleanup; + } + + return 0; + cleanup: + if (handle->non_null != 0) + _gnutls_cipher_deinit(&handle->cipher); + return ret; + +} + +#ifdef ENABLE_SSL3 +# define MAC(handle, text, textlen) \ + if (handle->ssl_hmac) { \ + ret = \ + _gnutls_hash(&handle->mac.dig, text, textlen); \ + } else { \ + ret = _gnutls_mac(&handle->mac.mac, text, textlen); \ + } \ + if (unlikely(ret < 0)) \ + return gnutls_assert_val(ret) +#else +# define MAC(handle, text, textlen) \ + ret = _gnutls_mac(&handle->mac.mac, text, textlen); \ + if (unlikely(ret < 0)) \ + return gnutls_assert_val(ret) +#endif + +int _gnutls_auth_cipher_add_auth(auth_cipher_hd_st * handle, + const void *text, int textlen) +{ + int ret; + + if (handle->is_mac) { + MAC(handle, text, textlen); + } else if (_gnutls_cipher_is_aead(&handle->cipher)) + return _gnutls_cipher_auth(&handle->cipher, text, textlen); + return 0; +} + + +/* The caller must make sure that textlen+pad_size+tag_size is divided by the block size of the cipher */ +int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, + const uint8_t * text, int textlen, + void *_ciphertext, int ciphertextlen, + int pad_size) +{ + int ret; + uint8_t *ciphertext = _ciphertext; + unsigned blocksize = + _gnutls_cipher_get_block_size(handle->cipher.e); + unsigned l; + + assert(ciphertext != NULL); + + if (handle->is_mac) { /* cipher + mac */ + if (handle->non_null == 0) { /* NULL cipher + MAC */ + MAC(handle, text, textlen); + + if (unlikely(textlen + pad_size + handle->tag_size) > + ciphertextlen) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (text != ciphertext) + memcpy(ciphertext, text, textlen); + ret = + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + + } else { + uint8_t *orig_ciphertext = ciphertext; + + if (handle->etm == 0 || handle->cipher.e->type != CIPHER_BLOCK) { + MAC(handle, text, textlen); + } + + if (unlikely(textlen + pad_size + handle->tag_size) > + ciphertextlen) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + assert(blocksize != 0); + l = (textlen / blocksize) * blocksize; + if (l > 0) { + ret = + _gnutls_cipher_encrypt2(&handle->cipher, text, + l, ciphertext, + ciphertextlen); + if (ret < 0) + return gnutls_assert_val(ret); + + textlen -= l; + text += l; + ciphertext += l; + ciphertextlen -= l; + } + + if (ciphertext != text && textlen > 0) + memcpy(ciphertext, text, textlen); + + if (handle->etm == 0 || handle->cipher.e->type != CIPHER_BLOCK) { + ret = + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + textlen += handle->tag_size; + } + + /* TLS 1.0 style padding */ + if (pad_size > 0) { + memset(ciphertext + textlen, pad_size - 1, + pad_size); + textlen += pad_size; + } + + ret = + _gnutls_cipher_encrypt2(&handle->cipher, + ciphertext, textlen, + ciphertext, + ciphertextlen); + if (ret < 0) + return gnutls_assert_val(ret); + + if (handle->etm != 0 && handle->cipher.e->type == CIPHER_BLOCK) { + MAC(handle, orig_ciphertext, l); + MAC(handle, ciphertext, textlen); + + ret = + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + } + } + } else if (_gnutls_cipher_is_aead(&handle->cipher)) { + ret = + _gnutls_cipher_encrypt2(&handle->cipher, text, textlen, + ciphertext, ciphertextlen); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + ret = + _gnutls_auth_cipher_tag(handle, ciphertext + textlen, + handle->tag_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } else if (handle->non_null == 0 && text != ciphertext) /* NULL cipher - no MAC */ + memcpy(ciphertext, text, textlen); + + return 0; +} + +int _gnutls_auth_cipher_decrypt2(auth_cipher_hd_st * handle, + const void *ciphertext, int ciphertextlen, + void *text, int textlen) +{ + int ret; + + if (unlikely(ciphertextlen > textlen)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (handle->is_mac && (handle->etm != 0 && handle->cipher.e->type == CIPHER_BLOCK)) { + /* The MAC is not to be hashed */ + ciphertextlen -= handle->tag_size; + + MAC(handle, ciphertext, ciphertextlen); + } + + if (handle->non_null != 0) { + ret = + _gnutls_cipher_decrypt2(&handle->cipher, ciphertext, + ciphertextlen, text, textlen); + if (ret < 0) + return gnutls_assert_val(ret); + } else if (handle->non_null == 0 && text != ciphertext) + memcpy(text, ciphertext, ciphertextlen); + + if (handle->is_mac && (handle->etm == 0 || handle->cipher.e->type != CIPHER_BLOCK)) { + /* The MAC is not to be hashed */ + ciphertextlen -= handle->tag_size; + + MAC(handle, text, ciphertextlen); + } + + return 0; +} + +int _gnutls_auth_cipher_tag(auth_cipher_hd_st * handle, void *tag, + int tag_size) +{ + if (handle->is_mac) { +#ifdef ENABLE_SSL3 + if (handle->ssl_hmac) { + int ret = + _gnutls_mac_output_ssl3(&handle->mac.dig, tag); + if (ret < 0) + return gnutls_assert_val(ret); + } else +#endif +#ifdef ENABLE_GOST + /* draft-smyshlyaev-tls12-gost-suites section 4.1.2 */ + if (handle->continuous_mac) { + mac_hd_st temp_mac; + int ret = _gnutls_mac_copy(&handle->mac.mac, &temp_mac); + if (ret < 0) + return gnutls_assert_val(ret); + _gnutls_mac_deinit(&temp_mac, tag); + } else +#endif + _gnutls_mac_output(&handle->mac.mac, tag); + } else if (_gnutls_cipher_is_aead(&handle->cipher)) { + _gnutls_cipher_tag(&handle->cipher, tag, tag_size); + } else + memset(tag, 0, tag_size); + + return 0; +} + +void _gnutls_auth_cipher_deinit(auth_cipher_hd_st * handle) +{ + if (handle->is_mac) { +#ifdef ENABLE_SSL3 + if (handle->ssl_hmac) /* failure here doesn't matter */ + _gnutls_mac_deinit_ssl3(&handle->mac.dig, NULL); + else +#endif + _gnutls_mac_deinit(&handle->mac.mac, NULL); + } + if (handle->non_null != 0) + _gnutls_cipher_deinit(&handle->cipher); +} |