[RFC PATCH 2/2] Crypto kernel tls socket

From: Dave Watson
Date: Mon Nov 23 2015 - 12:43:22 EST


Userspace crypto interface for TLS. Currently supports gcm(aes) 128bit only,
however the interface is the same as the rest of the SOCK_ALG interface, so it
should be possible to add more without any user interface changes.

Currently gcm(aes) represents ~80% of our SSL connections.

Userspace interface:

1) A transform and op socket are created using the userspace crypto interface
2) Setsockopt ALG_SET_AUTHSIZE is called
3) Setsockopt ALG_SET_KEY is called twice, since we need both send/recv keys
4) ALG_SET_IV cmsgs are sent twice, since we need both send/recv IVs.
To support userspace heartbeats, changeciphersuite, etc, we would also need
to get these back out, use them, then reset them via CMSG.
5) ALG_SET_OP cmsg is overloaded to mean FD to read/write from.

Example program:

https://github.com/djwatson/ktls

At a high level, this could be implemented on TCP sockets directly instead with
various tradeoffs.

The userspace crypto interface might benefit from some interface
tweaking to deal with multiple keys / ivs better. The crypto accept()
op socket interface isn't a great fit, since there are never multiple
parallel operations.

There's also some questions around using skbuffs instead of scatterlists for
send/recv, and if we are buffering on recv, when we should be decrypting the
data.
---
crypto/Kconfig | 12 +
crypto/Makefile | 1 +
crypto/algif_tls.c | 1233 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1246 insertions(+)
create mode 100644 crypto/algif_tls.c

diff --git a/crypto/Kconfig b/crypto/Kconfig
index 7240821..c15638a 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -1639,6 +1639,18 @@ config CRYPTO_USER_API_AEAD
This option enables the user-spaces interface for AEAD
cipher algorithms.

+config CRYPTO_USER_API_TLS
+ tristate "User-space interface for TLS net sockets"
+ depends on NET
+ select CRYPTO_AEAD
+ select CRYPTO_USER_API
+ help
+ This option enables kernel TLS socket framing
+ cipher algorithms. TLS framing is added/removed and
+ chained to a TCP socket. Handshake is done in
+ userspace.
+
+
config CRYPTO_HASH_INFO
bool

diff --git a/crypto/Makefile b/crypto/Makefile
index f7aba92..fc26012 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -121,6 +121,7 @@ obj-$(CONFIG_CRYPTO_USER_API_HASH) += algif_hash.o
obj-$(CONFIG_CRYPTO_USER_API_SKCIPHER) += algif_skcipher.o
obj-$(CONFIG_CRYPTO_USER_API_RNG) += algif_rng.o
obj-$(CONFIG_CRYPTO_USER_API_AEAD) += algif_aead.o
+obj-$(CONFIG_CRYPTO_USER_API_TLS) += algif_tls.o

