diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2018-06-02 21:25:10 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2018-06-14 16:30:45 +0200 |
commit | 9a714f6047e18a30d0c2de771933e0f297f4b58f (patch) | |
tree | 908faf326e5551cf44fc27c514f565e51407d3f1 /lib/crypto-api.c | |
parent | aa54b849c102e9cc433201f849f1e8ad029a9a80 (diff) | |
download | gnutls-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>
Diffstat (limited to 'lib/crypto-api.c')
-rw-r--r-- | lib/crypto-api.c | 249 |
1 files changed, 249 insertions, 0 deletions
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. |