summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@gnutls.org>2018-06-02 21:25:10 +0200
committerNikos Mavrogiannopoulos <nmav@redhat.com>2018-06-14 16:30:45 +0200
commit9a714f6047e18a30d0c2de771933e0f297f4b58f (patch)
tree908faf326e5551cf44fc27c514f565e51407d3f1
parentaa54b849c102e9cc433201f849f1e8ad029a9a80 (diff)
downloadgnutls-9a714f6047e18a30d0c2de771933e0f297f4b58f.tar.gz
gnutls_aead_cipher_encryptv: introduced
This API allows encryption using a scatter input, by also taking advantage of ciphers which are optimized for such input. That is particularly useful under TLS1.3 since its encryption is based on encryption of scattered data (data+pad). Resolves #458 Signed-off-by: Nikos Mavrogiannopoulos <nmav@gnutls.org>
-rw-r--r--doc/cha-crypto.texi32
-rw-r--r--lib/crypto-api.c249
-rw-r--r--lib/crypto-selftests.c194
-rw-r--r--lib/includes/gnutls/crypto.h8
-rw-r--r--lib/libgnutls.map1
5 files changed, 474 insertions, 10 deletions
diff --git a/doc/cha-crypto.texi b/doc/cha-crypto.texi
index dc28a42a94..ab5f9ac15f 100644
--- a/doc/cha-crypto.texi
+++ b/doc/cha-crypto.texi
@@ -23,11 +23,37 @@ to the random number generation. For a low-level crypto API the usage of nettle
@cindex symmetric cryptography
The available functions to access symmetric crypto algorithms operations
-are shown below. The supported algorithms are the algorithms required by the TLS protocol.
-They are listed in @ref{gnutls_cipher_algorithm_t}.
+are listed in the sections below. The supported algorithms are the algorithms required by the TLS protocol.
+They are listed in @ref{gnutls_cipher_algorithm_t}. Note that there two
+types of ciphers, the ones providing an authenticated-encryption with
+associated data (AEAD), and the legacy ciphers which provide raw access
+to the ciphers. We recommend the use of the AEAD APIs for new applications
+as it is designed to minimize misuse of cryptography.
@showenumdesc{gnutls_cipher_algorithm_t,The supported ciphers.}
+@subheading Authenticated-encryption API
+
+The AEAD API provides access to all ciphers supported by GnuTLS which support
+authenticated encryption with associated data. That is particularly suitable
+for message or packet-encryption as it provides authentication and
+encryption on the same API. See @code{RFC5116} for more information on
+authenticated encryption.
+
+@showfuncD{gnutls_aead_cipher_init,gnutls_aead_cipher_encrypt,gnutls_aead_cipher_decrypt,gnutls_aead_cipher_deinit}
+
+Because the encryption function above may be difficult to use with
+scattered data, we provide the following API.
+
+@showfuncdesc{gnutls_aead_cipher_encryptv}
+
+@subheading Legacy API
+
+The legacy API provides low-level access to all legacy ciphers supported by GnuTLS,
+and some of the AEAD ciphers (e.g., AES-GCM and CHACHA20). The restrictions
+of the nettle library implementation of the ciphers apply verbatim to this
+API@footnote{See the nettle manual @url{https://www.lysator.liu.se/~nisse/nettle/nettle.html}}.
+
@showfuncE{gnutls_cipher_init,gnutls_cipher_encrypt2,gnutls_cipher_decrypt2,gnutls_cipher_set_iv,gnutls_cipher_deinit}
@showfuncB{gnutls_cipher_add_auth,gnutls_cipher_tag}
@@ -36,8 +62,6 @@ it is recommended to use the following functions which are solely for AEAD ciphe
API is designed to be simple to use and also hard to misuse, by handling the tag verification
and addition in transparent way.
-@showfuncD{gnutls_aead_cipher_init,gnutls_aead_cipher_encrypt,gnutls_aead_cipher_decrypt,gnutls_aead_cipher_deinit}
-
@node Public key algorithms
@section Public key algorithms
@cindex public key algorithms
diff --git a/lib/crypto-api.c b/lib/crypto-api.c
index 841eb8c541..c3d367b542 100644
--- a/lib/crypto-api.c
+++ b/lib/crypto-api.c
@@ -777,6 +777,255 @@ gnutls_aead_cipher_encrypt(gnutls_aead_cipher_hd_t handle,
return 0;
}
+struct iov_store_st {
+ void *data;
+ size_t size;
+ unsigned allocated;
+};
+
+static void iov_store_free(struct iov_store_st *s)
+{
+ if (s->allocated) {
+ gnutls_free(s->data);
+ s->allocated = 0;
+ }
+}
+
+static int copy_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt)
+{
+ memset(dst, 0, sizeof(*dst));
+ if (iovcnt == 0) {
+ return 0;
+ } else if (iovcnt == 1) {
+ dst->data = iov[0].iov_base;
+ dst->size = iov[0].iov_len;
+ /* implies: dst->allocated = 0; */
+ return 0;
+ } else {
+ int i;
+ uint8_t *p;
+
+ dst->size = 0;
+ for (i=0;i<iovcnt;i++)
+ dst->size += iov[i].iov_len;
+ dst->data = gnutls_malloc(dst->size);
+ if (dst->data == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ p = dst->data;
+ for (i=0;i<iovcnt;i++) {
+ memcpy(p, iov[i].iov_base, iov[i].iov_len);
+ p += iov[i].iov_len;
+ }
+
+ dst->allocated = 1;
+ return 0;
+ }
+}
+
+#define AUTH_UPDATE_FINAL(ctx) do { \
+ if (index) { \
+ ret = _gnutls_cipher_auth(ctx, cache, index); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ } \
+ } while(0)
+
+#define AUTH_UPDATE(ctx, data, length) do { \
+ if (index) { \
+ unsigned left = blocksize - index; \
+ if (length < left) { \
+ memcpy(cache+index, data, \
+ length); \
+ index += length; \
+ goto __update_done; \
+ } else { \
+ memcpy(cache+index, data, left); \
+ ret = _gnutls_cipher_auth(ctx, cache, blocksize); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ data += left; \
+ length -= left; \
+ } \
+ } \
+ if (length >= blocksize) { \
+ unsigned to_proc = (length/blocksize)*blocksize; \
+ ret = _gnutls_cipher_auth(ctx, data, to_proc); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ data += to_proc; \
+ length -= to_proc; \
+ } \
+ if (length) \
+ memcpy(cache, data, length); \
+ index = length; \
+ __update_done: \
+ ; \
+ } while(0)
+
+#define ENCRYPT_FINAL(ctx, dst, dst_size) do { \
+ if (index) { \
+ if (unlikely(dst_size < index)) \
+ return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); \
+ ret = _gnutls_cipher_encrypt2(ctx, cache, index, dst, dst_size); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ dst += index; \
+ dst_size -= index; \
+ } \
+ } while(0)
+
+#define ENCRYPT(ctx, data, length, dst, dst_size) do { \
+ if (index) { \
+ unsigned left = blocksize - index; \
+ if (length < left) { \
+ memcpy(cache+index, data, \
+ length); \
+ index += length; \
+ goto __encrypt_done; \
+ } else { \
+ if (unlikely(dst_size < blocksize)) \
+ return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); \
+ memcpy(cache+index, data, left); \
+ ret = _gnutls_cipher_encrypt2(ctx, cache, blocksize, dst, dst_size); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ data += left; \
+ length -= left; \
+ dst += blocksize; \
+ dst_size -= blocksize; \
+ } \
+ } \
+ if (length >= blocksize) { \
+ unsigned to_proc = (length/blocksize)*blocksize; \
+ if (unlikely(dst_size < to_proc)) \
+ return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); \
+ ret = _gnutls_cipher_encrypt2(ctx, data, to_proc, dst, dst_size); \
+ if (unlikely(ret < 0)) \
+ return gnutls_assert_val(ret); \
+ data += to_proc; \
+ length -= to_proc; \
+ dst += to_proc; \
+ dst_size -= to_proc; \
+ } \
+ if (length) \
+ memcpy(cache, data, length); \
+ index = length; \
+ __encrypt_done: \
+ ; \
+ } while(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: the 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
+ * @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)
+{
+ api_aead_cipher_hd_st *h = handle;
+ int ret;
+ uint8_t *dst;
+ ssize_t dst_size, total = 0, len;
+ uint8_t *p;
+ unsigned i;
+ uint8_t cache[MAX_CIPHER_BLOCK_SIZE];
+ unsigned index;
+ unsigned blocksize = handle->ctx_enc.e->blocksize;
+
+ /* 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 (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))
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+
+ if (handle->ctx_enc.e->only_aead || handle->ctx_enc.encrypt == NULL) {
+ /* ciphertext cannot be produced in a piecemeal approach */
+ struct iov_store_st auth;
+ struct iov_store_st ptext;
+
+ ret = copy_iov(&auth, auth_iov, auth_iovcnt);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = copy_iov(&ptext, iov, iovcnt);
+ if (ret < 0) {
+ iov_store_free(&auth);
+ return gnutls_assert_val(ret);
+ }
+
+ ret = gnutls_aead_cipher_encrypt(handle, nonce, nonce_len,
+ auth.data, auth.size,
+ tag_size,
+ ptext.data, ptext.size,
+ ctext, ctext_len);
+ iov_store_free(&auth);
+ iov_store_free(&ptext);
+
+ return ret;
+ }
+
+ ret = _gnutls_cipher_setiv(&handle->ctx_enc, nonce, nonce_len);
+ if (unlikely(ret < 0))
+ return gnutls_assert_val(ret);
+
+ index = 0;
+ for (i = 0; i < (unsigned)auth_iovcnt; i++) {
+ p = auth_iov[i].iov_base;
+ len = auth_iov[i].iov_len;
+ AUTH_UPDATE(&handle->ctx_enc, p, len);
+ }
+ AUTH_UPDATE_FINAL(&handle->ctx_enc);
+
+ dst = ctext;
+ dst_size = *ctext_len;
+
+ index = 0;
+ for (i = 0; i < (unsigned)iovcnt; i++) {
+ p = iov[i].iov_base;
+ len = iov[i].iov_len;
+ ENCRYPT(&handle->ctx_enc, p, len, dst, dst_size);
+ total += iov[i].iov_len;
+ }
+ ENCRYPT_FINAL(&handle->ctx_enc, dst, dst_size);
+
+ if ((size_t)dst_size < tag_size)
+ return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
+
+ _gnutls_cipher_tag(&handle->ctx_enc, dst, tag_size);
+
+ total += tag_size;
+ *ctext_len = total;
+
+ return 0;
+}
+
/**
* gnutls_aead_cipher_deinit:
* @handle: is a #gnutls_aead_cipher_hd_t type.
diff --git a/lib/crypto-selftests.c b/lib/crypto-selftests.c
index a637223b0a..1e51dfd6c3 100644
--- a/lib/crypto-selftests.c
+++ b/lib/crypto-selftests.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Red Hat
+ * Copyright (C) 2013-2018 Red Hat
*
* Author: Nikos Mavrogiannopoulos
*
@@ -660,6 +660,191 @@ static int test_cipher_aead_compat(gnutls_cipher_algorithm_t cipher,
}
+#define IOV_PARTS 8
+/* AEAD modes - scatter read */
+static int test_cipher_aead_scatter(gnutls_cipher_algorithm_t cipher,
+ const struct cipher_aead_vectors_st *vectors,
+ size_t vectors_size, unsigned flags)
+{
+ gnutls_aead_cipher_hd_t hd;
+ int ret;
+ unsigned int i, z;
+ uint8_t tmp[384];
+ gnutls_datum_t key, iv;
+ size_t s;
+ unsigned tag_size;
+ giovec_t auth_iov[IOV_PARTS];
+ int auth_iov_len;
+ int iov_len;
+ giovec_t iov[IOV_PARTS];
+
+ _gnutls_debug_log("running scatter (iovec) tests for: %s\n",
+ gnutls_cipher_get_name(cipher));
+
+ for (i = 0; i < vectors_size; i++) {
+ memset(tmp, 0, sizeof(tmp));
+ key.data = (void *) vectors[i].key;
+ key.size = vectors[i].key_size;
+
+ iv.data = (void *) vectors[i].iv;
+ iv.size = vectors[i].iv_size;
+ tag_size = vectors[i].tag_size;
+
+ if (tag_size > gnutls_cipher_get_tag_size(cipher)) {
+ return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ ret = gnutls_aead_cipher_init(&hd, cipher, &key);
+ if (ret < 0) {
+ _gnutls_debug_log("error initializing: %s\n",
+ gnutls_cipher_get_name(cipher));
+ return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ s = sizeof(tmp);
+
+ /* single vector */
+ auth_iov_len = 1;
+ auth_iov[0].iov_base = (void*)vectors[i].auth;
+ auth_iov[0].iov_len = vectors[i].auth_size;
+
+ iov_len = 1;
+ iov[0].iov_base = (void*)vectors[i].plaintext;
+ iov[0].iov_len = vectors[i].plaintext_size;
+
+ ret =
+ gnutls_aead_cipher_encryptv(hd,
+ iv.data, iv.size,
+ auth_iov, auth_iov_len,
+ vectors[i].tag_size,
+ iov, iov_len,
+ tmp, &s);
+ if (ret < 0)
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+
+ if (s != vectors[i].plaintext_size + tag_size) {
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ if (memcmp(tmp+vectors[i].plaintext_size, vectors[i].tag, tag_size) != 0) {
+ _gnutls_debug_log
+ ("%s test vector %d failed (tag)!\n",
+ gnutls_cipher_get_name(cipher), i);
+ return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ if (vectors[i].plaintext_size > 0) {
+ if (memcmp
+ (tmp, vectors[i].ciphertext,
+ vectors[i].plaintext_size) != 0) {
+ _gnutls_debug_log
+ ("%s test vector %d failed!\n",
+ gnutls_cipher_get_name(cipher), i);
+
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+ }
+ }
+
+ /* multi-vector */
+ auth_iov_len = 0;
+ if (vectors[i].auth_size > IOV_PARTS) {
+ unsigned split = vectors[i].auth_size / IOV_PARTS;
+ assert(split>0);
+ for (z=0;z<IOV_PARTS;z++) {
+ auth_iov[z].iov_base = (void*)(vectors[i].auth+(z*split));
+ if (z==IOV_PARTS-1)
+ auth_iov[z].iov_len = vectors[i].auth_size - z*split;
+ else
+ auth_iov[z].iov_len = split;
+ auth_iov_len++;
+ }
+ } else {
+ auth_iov_len = 1;
+ auth_iov[0].iov_base = (void*)vectors[i].auth;
+ auth_iov[0].iov_len = vectors[i].auth_size;
+ }
+
+ iov_len = 0;
+ if (vectors[i].plaintext_size > IOV_PARTS) {
+ unsigned split = vectors[i].plaintext_size / IOV_PARTS;
+ assert(split>0);
+
+ for (z=0;z<IOV_PARTS;z++) {
+ iov[z].iov_base = (void*)(vectors[i].plaintext+(z*split));
+ if (z==IOV_PARTS-1)
+ iov[z].iov_len = vectors[i].plaintext_size - z*split;
+ else
+ iov[z].iov_len = split;
+ iov_len++;
+ }
+ } else {
+ iov_len = 1;
+ iov[0].iov_base = (void*)vectors[i].plaintext;
+ iov[0].iov_len = vectors[i].plaintext_size;
+ }
+
+ s = sizeof(tmp);
+
+ ret =
+ gnutls_aead_cipher_encryptv(hd,
+ iv.data, iv.size,
+ auth_iov, auth_iov_len,
+ vectors[i].tag_size,
+ iov, iov_len,
+ tmp, &s);
+ if (ret < 0)
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+
+ if (s != vectors[i].plaintext_size + tag_size) {
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ if (memcmp(tmp+vectors[i].plaintext_size, vectors[i].tag, tag_size) != 0) {
+ _gnutls_debug_log
+ ("%s test vector %d failed (tag)!\n",
+ gnutls_cipher_get_name(cipher), i);
+ return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
+ }
+
+ if (vectors[i].plaintext_size > 0) {
+ if (memcmp
+ (tmp, vectors[i].ciphertext,
+ vectors[i].plaintext_size) != 0) {
+ _gnutls_debug_log
+ ("%s test vector %d failed!\n",
+ gnutls_cipher_get_name(cipher), i);
+
+ return
+ gnutls_assert_val
+ (GNUTLS_E_SELF_TEST_ERROR);
+ }
+ }
+
+
+
+ gnutls_aead_cipher_deinit(hd);
+ }
+
+ _gnutls_debug_log
+ ("%s scatter self check succeeded\n",
+ gnutls_cipher_get_name(cipher));
+
+ if (flags & GNUTLS_SELF_TEST_FLAG_NO_COMPAT)
+ return 0;
+ else
+ return test_cipher_aead_compat(cipher, vectors, vectors_size);
+}
+
/* AEAD modes */
static int test_cipher_aead(gnutls_cipher_algorithm_t cipher,
const struct cipher_aead_vectors_st *vectors,
@@ -792,14 +977,11 @@ static int test_cipher_aead(gnutls_cipher_algorithm_t cipher,
("%s self check succeeded\n",
gnutls_cipher_get_name(cipher));
- /* test the compatibility APIs */
- if (flags & GNUTLS_SELF_TEST_FLAG_NO_COMPAT)
- return 0;
- else
- return test_cipher_aead_compat(cipher, vectors, vectors_size);
+ return test_cipher_aead_scatter(cipher, vectors, vectors_size, flags);
}
+
struct hash_vectors_st {
const uint8_t *plaintext;
unsigned int plaintext_size;
diff --git a/lib/includes/gnutls/crypto.h b/lib/includes/gnutls/crypto.h
index 43dd37163b..947e25caf9 100644
--- a/lib/includes/gnutls/crypto.h
+++ b/lib/includes/gnutls/crypto.h
@@ -84,6 +84,14 @@ gnutls_aead_cipher_encrypt(gnutls_aead_cipher_hd_t handle,
const void *ptext, size_t ptext_len,
void *ctext, size_t *ctext_len);
+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);
+
void gnutls_aead_cipher_deinit(gnutls_aead_cipher_hd_t handle);
/* Hash - MAC API */
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index cfbd58c40e..2067bca5c9 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -1220,6 +1220,7 @@ GNUTLS_3_6_3
gnutls_pkcs11_token_get_ptr;
gnutls_pkcs11_obj_get_ptr;
gnutls_session_ticket_send;
+ gnutls_aead_cipher_encryptv;
} GNUTLS_3_6_2;
GNUTLS_FIPS140_3_4 {