From 38c8dc4317296624cba5b2c8ddba6e9047048180 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Thu, 1 Aug 2019 17:41:45 +0200 Subject: [PATCH 1/3] iov: add iterator interface for giovec_t This adds an iterator interface over giovec_t array, extracting a fixed sized block. Signed-off-by: Daiki Ueno --- .gitignore | 1 + lib/Makefile.am | 3 +- lib/iov.c | 120 ++++++++++++++++++++++++++++++++ lib/iov.h | 46 +++++++++++++ lib/libgnutls.map | 3 + tests/Makefile.am | 6 +- tests/iov.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 lib/iov.c create mode 100644 lib/iov.h create mode 100644 tests/iov.c diff --git a/lib/Makefile.am b/lib/Makefile.am index ffc72e4c2..9fe78afbd 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -80,7 +80,8 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls system-keys.h urls.c urls.h prf.c auto-verify.c dh-session.c \ cert-session.c handshake-checks.c dtls-sw.c dh-primes.c openpgp_compat.c \ crypto-selftests.c crypto-selftests-pk.c secrets.c extv.c extv.h \ - hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c cert-cred-rawpk.c + hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c cert-cred-rawpk.c \ + iov.c iov.h if WINDOWS COBJECTS += system/keys-win.c diff --git a/lib/iov.c b/lib/iov.c new file mode 100644 index 000000000..5dc29c54b --- /dev/null +++ b/lib/iov.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * 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 + * + */ + +#include "gnutls_int.h" +#include "iov.h" + +/** + * _gnutls_iov_iter_init: + * @iter: the iterator + * @iov: the data buffers + * @iov_count: the number of data buffers + * @block_size: block size to iterate + * + * Initialize the iterator. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise + * an error code is returned + */ +int +_gnutls_iov_iter_init(struct iov_iter_st *iter, + const giovec_t *iov, size_t iov_count, + size_t block_size) +{ + if (unlikely(block_size > MAX_CIPHER_BLOCK_SIZE)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + iter->iov = iov; + iter->iov_count = iov_count; + iter->iov_index = 0; + iter->iov_offset = 0; + iter->block_size = block_size; + iter->block_offset = 0; + return 0; +} + +/** + * _gnutls_iov_iter_next: + * @iter: the iterator + * @data: the return location of extracted data + * + * Retrieve block(s) pointed by @iter and advance it to the next + * position. It returns the number of consecutive blocks in @data. + * At the end of iteration, 0 is returned. + * + * If the data stored in @iter is not multiple of the block size, the + * remaining data is stored in the "block" field of @iter with the + * size stored in the "block_offset" field. + * + * Returns: On success, a value greater than or equal to zero is + * returned, otherwise a negative error code is returned + */ +ssize_t +_gnutls_iov_iter_next(struct iov_iter_st *iter, uint8_t **data) +{ + while (iter->iov_index < iter->iov_count) { + const giovec_t *iov = &iter->iov[iter->iov_index]; + uint8_t *p = iov->iov_base; + size_t len = iov->iov_len; + size_t block_left; + + if (unlikely(len < iter->iov_offset)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + len -= iter->iov_offset; + p += iter->iov_offset; + + /* We have at least one full block, return a whole set + * of full blocks immediately. */ + if (iter->block_offset == 0 && len >= iter->block_size) { + if ((len % iter->block_size) == 0) { + iter->iov_index++; + iter->iov_offset = 0; + } else + iter->iov_offset += + len - (len % iter->block_size); + + /* Return the blocks. */ + *data = p; + return len / iter->block_size; + } + + /* We can complete one full block to return. */ + block_left = iter->block_size - iter->block_offset; + if (len >= block_left) { + memcpy(iter->block + iter->block_offset, p, block_left); + iter->iov_offset += block_left; + iter->block_offset = 0; + + /* Return the filled block. */ + *data = iter->block; + return 1; + } + + /* Not enough data for a full block, store in temp + * memory and continue. */ + memcpy(iter->block + iter->block_offset, p, len); + iter->block_offset += len; + iter->iov_index++; + iter->iov_offset = 0; + } + return 0; +} diff --git a/lib/iov.h b/lib/iov.h new file mode 100644 index 000000000..47fba559a --- /dev/null +++ b/lib/iov.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * 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 + * + */ + +#ifndef GNUTLS_LIB_IOV_H +#define GNUTLS_LIB_IOV_H + +#include "gnutls_int.h" + +struct iov_iter_st { + const giovec_t *iov; + size_t iov_count; /* the number of iov */ + size_t iov_index; /* index of the current buffer */ + size_t iov_offset; /* byte offset in the current buffer */ + + uint8_t block[MAX_CIPHER_BLOCK_SIZE]; /* incomplete block for reading */ + size_t block_size; /* actual block size of the cipher */ + size_t block_offset; /* offset in block */ + +}; + +int _gnutls_iov_iter_init(struct iov_iter_st *iter, + const giovec_t *iov, size_t iov_count, + size_t block_size); + +ssize_t _gnutls_iov_iter_next(struct iov_iter_st *iter, uint8_t **data); + +#endif /* GNUTLS_LIB_IOV_H */ diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 0f31f4aef..fc93c0857 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1374,4 +1374,7 @@ GNUTLS_PRIVATE_3_4 { _gnutls_global_set_gettime_function; # Internal symbols needed by tests/tls13/anti_replay.c _gnutls_anti_replay_check; + # needed by tests/iov: + _gnutls_iov_iter_init; + _gnutls_iov_iter_next; } GNUTLS_3_4; diff --git a/tests/Makefile.am b/tests/Makefile.am index a8c2d152e..a2883570f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -212,7 +212,7 @@ ctests += mini-record-2 simple gnutls_hm null_retrieve_function tls-record-size-limit tls-crt_type-neg \ resume-with-stek-expiration resume-with-previous-stek rawpk-api \ tls-record-size-limit-asym dh-compute ecdh-compute \ - sign-verify-deterministic + sign-verify-deterministic iov if HAVE_SECCOMP_TESTS ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp @@ -460,6 +460,10 @@ tls13_anti_replay_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_builddir)/gl \ $(NETTLE_CFLAGS) +iov_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_srcdir)/gl \ + -I$(top_builddir)/gl + if ENABLE_PKCS11 if !WINDOWS ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key diff --git a/tests/iov.c b/tests/iov.c new file mode 100644 index 000000000..eda5583a7 --- /dev/null +++ b/tests/iov.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2019 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gnutls_int.h" +#include "../lib/iov.h" + +#include "utils.h" + +struct exp_st { + ssize_t ret; + size_t iov_index; + size_t iov_offset; + size_t block_offset; +}; + +struct test_st { + const char *name; + const giovec_t *iov; + size_t iovcnt; + size_t block_size; + const struct exp_st *exp; + size_t expcnt; + size_t remaining; +}; + +static const giovec_t iov16[] = { + {(void *) "0123456789abcdef", 16}, + {(void *) "0123456789abcdef", 16}, + {(void *) "0123456789abcdef", 16}, + {(void *) "0123456789abcdef", 16} +}; + +static const struct exp_st exp16_64[] = { + {1, 3, 16, 0}, + {0, 0, 0, 0} +}; + +static const struct exp_st exp16_32[] = { + {1, 1, 16, 0}, + {1, 3, 16, 0}, + {0, 0, 0, 0} +}; + +static const struct exp_st exp16_16[] = { + {1, 1, 0, 0}, + {1, 2, 0, 0}, + {1, 3, 0, 0}, + {1, 4, 0, 0}, + {0, 0, 0, 0} +}; + +static const struct exp_st exp16_4[] = { + {4, 1, 0, 0}, + {4, 2, 0, 0}, + {4, 3, 0, 0}, + {4, 4, 0, 0}, + {0, 0, 0, 0} +}; + +static const struct exp_st exp16_3[] = { + {5, 0, 15, 0}, + {1, 1, 2, 0}, + {4, 1, 14, 0}, + {1, 2, 1, 0}, + {5, 3, 0, 0}, + {5, 3, 15, 0}, + {0, 0, 0, 1} +}; + +static const giovec_t iov8[] = { + {(void *) "01234567", 8}, + {(void *) "01234567", 8}, + {(void *) "01234567", 8}, + {(void *) "01234567", 8} +}; + +static const struct exp_st exp8_64[] = { + {0, 0, 0, 32} +}; + +static const struct test_st tests[] = { + { "16/64", iov16, sizeof(iov16)/sizeof(iov16[0]), 64, + exp16_64, sizeof(exp16_64)/sizeof(exp16_64[0]), 0 }, + { "16/32", iov16, sizeof(iov16)/sizeof(iov16[0]), 32, + exp16_32, sizeof(exp16_32)/sizeof(exp16_32[0]), 0 }, + { "16/16", iov16, sizeof(iov16)/sizeof(iov16[0]), 16, + exp16_16, sizeof(exp16_16)/sizeof(exp16_16[0]), 0 }, + { "16/4", iov16, sizeof(iov16)/sizeof(iov16[0]), 4, + exp16_4, sizeof(exp16_4)/sizeof(exp16_4[0]), 0 }, + { "16/3", iov16, sizeof(iov16)/sizeof(iov16[0]), 3, + exp16_3, sizeof(exp16_3)/sizeof(exp16_3[0]), 1 }, + { "8/64", iov8, sizeof(iov8)/sizeof(iov8[0]), 64, + exp8_64, sizeof(exp8_64)/sizeof(exp8_64[0]), 32 } +}; + +void +doit (void) +{ + size_t i; + + for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + struct iov_iter_st iter; + const struct exp_st *exp = tests[i].exp; + uint8_t *data; + size_t j; + + success("%s\n", tests[i].name); + assert(_gnutls_iov_iter_init(&iter, + tests[i].iov, tests[i].iovcnt, + tests[i].block_size) == 0); + for (j = 0; j < tests[i].expcnt; j++) { + ssize_t ret; + + ret = _gnutls_iov_iter_next(&iter, &data); + if (ret != exp[j].ret) + fail("iov_iter_next: %d != %d\n", + (int) ret, (int) exp[j].ret); + else if (debug) + success("iov_iter_next: %d == %d\n", + (int) ret, (int) exp[j].ret); + if (ret == 0) + break; + if (ret > 0) { + if (iter.iov_index != exp[j].iov_index) + fail("iter.iov_index: %u != %u\n", + (unsigned) iter.iov_index, (unsigned) exp[j].iov_index); + else if (debug) + success("iter.iov_index: %u == %u\n", + (unsigned) iter.iov_index, (unsigned) exp[j].iov_index); + if (iter.iov_offset != exp[j].iov_offset) + fail("iter.iov_offset: %u != %u\n", + (unsigned) iter.iov_offset, (unsigned) exp[j].iov_offset); + else if (debug) + success("iter.iov_offset: %u == %u\n", + (unsigned) iter.iov_offset, (unsigned) exp[j].iov_offset); + if (iter.block_offset != exp[j].block_offset) + fail("iter.block_offset: %u != %u\n", + (unsigned) iter.block_offset, (unsigned) exp[j].block_offset); + else if (debug) + success("iter.block_offset: %u == %u\n", + (unsigned) iter.block_offset, (unsigned) exp[j].block_offset); + } + } + if (iter.block_offset != tests[i].remaining) + fail("remaining: %u != %u\n", + (unsigned) iter.block_offset, (unsigned) tests[i].remaining); + } +} -- 2.21.0 From 9ca7a2b42168d356126e306e25211d43ea3c2e7d Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Thu, 1 Aug 2019 18:13:38 +0200 Subject: [PATCH 2/3] crypto-api: use giovec_t iterator interface for aead_encryptv This replaces the macros AUTH_UPDATE and ENCRYPT used in gnutls_aead_cipher_encryptv() with the iov_iter interface. Signed-off-by: Daiki Ueno --- lib/crypto-api.c | 167 ++++++++++++++++------------------------------- 1 file changed, 57 insertions(+), 110 deletions(-) diff --git a/lib/crypto-api.c b/lib/crypto-api.c index 8af3f3b7d..70107fed0 100644 --- a/lib/crypto-api.c +++ b/lib/crypto-api.c @@ -31,6 +31,7 @@ #include #include #include "crypto-api.h" +#include "iov.h" typedef struct api_cipher_hd_st { cipher_hd_st ctx_enc; @@ -916,98 +917,6 @@ static int copy_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt) } } -#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) { \ - ssize_t 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) { \ - ssize_t 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 < (ssize_t)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) { \ - ssize_t 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) { \ - ssize_t 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: @@ -1039,14 +948,13 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, void *ctext, size_t *ctext_len) { api_aead_cipher_hd_st *h = handle; - int ret; + ssize_t ret; uint8_t *dst; - ssize_t dst_size, total = 0, len; + ssize_t dst_size, total = 0; uint8_t *p; - unsigned i; - uint8_t cache[MAX_CIPHER_BLOCK_SIZE]; - unsigned index; ssize_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + size_t blocks; /* 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(), @@ -1088,25 +996,64 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, 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); + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, + blocksize * blocks); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } + if (iter.block_offset > 0) { + ret = _gnutls_cipher_auth(&handle->ctx_enc, + iter.block, iter.block_offset); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); } - 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; + 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)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + if (unlikely((size_t) dst_size < blocksize * blocks)) + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, p, + blocksize * blocks, + dst, dst_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + DECR_LEN(dst_size, blocksize * blocks); + dst += blocksize * blocks; + total += blocksize * blocks; + } + if (iter.block_offset > 0) { + if (unlikely((size_t) dst_size < iter.block_offset)) + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, + iter.block, iter.block_offset, + dst, dst_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + DECR_LEN(dst_size, iter.block_offset); + dst += iter.block_offset; + total += iter.block_offset; } - ENCRYPT_FINAL(&handle->ctx_enc, dst, dst_size); if ((size_t)dst_size < tag_size) return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); -- 2.21.0 From d230011cdbbe55f429b43d818c75c8f6687cbc78 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Fri, 2 Aug 2019 07:40:44 +0200 Subject: [PATCH 3/3] crypto-api: add gnutls_aead_cipher_{en,de}cryptv2 This adds an in-place equivalent of gnutls_aead_cipher_encrypt() and gnutls_aead_cipher_decrypt(), that works on data buffers. Signed-off-by: Daiki Ueno --- .gitignore | 1 + NEWS | 7 + devel/libgnutls-latest-x86_64.abi | 26 +++ devel/symbols.last | 3 + doc/Makefile.am | 4 + doc/manpages/Makefile.am | 2 + lib/crypto-api.c | 356 +++++++++++++++++++++++++++++- lib/includes/gnutls/crypto.h | 14 ++ lib/libgnutls.map | 7 + tests/Makefile.am | 2 +- tests/aead-cipher-vec.c | 123 +++++++++++ 11 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 tests/aead-cipher-vec.c diff --git a/doc/Makefile.am b/doc/Makefile.am index 6d21d7482..add63c23d 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -635,12 +635,16 @@ FUNCS += functions/dane_verify_session_crt FUNCS += functions/dane_verify_session_crt.short FUNCS += functions/gnutls_aead_cipher_decrypt FUNCS += functions/gnutls_aead_cipher_decrypt.short +FUNCS += functions/gnutls_aead_cipher_decryptv2 +FUNCS += functions/gnutls_aead_cipher_decryptv2.short FUNCS += functions/gnutls_aead_cipher_deinit FUNCS += functions/gnutls_aead_cipher_deinit.short FUNCS += functions/gnutls_aead_cipher_encrypt FUNCS += functions/gnutls_aead_cipher_encrypt.short FUNCS += functions/gnutls_aead_cipher_encryptv FUNCS += functions/gnutls_aead_cipher_encryptv.short +FUNCS += functions/gnutls_aead_cipher_encryptv2 +FUNCS += functions/gnutls_aead_cipher_encryptv2.short FUNCS += functions/gnutls_aead_cipher_init FUNCS += functions/gnutls_aead_cipher_init.short FUNCS += functions/gnutls_alert_get diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am index d06c18013..ee855adf3 100644 --- a/doc/manpages/Makefile.am +++ b/doc/manpages/Makefile.am @@ -119,9 +119,11 @@ APIMANS += dane_verify_crt.3 APIMANS += dane_verify_crt_raw.3 APIMANS += dane_verify_session_crt.3 APIMANS += gnutls_aead_cipher_decrypt.3 +APIMANS += gnutls_aead_cipher_decryptv2.3 APIMANS += gnutls_aead_cipher_deinit.3 APIMANS += gnutls_aead_cipher_encrypt.3 APIMANS += gnutls_aead_cipher_encryptv.3 +APIMANS += gnutls_aead_cipher_encryptv2.3 APIMANS += gnutls_aead_cipher_init.3 APIMANS += gnutls_alert_get.3 APIMANS += gnutls_alert_get_name.3 diff --git a/lib/crypto-api.c b/lib/crypto-api.c index 70107fed0..2834c0199 100644 --- a/lib/crypto-api.c +++ b/lib/crypto-api.c @@ -885,7 +885,26 @@ static void iov_store_free(struct iov_store_st *s) } } -static int copy_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt) +static int iov_store_grow(struct iov_store_st *s, size_t length) +{ + if (s->allocated || s->data == NULL) { + s->size += length; + s->data = gnutls_realloc(s->data, s->size); + if (s->data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + s->allocated = 1; + } else { + void *data = s->data; + size_t size = s->size + length; + s->data = gnutls_malloc(size); + memcpy(s->data, data, s->size); + s->size += length; + } + return 0; +} + +static int +copy_from_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt) { memset(dst, 0, sizeof(*dst)); if (iovcnt == 0) { @@ -917,6 +936,27 @@ static int copy_iov(struct iov_store_st *dst, const giovec_t *iov, int iovcnt) } } +static int +copy_to_iov(struct iov_store_st *src, size_t size, + const giovec_t *iov, int iovcnt) +{ + size_t offset = 0; + int i; + + if (unlikely(src->size < size)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + 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 *) src->data + offset, to_copy); + offset += to_copy; + size -= to_copy; + } + if (size > 0) + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + return 0; +} + /** * gnutls_aead_cipher_encryptv: @@ -971,11 +1011,11 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, struct iov_store_st auth; struct iov_store_st ptext; - ret = copy_iov(&auth, auth_iov, auth_iovcnt); + ret = copy_from_iov(&auth, auth_iov, auth_iovcnt); if (ret < 0) return gnutls_assert_val(ret); - ret = copy_iov(&ptext, iov, iovcnt); + ret = copy_from_iov(&ptext, iov, iovcnt); if (ret < 0) { iov_store_free(&auth); return gnutls_assert_val(ret); @@ -1066,6 +1106,316 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, 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) +{ + api_aead_cipher_hd_st *h = handle; + ssize_t ret; + uint8_t *p; + ssize_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + size_t blocks; + 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)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + /* 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->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; + size_t ptext_size; + + ret = copy_from_iov(&auth, auth_iov, auth_iovcnt); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = copy_from_iov(&ptext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + ptext_size = ptext.size; + + /* append space for tag */ + ret = iov_store_grow(&ptext, _tag_size); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + ret = gnutls_aead_cipher_encrypt(handle, nonce, nonce_len, + auth.data, auth.size, + _tag_size, + ptext.data, ptext_size, + ptext.data, &ptext.size); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + ret = copy_to_iov(&ptext, ptext_size, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + if (tag != NULL) + memcpy(tag, + (uint8_t *) ptext.data + ptext_size, + _tag_size); + if (tag_size != NULL) + *tag_size = _tag_size; + + fallback_fail: + 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); + + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, + blocksize * blocks); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } + if (iter.block_offset > 0) { + ret = _gnutls_cipher_auth(&handle->ctx_enc, + iter.block, iter.block_offset); + if (unlikely(ret < 0)) + 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)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, + p, blocksize * blocks, + p, blocksize * blocks); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } + if (iter.block_offset > 0) { + ret = _gnutls_cipher_encrypt2(&handle->ctx_enc, + iter.block, iter.block_offset, + iter.block, iter.block_offset); + if (unlikely(ret < 0)) + 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; + + 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) +{ + api_aead_cipher_hd_st *h = handle; + ssize_t ret; + uint8_t *p; + ssize_t blocksize = handle->ctx_enc.e->blocksize; + struct iov_iter_st iter; + size_t blocks; + uint8_t _tag[MAX_HASH_SIZE]; + + 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); + + /* 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->only_aead || handle->ctx_enc.encrypt == NULL) { + /* ciphertext cannot be produced in a piecemeal approach */ + struct iov_store_st auth; + struct iov_store_st ctext; + size_t ctext_size; + + ret = copy_from_iov(&auth, auth_iov, auth_iovcnt); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = copy_from_iov(&ctext, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + ctext_size = ctext.size; + + /* append tag */ + ret = iov_store_grow(&ctext, tag_size); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + memcpy((uint8_t *) ctext.data + ctext_size, tag, tag_size); + + ret = gnutls_aead_cipher_decrypt(handle, nonce, nonce_len, + auth.data, auth.size, + tag_size, + ctext.data, ctext.size, + ctext.data, &ctext_size); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + ret = copy_to_iov(&ctext, ctext_size, iov, iovcnt); + if (ret < 0) { + gnutls_assert(); + goto fallback_fail; + } + + fallback_fail: + iov_store_free(&auth); + iov_store_free(&ctext); + + return ret; + } + + ret = _gnutls_cipher_setiv(&handle->ctx_enc, nonce, nonce_len); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + ret = _gnutls_iov_iter_init(&iter, auth_iov, auth_iovcnt, blocksize); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + while (1) { + ret = _gnutls_iov_iter_next(&iter, &p); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + ret = _gnutls_cipher_auth(&handle->ctx_enc, p, + blocksize * blocks); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } + if (iter.block_offset > 0) { + ret = _gnutls_cipher_auth(&handle->ctx_enc, + iter.block, iter.block_offset); + if (unlikely(ret < 0)) + 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)) + return gnutls_assert_val(ret); + if (ret == 0) + break; + blocks = ret; + ret = _gnutls_cipher_decrypt2(&handle->ctx_enc, + p, blocksize * blocks, + p, blocksize * blocks); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } + if (iter.block_offset > 0) { + ret = _gnutls_cipher_decrypt2(&handle->ctx_enc, + iter.block, iter.block_offset, + iter.block, iter.block_offset); + if (unlikely(ret < 0)) + 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) + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + + return 0; +} + /** * gnutls_aead_cipher_deinit: * @handle: is a #gnutls_aead_cipher_hd_t type. diff --git a/lib/includes/gnutls/crypto.h b/lib/includes/gnutls/crypto.h index d2b8cae8f..4d4926c86 100644 --- a/lib/includes/gnutls/crypto.h +++ b/lib/includes/gnutls/crypto.h @@ -92,6 +92,20 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle, const giovec_t *iov, int iovcnt, void *ctext, size_t *ctext_len); +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); + +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); + 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 fc93c0857..f83a21e9b 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1286,6 +1286,13 @@ GNUTLS_3_6_8 gnutls_ffdhe_8192_group_q; } GNUTLS_3_6_6; +GNUTLS_3_6_10 +{ + global: + gnutls_aead_cipher_encryptv2; + gnutls_aead_cipher_decryptv2; +} GNUTLS_3_6_8; + GNUTLS_FIPS140_3_4 { global: gnutls_cipher_self_test; diff --git a/tests/Makefile.am b/tests/Makefile.am index a2883570f..075c2728f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -212,7 +212,7 @@ ctests += mini-record-2 simple gnutls_hm null_retrieve_function tls-record-size-limit tls-crt_type-neg \ resume-with-stek-expiration resume-with-previous-stek rawpk-api \ tls-record-size-limit-asym dh-compute ecdh-compute \ - sign-verify-deterministic iov + sign-verify-deterministic iov aead-cipher-vec if HAVE_SECCOMP_TESTS ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp diff --git a/tests/aead-cipher-vec.c b/tests/aead-cipher-vec.c new file mode 100644 index 000000000..6c2542cf1 --- /dev/null +++ b/tests/aead-cipher-vec.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2019 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include +#include +#include "utils.h" + +static void tls_log_func(int level, const char *str) +{ + fprintf(stderr, "<%d>| %s", level, str); +} + +/* Test whether gnutls_aead_cipher_{en,de}crypt_vec works */ +static void start(const char *name, int algo) +{ + int ret; + gnutls_aead_cipher_hd_t ch; + uint8_t key16[64]; + uint8_t iv16[32]; + uint8_t auth[128]; + uint8_t data[128+64]; + gnutls_datum_t key, iv; + giovec_t iov[2]; + giovec_t auth_iov[2]; + uint8_t tag[64]; + size_t tag_size = 0; + + key.data = key16; + key.size = gnutls_cipher_get_key_size(algo); + assert(key.size <= sizeof(key16)); + + iv.data = iv16; + iv.size = gnutls_cipher_get_iv_size(algo); + assert(iv.size <= sizeof(iv16)); + + memset(iv.data, 0xff, iv.size); + memset(key.data, 0xfe, key.size); + memset(data, 0xfa, 128); + memset(auth, 0xaa, sizeof(auth)); + + iov[0].iov_base = data; + iov[0].iov_len = 64; + iov[1].iov_base = data + 64; + iov[1].iov_len = 64; + + auth_iov[0].iov_base = auth; + auth_iov[0].iov_len = 64; + auth_iov[1].iov_base = auth + 64; + auth_iov[1].iov_len = 64; + + success("trying %s\n", name); + + ret = + gnutls_aead_cipher_init(&ch, algo, &key); + if (ret < 0) + fail("gnutls_cipher_init: %s\n", gnutls_strerror(ret)); + + ret = gnutls_aead_cipher_encryptv2(ch, + iv.data, iv.size, + auth_iov, 2, + iov, 2, + tag, &tag_size); + if (ret < 0) + fail("could not encrypt data: %s\n", gnutls_strerror(ret)); + + ret = gnutls_aead_cipher_decryptv2(ch, + iv.data, iv.size, + auth_iov, 2, + iov, 2, + tag, tag_size); + if (ret < 0) + fail("could not decrypt data: %s\n", gnutls_strerror(ret)); + + gnutls_aead_cipher_deinit(ch); +} + +void +doit(void) +{ + int ret; + + gnutls_global_set_log_function(tls_log_func); + if (debug) + gnutls_global_set_log_level(4711); + + ret = global_init(); + if (ret < 0) { + fail("Cannot initialize library\n"); /*errcode 1 */ + } + + start("aes-128-gcm", GNUTLS_CIPHER_AES_128_GCM); + start("aes-256-gcm", GNUTLS_CIPHER_AES_256_GCM); + start("aes-128-ccm", GNUTLS_CIPHER_AES_128_CCM); + if (!gnutls_fips140_mode_enabled()) + start("chacha20-poly1305", GNUTLS_CIPHER_CHACHA20_POLY1305); + + gnutls_global_deinit(); +} -- 2.21.0