summaryrefslogtreecommitdiff
path: root/lib/crypto-api.c
diff options
context:
space:
mode:
authorDaiki Ueno <dueno@redhat.com>2019-08-02 07:40:44 +0200
committerDaiki Ueno <dueno@redhat.com>2019-08-09 13:11:22 +0200
commitd230011cdbbe55f429b43d818c75c8f6687cbc78 (patch)
treeee01995396ce288a37f3323656a54ae6fecba35d /lib/crypto-api.c
parent9ca7a2b42168d356126e306e25211d43ea3c2e7d (diff)
downloadgnutls-d230011cdbbe55f429b43d818c75c8f6687cbc78.tar.gz
crypto-api: add gnutls_aead_cipher_{en,de}cryptv2tmp-encryptv2
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 <dueno@redhat.com>
Diffstat (limited to 'lib/crypto-api.c')
-rw-r--r--lib/crypto-api.c356
1 files changed, 353 insertions, 3 deletions
diff --git a/lib/crypto-api.c b/lib/crypto-api.c
index 70107fed0a..2834c01996 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);
@@ -1067,6 +1107,316 @@ gnutls_aead_cipher_encryptv(gnutls_aead_cipher_hd_t handle,
}
/**
+ * 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.
*