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-crypto" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html