summaryrefslogtreecommitdiffstats
path: root/lib/cipher_int.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cipher_int.c')
-rw-r--r--lib/cipher_int.c475
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);
+}