#
# generic algorithms and the async_tx api
diff --git a/crypto/algif_tls.c b/crypto/algif_tls.c
new file mode 100644
index 0000000..123ade3
--- /dev/null
+++ b/crypto/algif_tls.c
@@ -0,0 +1,1233 @@
+/*
+ * algif_tls: User-space interface for TLS
+ *
+ * Copyright (C) 2015, Dave Watson <davejwatson@xxxxxx>
+ *
+ * This file provides the user-space API for AEAD ciphers.
+ *
+ * This program 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 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <crypto/aead.h>
+#include <crypto/if_alg.h>
+#include <linux/init.h>
+#include <linux/file.h>
+#include <linux/list.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#define TLS_HEADER_SIZE 13
+#define TLS_TAG_SIZE 16
+#define TLS_IV_SIZE 8
+#define TLS_PADDED_AADLEN 16
+#define TLS_MAX_MESSAGE_LEN (1 << 14)
+
+/* Bytes not included in tls msg size field */
+#define TLS_FRAMING_SIZE 5
+
+#define TLS_APPLICATION_DATA_MSG 0x17
+#define TLS_VERSION 3
+
+struct tls_tfm_pair {
+ struct crypto_aead *tfm_send;
+ struct crypto_aead *tfm_recv;
+ int cur_setkey;
+};
+
+static struct workqueue_struct *tls_wq;
+
+struct tls_sg_list {
+ unsigned int cur;
+ struct scatterlist sg[ALG_MAX_PAGES];
+};
+
+#define RSGL_MAX_ENTRIES ALG_MAX_PAGES
+
+struct tls_ctx {
+ /* Send and encrypted transmit buffers */
+ struct tls_sg_list tsgl;
+ struct scatterlist tcsgl[ALG_MAX_PAGES];
+
+ /* Encrypted receive and receive buffers. */
+ struct tls_sg_list rcsgl;
+ struct af_alg_sgl rsgl[RSGL_MAX_ENTRIES];
+
+ /* Sequence numbers. */
+ int iv_set;
+ void *iv_send;
+ void *iv_recv;
+
+ struct af_alg_completion completion;
+
+ /* Bytes to send */
+ unsigned long used;
+
+ /* padded */
+ size_t aead_assoclen;
+ /* unpadded */
+ size_t assoclen;
+ struct aead_request aead_req;
+ struct aead_request aead_resp;
+
+ bool more;
+ bool merge;
+
+ /* Chained TCP socket */
+ struct sock *sock;
+ struct socket *socket;
+
+ void (*save_data_ready)(struct sock *sk);
+ void (*save_write_space)(struct sock *sk);
+ void (*save_state_change)(struct sock *sk);
+ struct work_struct tx_work;
+ struct work_struct rx_work;
+
+ /* This socket for use with above callbacks */
+ struct sock *alg_sock;
+
+ /* Send buffer tracking */
+ int page_to_send;
+ int tcsgl_size;
+
+ /* Recv buffer tracking */
+ int recv_wanted;
+ int recved_len;
+
+ /* Receive AAD. */
+ unsigned char buf[24];
+};
+
+static void increment_seqno(u64 *seqno)
+{
+ u64 seq_h = be64_to_cpu(*seqno);
+
+ seq_h++;
+ *seqno = cpu_to_be64(seq_h);
+}
+
+static int do_tls_kernel_sendpage(struct sock *sk);
+
+static int tls_wait_for_data(struct sock *sk, unsigned flags)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ long timeout;
+ DEFINE_WAIT(wait);
+ int err = -ERESTARTSYS;
+
+ if (flags & MSG_DONTWAIT)
+ return -EAGAIN;
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
+
+ for (;;) {
+ if (signal_pending(current))
+ break;
+ prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ if (sk_wait_event(sk, &timeout,
+ ctx->recved_len == ctx->recv_wanted)) {
+ err = 0;
+ break;
+ }
+ }
+ finish_wait(sk_sleep(sk), &wait);
+
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
+
+ return err;
+}
+
+static int tls_wait_for_write_space(struct sock *sk, unsigned flags)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ long timeout;
+ DEFINE_WAIT(wait);
+ int err = -ERESTARTSYS;
+
+ if (flags & MSG_DONTWAIT)
+ return -EAGAIN;
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
+
+ for (;;) {
+ if (signal_pending(current))
+ break;
+ prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ if (sk_wait_event(sk, &timeout, !ctx->page_to_send)) {
+ err = 0;
+ break;
+ }
+ }
+ finish_wait(sk_sleep(sk), &wait);
+
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
+
+ return err;
+}
+
+static inline int tls_sndbuf(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+
+ return max_t(int, max_t(int, sk->sk_sndbuf & PAGE_MASK, PAGE_SIZE) -
+ ctx->used, 0);
+}
+
+static inline bool tls_writable(struct sock *sk)
+{
+ return tls_sndbuf(sk) >= PAGE_SIZE;
+}
+
+
+static void tls_put_sgl(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ struct tls_sg_list *sgl = &ctx->tsgl;
+ struct scatterlist *sg = sgl->sg;
+ unsigned int i;
+
+ for (i = 0; i < sgl->cur; i++) {
+ if (!sg_page(sg + i))
+ continue;
+
+ put_page(sg_page(sg + i));
+ sg_assign_page(sg + i, NULL);
+ }
+ sg_init_table(sg, ALG_MAX_PAGES);
+ sgl->cur = 0;
+ ctx->used = 0;
+ ctx->more = 0;
+ ctx->merge = 0;
+}
+
+static void tls_wmem_wakeup(struct sock *sk)
+{
+ struct socket_wq *wq;
+
+ if (!tls_writable(sk))
+ return;
+
+ rcu_read_lock();
+ wq = rcu_dereference(sk->sk_wq);
+ if (wq_has_sleeper(wq))
+ wake_up_interruptible_sync_poll(&wq->wait, POLLIN |
+ POLLRDNORM |
+ POLLRDBAND);
+ sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
+ rcu_read_unlock();
+}
+
+static void tls_put_rcsgl(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ struct tls_sg_list *sgl = &ctx->rcsgl;
+ int i;
+
+ for (i = 0; i < sgl->cur; i++)
+ put_page(sg_page(&sgl->sg[i]));
+ sgl->cur = 0;
+ sg_init_table(&sgl->sg[0], ALG_MAX_PAGES);
+}
+
+
+static void tls_sock_state_change(struct sock *sk)
+{
+ struct tls_ctx *ctx;
+
+ ctx = (struct tls_ctx *)sk->sk_user_data;
+
+ switch (sk->sk_state) {
+ case TCP_CLOSE:
+ case TCP_CLOSE_WAIT:
+ case TCP_ESTABLISHED:
+ ctx->alg_sock->sk_state = sk->sk_state;
+ ctx->alg_sock->sk_state_change(ctx->alg_sock);
+ tls_wmem_wakeup(ctx->alg_sock);
+
+ break;
+ default: /* Everything else is uninteresting */
+ break;
+ }
+}
+
+/* Both socket lock held */
+static ssize_t tls_socket_splice(struct sock *sk,
+ struct pipe_inode_info *pipe,
+ struct splice_pipe_desc *spd) {
+ struct tls_ctx *ctx = (struct tls_ctx *)pipe;
+ struct tls_sg_list *sgl = &ctx->rcsgl;
+
+ unsigned int spd_pages = spd->nr_pages;
+ int ret = 0;
+ int page_nr = 0;
+
+ while (spd->nr_pages > 0) {
+ if (sgl->cur < ALG_MAX_PAGES) {
+ struct scatterlist *sg = &sgl->sg[sgl->cur];
+
+ sg_assign_page(sg, spd->pages[page_nr]);
+ sg->offset = spd->partial[page_nr].offset;
+ sg->length = spd->partial[page_nr].len;
+ sgl->cur++;
+
+ ret += spd->partial[page_nr].len;
+ page_nr++;
+
+ --spd->nr_pages;
+ } else {
+ sk->sk_err = -ENOMEM;
+ break;
+ }
+ }
+
+ while (page_nr < spd_pages)
+ spd->spd_release(spd, page_nr++);
+
+ ctx->recved_len += ret;
+
+ if (ctx->recved_len == ctx->recv_wanted || sk->sk_err)
+ tls_wmem_wakeup(ctx->alg_sock);
+
+ return ret;
+}
+
+/* Both socket lock held */
+static int tls_tcp_recv(read_descriptor_t *desc, struct sk_buff *skb,
+ unsigned int offset, size_t len)
+{
+ int ret;
+
+ ret = skb_splice_bits(skb, skb->sk, offset, desc->arg.data,
+ min(desc->count, len),
+ 0, tls_socket_splice);
+ if (ret > 0)
+ desc->count -= ret;
+
+ return ret;
+}
+
+static int tls_tcp_read_sock(struct tls_ctx *ctx)
+{
+ struct sock *sk = ctx->alg_sock;
+
+ struct msghdr msg = {};
+ struct kvec iov;
+ read_descriptor_t desc;
+
+ desc.arg.data = ctx;
+ desc.error = 0;
+
+ lock_sock(sk);
+
+ iov.iov_base = ctx->buf;
+ iov.iov_len = TLS_HEADER_SIZE;
+
+ if (ctx->recv_wanted == -1) {
+ unsigned int encrypted_size = 0;
+
+ /* Peek at framing.
+ *
+ * We only handle TLS message type 0x17, application_data.
+ *
+ * Otherwise set an error on the socket and let
+ * userspace handle the message types
+ * change_cipher_spec, alert, handshake
+ *
+ */
+ int bytes = kernel_recvmsg(ctx->socket, &msg, &iov, 1,
+ iov.iov_len, MSG_PEEK | MSG_DONTWAIT);
+
+ if (bytes <= 0)
+ goto unlock;
+
+ if (ctx->buf[0] != TLS_APPLICATION_DATA_MSG) {
+ sk->sk_err = -EBADMSG;
+ desc.error = sk->sk_err;
+ goto unlock;
+ }
+
+ if (bytes < TLS_HEADER_SIZE)
+ goto unlock;
+
+
+ encrypted_size = ctx->buf[4] | (ctx->buf[3] << 8);
+
+ /* Verify encrypted size looks sane */
+ if (encrypted_size > TLS_MAX_MESSAGE_LEN + TLS_TAG_SIZE +
+ TLS_HEADER_SIZE - TLS_FRAMING_SIZE) {
+ sk->sk_err = -EINVAL;
+ desc.error = sk->sk_err;
+ goto unlock;
+ }
+ /* encrypted_size field doesn't include 5 bytes of framing */
+ ctx->recv_wanted = encrypted_size + TLS_FRAMING_SIZE;
+
+ /* Flush header bytes. We peeked at before, we will
+ * handle this message type
+ */
+ bytes = kernel_recvmsg(ctx->socket, &msg, &iov, 1,
+ iov.iov_len, MSG_DONTWAIT);
+ WARN_ON(bytes != TLS_HEADER_SIZE);
+ ctx->recved_len = TLS_HEADER_SIZE;
+ }
+
+ if (ctx->recv_wanted <= 0)
+ goto unlock;
+
+ desc.count = ctx->recv_wanted - ctx->recved_len;
+
+ if (desc.count > 0) {
+ lock_sock(ctx->sock);
+
+ tcp_read_sock(ctx->sock, &desc, tls_tcp_recv);
+
+ release_sock(ctx->sock);
+ }
+
+unlock:
+ if (desc.error)
+ tls_wmem_wakeup(ctx->alg_sock);
+
+ release_sock(sk);
+
+ return desc.error;
+}
+
+static void tls_tcp_data_ready(struct sock *sk)
+{
+ struct tls_ctx *ctx;
+
+ read_lock_bh(&sk->sk_callback_lock);
+
+ ctx = (struct tls_ctx *)sk->sk_user_data;
+
+ queue_work(tls_wq, &ctx->rx_work);
+
+ read_unlock_bh(&sk->sk_callback_lock);
+}
+
+static void tls_tcp_write_space(struct sock *sk)
+{
+ struct tls_ctx *ctx;
+
+ read_lock_bh(&sk->sk_callback_lock);
+
+ ctx = (struct tls_ctx *)sk->sk_user_data;
+
+ queue_work(tls_wq, &ctx->tx_work);
+
+ read_unlock_bh(&sk->sk_callback_lock);
+}
+
+static void tls_rx_work(struct work_struct *w)
+{
+ struct tls_ctx *ctx = container_of(w, struct tls_ctx, rx_work);
+
+ tls_tcp_read_sock(ctx);
+}
+
+static void tls_tx_work(struct work_struct *w)
+{
+ struct tls_ctx *ctx = container_of(w, struct tls_ctx, tx_work);
+ struct sock *sk = ctx->alg_sock;
+ int err;
+
+ lock_sock(sk);
+
+ err = do_tls_kernel_sendpage(sk);
+ if (err < 0) {
+ /* Hard failure in write, report error on KCM socket */
+ pr_warn("TLS: Hard failure on do_tls_sendpage %d\n", err);
+ sk->sk_err = -err;
+ tls_wmem_wakeup(sk);
+ goto out;
+ }
+
+out:
+ release_sock(sk);
+}
+
+static int do_tls_kernel_sendpage(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ int err = 0;
+ int i;
+
+ if (ctx->page_to_send == 0)
+ return err;
+ for (; ctx->page_to_send < ctx->tcsgl_size; ctx->page_to_send++) {
+ int flags = MSG_DONTWAIT;
+
+ if (ctx->page_to_send != ctx->tcsgl_size - 1)
+ flags |= MSG_MORE;
+ err = kernel_sendpage(ctx->sock->sk_socket,
+ sg_page(&ctx->tcsgl[ctx->page_to_send]),
+ ctx->tcsgl[ctx->page_to_send].offset,
+ ctx->tcsgl[ctx->page_to_send].length,
+ flags);
+ if (err <= 0) {
+ if (err == -EAGAIN) {
+ /* Don't forward EAGAIN */
+ err = 0;
+ goto out;
+ }
+ goto out;
+ }
+ }
+
+ ctx->page_to_send = 0;
+
+ increment_seqno(ctx->iv_send);
+
+
+ for (i = 1; i < ctx->tcsgl_size; i++)
+ put_page(sg_page(&ctx->tcsgl[i]));
+
+ tls_wmem_wakeup(sk);
+out:
+
+ return err;
+}
+
+static int do_tls_sendpage(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ struct tls_sg_list *sgl = &ctx->tsgl;
+
+ int used = ctx->used;
+
+ unsigned ivsize =
+ crypto_aead_ivsize(crypto_aead_reqtfm(&ctx->aead_req));
+ int encrypted_size = ivsize + used +
+ crypto_aead_authsize(crypto_aead_reqtfm(&ctx->aead_req));
+
+ /* Ensure enough space in sg list for tag. */
+ struct scatterlist *sg = &ctx->tcsgl[1];
+ int bytes_needed = used + TLS_HEADER_SIZE + TLS_TAG_SIZE;
+ int err = -ENOMEM;
+
+ struct page *p;
+ unsigned char *framing;
+ unsigned char aad[ctx->aead_assoclen];
+ struct scatterlist sgaad[2];
+
+ WARN_ON(used > TLS_MAX_MESSAGE_LEN);
+
+ /* Framing will be put in first sg */
+ ctx->tcsgl_size = 1;
+
+ do {
+ sg_assign_page(sg, alloc_page(GFP_KERNEL));
+ if (!sg_page(sg))
+ goto unlock;
+
+ sg_unmark_end(sg);
+ sg->offset = 0;
+ sg->length = PAGE_SIZE;
+ if (bytes_needed < PAGE_SIZE)
+ sg->length = bytes_needed;
+
+ ctx->tcsgl_size++;
+ sg = &ctx->tcsgl[ctx->tcsgl_size];
+ bytes_needed -= PAGE_SIZE;
+ } while (bytes_needed > 0);
+
+ p = sg_page(&ctx->tcsgl[1]);
+
+ sg = &ctx->tcsgl[0];
+
+ sg->offset = 0;
+ sg->length = TLS_PADDED_AADLEN + TLS_IV_SIZE;
+ sg_assign_page(sg, p);
+
+ sg = &ctx->tcsgl[1];
+ sg->offset = TLS_HEADER_SIZE;
+ sg->length = sg->length - TLS_HEADER_SIZE;
+
+ sg_mark_end(&ctx->tcsgl[ctx->tcsgl_size - 1]);
+ framing = page_address(p);
+
+ /* Hardcoded to TLS 1.2 */
+ memset(framing, 0, ctx->aead_assoclen);
+ framing[0] = TLS_APPLICATION_DATA_MSG;
+ framing[1] = TLS_VERSION;
+ framing[2] = TLS_VERSION;
+ framing[3] = encrypted_size >> 8;
+ framing[4] = encrypted_size & 0xff;
+ /* Per spec, iv_send can be used as nonce */
+ memcpy(framing + 5, ctx->iv_send, TLS_IV_SIZE);
+
+ memset(aad, 0, ctx->aead_assoclen);
+ memcpy(aad, ctx->iv_send, TLS_IV_SIZE);
+
+ aad[8] = TLS_APPLICATION_DATA_MSG;
+ aad[9] = TLS_VERSION;
+ aad[10] = TLS_VERSION;
+ aad[11] = used >> 8;
+ aad[12] = used & 0xff;
+
+ sg_set_buf(&sgaad[0], aad, ctx->aead_assoclen);
+ sg_unmark_end(sgaad);
+ sg_chain(sgaad, 2, sgl->sg);
+
+ sg_mark_end(sgl->sg + sgl->cur - 1);
+ aead_request_set_crypt(&ctx->aead_req, sgaad, ctx->tcsgl,
+ used, ctx->iv_send);
+ aead_request_set_ad(&ctx->aead_req, ctx->assoclen);
+
+ err = af_alg_wait_for_completion(crypto_aead_encrypt(&ctx->aead_req),
+ &ctx->completion);
+
+ if (err) {
+ /* EBADMSG implies a valid cipher operation took place */
+ if (err == -EBADMSG)
+ tls_put_sgl(sk);
+ goto unlock;
+ }
+
+ ctx->tcsgl[1].length += TLS_HEADER_SIZE;
+ ctx->tcsgl[1].offset = 0;
+
+ ctx->page_to_send = 1;
+
+ tls_put_sgl(sk);
+
+ err = do_tls_kernel_sendpage(sk);
+
+unlock:
+
+ return err;
+}
+
+static int tls_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
+{
+ struct sock *sk = sock->sk;
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ struct tls_sg_list *sgl = &ctx->tsgl;
+ unsigned ivsize =
+ crypto_aead_ivsize(crypto_aead_reqtfm(&ctx->aead_req));
+ struct af_alg_control con = {};
+ long copied = 0;
+ bool init = 0;
+ int err = -EINVAL;
+
+ struct socket *csock = NULL;
+ struct sock *csk = NULL;
+
+ if (msg->msg_controllen) {
+ init = 1;
+ err = af_alg_cmsg_send(msg, &con);
+ if (err)
+ return err;
+
+ if (!ctx->sock) {
+ if (!con.op) {
+ err = -EINVAL;
+ return err;
+ }
+ csock = sockfd_lookup(con.op, &err);
+ if (!csock)
+ return -ENOENT;
+ csk = csock->sk;
+ ctx->sock = csk;
+ ctx->socket = csock;
+ ctx->alg_sock = sk;
+ if (!ctx->sock) {
+ err = -EINVAL;
+ fput(csock->file);
+ return err;
+ }
+ }
+
+ if (con.iv && con.iv->ivlen != ivsize)
+ return -EINVAL;
+ }
+
+ lock_sock(sk);
+
+ if (!ctx->more && ctx->used)
+ goto unlock;
+
+ if (init) {
+ if (con.iv) {
+ if (ctx->iv_set == 0) {
+ ctx->iv_set = 1;
+ memcpy(ctx->iv_send, con.iv->iv, ivsize);
+ } else {
+ memcpy(ctx->iv_recv, con.iv->iv, ivsize);
+ }
+ }
+
+ if (con.aead_assoclen) {
+ ctx->assoclen = con.aead_assoclen;
+ /* Pad out assoclen to 4-byte boundary */
+ ctx->aead_assoclen = (con.aead_assoclen + 3) & ~3;
+ }
+
+ if (csk) {
+ write_lock_bh(&csk->sk_callback_lock);
+ ctx->save_data_ready = csk->sk_data_ready;
+ ctx->save_write_space = csk->sk_write_space;
+ ctx->save_state_change = csk->sk_state_change;
+ csk->sk_user_data = ctx;
+ csk->sk_data_ready = tls_tcp_data_ready;
+ csk->sk_write_space = tls_tcp_write_space;
+ csk->sk_state_change = tls_sock_state_change;
+ write_unlock_bh(&csk->sk_callback_lock);
+ }
+ }
+
+ if (sk->sk_err)
+ goto out_error;
+
+ while (size) {
+ unsigned long len = size;
+ struct scatterlist *sg = NULL;
+
+ /* use the existing memory in an allocated page */
+ if (ctx->merge) {
+ sg = sgl->sg + sgl->cur - 1;
+ len = min_t(unsigned long, len,
+ PAGE_SIZE - sg->offset - sg->length);
+
+ if (ctx->page_to_send != 0) {
+ err = tls_wait_for_write_space(
+ sk, msg->msg_flags);
+ if (err)
+ goto unlock;
+ }
+
+ if (ctx->used + len > TLS_MAX_MESSAGE_LEN) {
+ err = do_tls_sendpage(sk);
+ if (err < 0)
+ goto unlock;
+
+ continue;
+ }
+
+ err = memcpy_from_msg(page_address(sg_page(sg)) +
+ sg->offset + sg->length,
+ msg, len);
+ if (err)
+ goto unlock;
+
+ sg->length += len;
+ ctx->merge = (sg->offset + sg->length) &
+ (PAGE_SIZE - 1);
+
+ ctx->used += len;
+ copied += len;
+ size -= len;
+ continue;
+ }
+
+ if (!tls_writable(sk)) {
+ /* user space sent too much data */
+ tls_put_sgl(sk);
+ err = -EMSGSIZE;
+ goto unlock;
+ }
+
+ /* allocate a new page */
+ len = min_t(unsigned long, size, tls_sndbuf(sk));
+ while (len) {
+ int plen = 0;
+
+ if (sgl->cur >= ALG_MAX_PAGES) {
+ tls_put_sgl(sk);
+ err = -E2BIG;
+ goto unlock;
+ }
+
+ sg = sgl->sg + sgl->cur;
+ plen = min_t(int, len, PAGE_SIZE);
+
+ if (ctx->page_to_send != 0) {
+ err = tls_wait_for_write_space(
+ sk, msg->msg_flags);
+ if (err)
+ goto unlock;
+ }
+
+ if (ctx->used + plen > TLS_MAX_MESSAGE_LEN) {
+ err = do_tls_sendpage(sk);
+ if (err < 0)
+ goto unlock;
+ continue;
+ }
+
+ sg_assign_page(sg, alloc_page(GFP_KERNEL));
+ err = -ENOMEM;
+ if (!sg_page(sg))
+ goto unlock;
+
+ err = memcpy_from_msg(page_address(sg_page(sg)),
+ msg, plen);
+ if (err) {
+ __free_page(sg_page(sg));
+ sg_assign_page(sg, NULL);
+ goto unlock;
+ }
+
+ sg->offset = 0;
+ sg->length = plen;
+ len -= plen;
+ ctx->used += plen;
+ copied += plen;
+ sgl->cur++;
+ size -= plen;
+ ctx->merge = plen & (PAGE_SIZE - 1);
+ }
+ }
+
+ err = 0;
+
+ ctx->more = msg->msg_flags & MSG_MORE;
+
+ if (ctx->more && ctx->used < TLS_MAX_MESSAGE_LEN)
+ goto unlock;
+
+ if (ctx->page_to_send != 0) {
+ err = tls_wait_for_write_space(sk, msg->msg_flags);
+ if (err)
+ goto unlock;
+ }
+
+ err = do_tls_sendpage(sk);
+
+unlock:
+ release_sock(sk);
+
+ return err ?: copied;
+
+out_error:
+ err = sk_stream_error(sk, msg->msg_flags, err);
+ release_sock(sk);
+
+ return err;
+}
+
+static ssize_t tls_sendpage(struct socket *sock, struct page *page,
+ int offset, size_t size, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ struct tls_sg_list *sgl = &ctx->tsgl;
+ int err = -EINVAL;
+
+ if (flags & MSG_SENDPAGE_NOTLAST)
+ flags |= MSG_MORE;
+
+ if (sgl->cur >= ALG_MAX_PAGES)
+ return -E2BIG;
+
+ lock_sock(sk);
+
+ if (sk->sk_err)
+ goto out_error;
+
+ if (ctx->page_to_send != 0) {
+ err = tls_wait_for_write_space(sk, flags);
+ if (err)
+ goto unlock;
+ }
+
+ if (size + ctx->used > TLS_MAX_MESSAGE_LEN) {
+ err = do_tls_sendpage(sk);
+ if (err < 0)
+ goto unlock;
+ err = -EINVAL;
+ }
+
+ if (!ctx->more && ctx->used)
+ goto unlock;
+
+ if (!size)
+ goto done;
+
+ if (!tls_writable(sk)) {
+ /* user space sent too much data */
+ tls_put_sgl(sk);
+ err = -EMSGSIZE;
+ goto unlock;
+ }
+
+ ctx->merge = 0;
+
+ get_page(page);
+ sg_set_page(sgl->sg + sgl->cur, page, size, offset);
+ sgl->cur++;
+ ctx->used += size;
+
+ err = 0;
+
+done:
+ ctx->more = flags & MSG_MORE;
+
+ if (ctx->more && ctx->used < TLS_MAX_MESSAGE_LEN)
+ goto unlock;
+
+ err = do_tls_sendpage(sk);
+
+unlock:
+ release_sock(sk);
+
+ return err < 0 ? err : size;
+
+out_error:
+ err = sk_stream_error(sk, flags, err);
+ release_sock(sk);
+
+ return err;
+}
+
+static int tls_recvmsg(struct socket *sock, struct msghdr *msg,
+ size_t ignored, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ unsigned int i = 0;
+ int err = -EINVAL;
+ size_t outlen = 0;
+ size_t usedpages = 0;
+ unsigned int cnt = 0;
+
+ char aad_unneeded[ctx->aead_assoclen];
+ struct scatterlist outaad[2];
+
+ struct tls_sg_list *sgl = &ctx->rcsgl;
+ struct scatterlist aadsg[2];
+
+ char buf[11];
+ int used;
+ char *aad;
+
+ char nonce[TLS_IV_SIZE];
+
+ /* Limit number of IOV blocks to be accessed below */
+ if (msg->msg_iter.nr_segs > RSGL_MAX_ENTRIES)
+ return -ENOMSG;
+
+ tls_tcp_read_sock(ctx);
+
+ lock_sock(sk);
+
+ if (sk->sk_err)
+ goto out_error;
+
+ if (ctx->recved_len != ctx->recv_wanted) {
+ err = tls_wait_for_data(sk, flags);
+ if (err)
+ goto unlock;
+ }
+
+ sg_set_buf(outaad, aad_unneeded, ctx->aead_assoclen);
+ sg_unmark_end(outaad);
+ sg_chain(outaad, 2, &ctx->rsgl[0].sg[0]);
+
+ outlen = ctx->recv_wanted - TLS_FRAMING_SIZE - ctx->aead_assoclen;
+
+ /* convert iovecs of output buffers into scatterlists */
+ while (iov_iter_count(&msg->msg_iter)) {
+ size_t seglen = min_t(size_t, iov_iter_count(&msg->msg_iter),
+ (outlen - usedpages));
+
+ /* make one iovec available as scatterlist */
+ err = af_alg_make_sg(&ctx->rsgl[cnt], &msg->msg_iter,
+ seglen);
+ if (err < 0)
+ goto unlock;
+ usedpages += err;
+ /* chain the new scatterlist with previous one */
+ if (cnt)
+ af_alg_link_sg(&ctx->rsgl[cnt-1], &ctx->rsgl[cnt]);
+
+ /* we do not need more iovecs as we have sufficient memory */
+ if (outlen <= usedpages)
+ break;
+ iov_iter_advance(&msg->msg_iter, err);
+ cnt++;
+ }
+
+ err = -EINVAL;
+
+ /* ensure output buffer is sufficiently large */
+ if (usedpages < outlen)
+ goto unlock;
+
+ memset(buf, 0, sizeof(buf));
+
+ used = ctx->recv_wanted - ctx->aead_assoclen - TLS_FRAMING_SIZE;
+
+ aad = ctx->buf;
+
+ sg_set_buf(aadsg, ctx->buf, ctx->aead_assoclen);
+ sg_unmark_end(aadsg);
+ sg_chain(aadsg, 2, sgl->sg);
+
+ memcpy(nonce, aad + TLS_FRAMING_SIZE, TLS_IV_SIZE);
+ memcpy(aad, ctx->iv_recv, TLS_IV_SIZE);
+
+ aad[8] = TLS_APPLICATION_DATA_MSG;
+ aad[9] = TLS_VERSION;
+ aad[10] = TLS_VERSION;
+ aad[11] = used >> 8;
+ aad[12] = used & 0xff;
+
+ sg_mark_end(sgl->sg + sgl->cur - 1);
+ aead_request_set_crypt(&ctx->aead_resp, aadsg, outaad,
+ ctx->recv_wanted + TLS_TAG_SIZE
+ - TLS_FRAMING_SIZE - ctx->aead_assoclen,
+ nonce);
+ aead_request_set_ad(&ctx->aead_resp, ctx->assoclen);
+
+ err = af_alg_wait_for_completion(crypto_aead_decrypt(&ctx->aead_resp),
+ &ctx->completion);
+
+ if (err) {
+ /* EBADMSG implies a valid cipher operation took place */
+ goto unlock;
+ } else {
+ ctx->recv_wanted = -1;
+ ctx->recved_len = 0;
+ }
+
+ increment_seqno(ctx->iv_recv);
+
+ err = 0;
+
+unlock:
+ tls_put_rcsgl(sk);
+
+ for (i = 0; i < cnt; i++)
+ af_alg_free_sg(&ctx->rsgl[i]);
+
+ queue_work(tls_wq, &ctx->rx_work);
+ release_sock(sk);
+
+ return err ? err : outlen;
+
+out_error:
+ err = sk_stream_error(sk, msg->msg_flags, err);
+ release_sock(sk);
+
+ return err;
+}
+
+
+static struct proto_ops algif_tls_ops = {
+ .family = PF_ALG,
+
+ .connect = sock_no_connect,
+ .socketpair = sock_no_socketpair,
+ .getname = sock_no_getname,
+ .ioctl = sock_no_ioctl,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ .getsockopt = sock_no_getsockopt,
+ .mmap = sock_no_mmap,
+ .bind = sock_no_bind,
+ .accept = sock_no_accept,
+ .setsockopt = sock_no_setsockopt,
+
+ .release = af_alg_release,
+ .sendmsg = tls_sendmsg,
+ .sendpage = tls_sendpage,
+ .recvmsg = tls_recvmsg,
+ .poll = sock_no_poll,
+};
+
+static void *tls_bind(const char *name, u32 type, u32 mask)
+{
+ struct tls_tfm_pair *pair = kmalloc(sizeof(struct tls_tfm_pair),
+ GFP_KERNEL);
+
+ if (!pair)
+ return NULL;
+ pair->tfm_send = crypto_alloc_aead(name, type, mask);
+ if (!pair->tfm_send)
+ goto error;
+ pair->tfm_recv = crypto_alloc_aead(name, type, mask);
+ if (!pair->tfm_recv)
+ goto error;
+
+ pair->cur_setkey = 0;
+
+ return pair;
+
+error:
+ if (pair->tfm_send)
+ crypto_free_aead(pair->tfm_send);
+ if (pair->tfm_recv)
+ crypto_free_aead(pair->tfm_recv);
+ kfree(pair);
+
+ return NULL;
+}
+
+static void tls_release(void *private)
+{
+ struct tls_tfm_pair *pair = private;
+
+ if (pair) {
+ if (pair->tfm_send)
+ crypto_free_aead(pair->tfm_send);
+ if (pair->tfm_recv)
+ crypto_free_aead(pair->tfm_recv);
+ kfree(private);
+ }
+}
+
+static int tls_setauthsize(void *private, unsigned int authsize)
+{
+ struct tls_tfm_pair *pair = private;
+
+ crypto_aead_setauthsize(pair->tfm_recv, authsize);
+ return crypto_aead_setauthsize(pair->tfm_send, authsize);
+}
+
+static int tls_setkey(void *private, const u8 *key, unsigned int keylen)
+{
+ struct tls_tfm_pair *pair = private;
+
+ if (pair->cur_setkey == 0) {
+ pair->cur_setkey = 1;
+ return crypto_aead_setkey(pair->tfm_send, key, keylen);
+ } else {
+ return crypto_aead_setkey(pair->tfm_recv, key, keylen);
+ }
+}
+
+static void tls_sock_destruct(struct sock *sk)
+{
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_ctx *ctx = ask->private;
+ unsigned int ivlen = crypto_aead_ivsize(
+ crypto_aead_reqtfm(&ctx->aead_req));
+
+
+
+ cancel_work_sync(&ctx->tx_work);
+ cancel_work_sync(&ctx->rx_work);
+
+ /* Stop getting callbacks from TCP socket. */
+ write_lock_bh(&ctx->sock->sk_callback_lock);
+ if (ctx->sock->sk_user_data) {
+ ctx->sock->sk_user_data = NULL;
+ ctx->sock->sk_data_ready = ctx->save_data_ready;
+ ctx->sock->sk_write_space = ctx->save_write_space;
+ ctx->sock->sk_state_change = ctx->save_state_change;
+ }
+ write_unlock_bh(&ctx->sock->sk_callback_lock);
+
+ tls_put_sgl(sk);
+ sock_kzfree_s(sk, ctx->iv_send, ivlen);
+ sock_kzfree_s(sk, ctx->iv_recv, ivlen);
+ sock_kfree_s(sk, ctx, sizeof(*ctx));
+ af_alg_release_parent(sk);
+}
+
+static int tls_accept_parent(void *private, struct sock *sk)
+{
+ struct tls_ctx *ctx;
+ struct alg_sock *ask = alg_sk(sk);
+ struct tls_tfm_pair *pair = private;
+
+ unsigned int len = sizeof(*ctx);
+ unsigned int ivlen = crypto_aead_ivsize(pair->tfm_send);
+
+ ctx = sock_kmalloc(sk, len, GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ memset(ctx, 0, len);
+
+ ctx->iv_send = sock_kmalloc(sk, ivlen, GFP_KERNEL);
+ if (!ctx->iv_send) {
+ sock_kfree_s(sk, ctx, len);
+ return -ENOMEM;
+ }
+ memset(ctx->iv_send, 0, ivlen);
+
+ ctx->iv_recv = sock_kmalloc(sk, ivlen, GFP_KERNEL);
+ if (!ctx->iv_recv) {
+ sock_kfree_s(sk, ctx, len);
+ return -ENOMEM;
+ }
+ memset(ctx->iv_recv, 0, ivlen);
+
+ ctx->aead_assoclen = 0;
+ ctx->recv_wanted = -1;
+ af_alg_init_completion(&ctx->completion);
+ INIT_WORK(&ctx->tx_work, tls_tx_work);
+ INIT_WORK(&ctx->rx_work, tls_rx_work);
+
+ ask->private = ctx;
+
+ aead_request_set_tfm(&ctx->aead_req, pair->tfm_send);
+ aead_request_set_tfm(&ctx->aead_resp, pair->tfm_recv);
+ aead_request_set_callback(&ctx->aead_req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+ af_alg_complete, &ctx->completion);
+
+ sk->sk_destruct = tls_sock_destruct;
+
+ return 0;
+}
+
+static const struct af_alg_type algif_type_tls = {
+ .bind = tls_bind,
+ .release = tls_release,
+ .setkey = tls_setkey,
+ .setauthsize = tls_setauthsize,
+ .accept = tls_accept_parent,
+ .ops = &algif_tls_ops,
+ .name = "tls",
+ .owner = THIS_MODULE
+};
+
+static int __init algif_tls_init(void)
+{
+ int err = -ENOMEM;
+
+ tls_wq = create_singlethread_workqueue("ktlsd");
+ if (!tls_wq)
+ goto error;
+
+ err = af_alg_register_type(&algif_type_tls);
+
+ if (!err)
+ return 0;
+error:
+ if (tls_wq)
+ destroy_workqueue(tls_wq);
+ return err;
+}
+
+static void __exit algif_tls_exit(void)
+{
+ af_alg_unregister_type(&algif_type_tls);
+ destroy_workqueue(tls_wq);
+}
+
+module_init(algif_tls_init);
+module_exit(algif_tls_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Watson <davejwatson@xxxxxx>");
+MODULE_DESCRIPTION("TLS kernel crypto API net interface");
--
2.4.6
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/