forked from Minki/linux
d25f2f71f6
Initialize the space reserved for storing the TLS keys, get and free the location where key is stored for the TLS connection. Program the Tx and Rx key as received from user in struct tls12_crypto_info_aes_gcm_128 and understood by hardware. added socket option TLS_RX Signed-off-by: Atul Gupta <atul.gupta@chelsio.com> Signed-off-by: David S. Miller <davem@davemloft.net>
413 lines
10 KiB
C
413 lines
10 KiB
C
/*
|
|
* Copyright (c) 2018 Chelsio Communications, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Written by: Atul Gupta (atul.gupta@chelsio.com)
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/list.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/tls.h>
|
|
#include <net/tls.h>
|
|
|
|
#include "chtls.h"
|
|
#include "chtls_cm.h"
|
|
|
|
static void __set_tcb_field_direct(struct chtls_sock *csk,
|
|
struct cpl_set_tcb_field *req, u16 word,
|
|
u64 mask, u64 val, u8 cookie, int no_reply)
|
|
{
|
|
struct ulptx_idata *sc;
|
|
|
|
INIT_TP_WR_CPL(req, CPL_SET_TCB_FIELD, csk->tid);
|
|
req->wr.wr_mid |= htonl(FW_WR_FLOWID_V(csk->tid));
|
|
req->reply_ctrl = htons(NO_REPLY_V(no_reply) |
|
|
QUEUENO_V(csk->rss_qid));
|
|
req->word_cookie = htons(TCB_WORD_V(word) | TCB_COOKIE_V(cookie));
|
|
req->mask = cpu_to_be64(mask);
|
|
req->val = cpu_to_be64(val);
|
|
sc = (struct ulptx_idata *)(req + 1);
|
|
sc->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_NOOP));
|
|
sc->len = htonl(0);
|
|
}
|
|
|
|
static void __set_tcb_field(struct sock *sk, struct sk_buff *skb, u16 word,
|
|
u64 mask, u64 val, u8 cookie, int no_reply)
|
|
{
|
|
struct cpl_set_tcb_field *req;
|
|
struct chtls_sock *csk;
|
|
struct ulptx_idata *sc;
|
|
unsigned int wrlen;
|
|
|
|
wrlen = roundup(sizeof(*req) + sizeof(*sc), 16);
|
|
csk = rcu_dereference_sk_user_data(sk);
|
|
|
|
req = (struct cpl_set_tcb_field *)__skb_put(skb, wrlen);
|
|
__set_tcb_field_direct(csk, req, word, mask, val, cookie, no_reply);
|
|
set_wr_txq(skb, CPL_PRIORITY_CONTROL, csk->port_id);
|
|
}
|
|
|
|
/*
|
|
* Send control message to HW, message go as immediate data and packet
|
|
* is freed immediately.
|
|
*/
|
|
static int chtls_set_tcb_field(struct sock *sk, u16 word, u64 mask, u64 val)
|
|
{
|
|
struct cpl_set_tcb_field *req;
|
|
unsigned int credits_needed;
|
|
struct chtls_sock *csk;
|
|
struct ulptx_idata *sc;
|
|
struct sk_buff *skb;
|
|
unsigned int wrlen;
|
|
int ret;
|
|
|
|
wrlen = roundup(sizeof(*req) + sizeof(*sc), 16);
|
|
|
|
skb = alloc_skb(wrlen, GFP_ATOMIC);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
credits_needed = DIV_ROUND_UP(wrlen, 16);
|
|
csk = rcu_dereference_sk_user_data(sk);
|
|
|
|
__set_tcb_field(sk, skb, word, mask, val, 0, 1);
|
|
skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA);
|
|
csk->wr_credits -= credits_needed;
|
|
csk->wr_unacked += credits_needed;
|
|
enqueue_wr(csk, skb);
|
|
ret = cxgb4_ofld_send(csk->egress_dev, skb);
|
|
if (ret < 0)
|
|
kfree_skb(skb);
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
/*
|
|
* Set one of the t_flags bits in the TCB.
|
|
*/
|
|
int chtls_set_tcb_tflag(struct sock *sk, unsigned int bit_pos, int val)
|
|
{
|
|
return chtls_set_tcb_field(sk, 1, 1ULL << bit_pos,
|
|
val << bit_pos);
|
|
}
|
|
|
|
static int chtls_set_tcb_keyid(struct sock *sk, int keyid)
|
|
{
|
|
return chtls_set_tcb_field(sk, 31, 0xFFFFFFFFULL, keyid);
|
|
}
|
|
|
|
static int chtls_set_tcb_seqno(struct sock *sk)
|
|
{
|
|
return chtls_set_tcb_field(sk, 28, ~0ULL, 0);
|
|
}
|
|
|
|
static int chtls_set_tcb_quiesce(struct sock *sk, int val)
|
|
{
|
|
return chtls_set_tcb_field(sk, 1, (1ULL << TF_RX_QUIESCE_S),
|
|
TF_RX_QUIESCE_V(val));
|
|
}
|
|
|
|
/* TLS Key bitmap processing */
|
|
int chtls_init_kmap(struct chtls_dev *cdev, struct cxgb4_lld_info *lldi)
|
|
{
|
|
unsigned int num_key_ctx, bsize;
|
|
int ksize;
|
|
|
|
num_key_ctx = (lldi->vr->key.size / TLS_KEY_CONTEXT_SZ);
|
|
bsize = BITS_TO_LONGS(num_key_ctx);
|
|
|
|
cdev->kmap.size = num_key_ctx;
|
|
cdev->kmap.available = bsize;
|
|
ksize = sizeof(*cdev->kmap.addr) * bsize;
|
|
cdev->kmap.addr = kvzalloc(ksize, GFP_KERNEL);
|
|
if (!cdev->kmap.addr)
|
|
return -ENOMEM;
|
|
|
|
cdev->kmap.start = lldi->vr->key.start;
|
|
spin_lock_init(&cdev->kmap.lock);
|
|
return 0;
|
|
}
|
|
|
|
static int get_new_keyid(struct chtls_sock *csk, u32 optname)
|
|
{
|
|
struct net_device *dev = csk->egress_dev;
|
|
struct chtls_dev *cdev = csk->cdev;
|
|
struct chtls_hws *hws;
|
|
struct adapter *adap;
|
|
int keyid;
|
|
|
|
adap = netdev2adap(dev);
|
|
hws = &csk->tlshws;
|
|
|
|
spin_lock_bh(&cdev->kmap.lock);
|
|
keyid = find_first_zero_bit(cdev->kmap.addr, cdev->kmap.size);
|
|
if (keyid < cdev->kmap.size) {
|
|
__set_bit(keyid, cdev->kmap.addr);
|
|
if (optname == TLS_RX)
|
|
hws->rxkey = keyid;
|
|
else
|
|
hws->txkey = keyid;
|
|
atomic_inc(&adap->chcr_stats.tls_key);
|
|
} else {
|
|
keyid = -1;
|
|
}
|
|
spin_unlock_bh(&cdev->kmap.lock);
|
|
return keyid;
|
|
}
|
|
|
|
void free_tls_keyid(struct sock *sk)
|
|
{
|
|
struct chtls_sock *csk = rcu_dereference_sk_user_data(sk);
|
|
struct net_device *dev = csk->egress_dev;
|
|
struct chtls_dev *cdev = csk->cdev;
|
|
struct chtls_hws *hws;
|
|
struct adapter *adap;
|
|
|
|
if (!cdev->kmap.addr)
|
|
return;
|
|
|
|
adap = netdev2adap(dev);
|
|
hws = &csk->tlshws;
|
|
|
|
spin_lock_bh(&cdev->kmap.lock);
|
|
if (hws->rxkey >= 0) {
|
|
__clear_bit(hws->rxkey, cdev->kmap.addr);
|
|
atomic_dec(&adap->chcr_stats.tls_key);
|
|
hws->rxkey = -1;
|
|
}
|
|
if (hws->txkey >= 0) {
|
|
__clear_bit(hws->txkey, cdev->kmap.addr);
|
|
atomic_dec(&adap->chcr_stats.tls_key);
|
|
hws->txkey = -1;
|
|
}
|
|
spin_unlock_bh(&cdev->kmap.lock);
|
|
}
|
|
|
|
unsigned int keyid_to_addr(int start_addr, int keyid)
|
|
{
|
|
return (start_addr + (keyid * TLS_KEY_CONTEXT_SZ)) >> 5;
|
|
}
|
|
|
|
static void chtls_rxkey_ivauth(struct _key_ctx *kctx)
|
|
{
|
|
kctx->iv_to_auth = cpu_to_be64(KEYCTX_TX_WR_IV_V(6ULL) |
|
|
KEYCTX_TX_WR_AAD_V(1ULL) |
|
|
KEYCTX_TX_WR_AADST_V(5ULL) |
|
|
KEYCTX_TX_WR_CIPHER_V(14ULL) |
|
|
KEYCTX_TX_WR_CIPHERST_V(0ULL) |
|
|
KEYCTX_TX_WR_AUTH_V(14ULL) |
|
|
KEYCTX_TX_WR_AUTHST_V(16ULL) |
|
|
KEYCTX_TX_WR_AUTHIN_V(16ULL));
|
|
}
|
|
|
|
static int chtls_key_info(struct chtls_sock *csk,
|
|
struct _key_ctx *kctx,
|
|
u32 keylen, u32 optname)
|
|
{
|
|
unsigned char key[CHCR_KEYCTX_CIPHER_KEY_SIZE_256];
|
|
struct tls12_crypto_info_aes_gcm_128 *gcm_ctx;
|
|
unsigned char ghash_h[AEAD_H_SIZE];
|
|
struct crypto_cipher *cipher;
|
|
int ck_size, key_ctx_size;
|
|
int ret;
|
|
|
|
gcm_ctx = (struct tls12_crypto_info_aes_gcm_128 *)
|
|
&csk->tlshws.crypto_info;
|
|
|
|
key_ctx_size = sizeof(struct _key_ctx) +
|
|
roundup(keylen, 16) + AEAD_H_SIZE;
|
|
|
|
if (keylen == AES_KEYSIZE_128) {
|
|
ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_128;
|
|
} else if (keylen == AES_KEYSIZE_192) {
|
|
ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_192;
|
|
} else if (keylen == AES_KEYSIZE_256) {
|
|
ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_256;
|
|
} else {
|
|
pr_err("GCM: Invalid key length %d\n", keylen);
|
|
return -EINVAL;
|
|
}
|
|
memcpy(key, gcm_ctx->key, keylen);
|
|
|
|
/* Calculate the H = CIPH(K, 0 repeated 16 times).
|
|
* It will go in key context
|
|
*/
|
|
cipher = crypto_alloc_cipher("aes", 0, 0);
|
|
if (IS_ERR(cipher)) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ret = crypto_cipher_setkey(cipher, key, keylen);
|
|
if (ret)
|
|
goto out1;
|
|
|
|
memset(ghash_h, 0, AEAD_H_SIZE);
|
|
crypto_cipher_encrypt_one(cipher, ghash_h, ghash_h);
|
|
csk->tlshws.keylen = key_ctx_size;
|
|
|
|
/* Copy the Key context */
|
|
if (optname == TLS_RX) {
|
|
int key_ctx;
|
|
|
|
key_ctx = ((key_ctx_size >> 4) << 3);
|
|
kctx->ctx_hdr = FILL_KEY_CRX_HDR(ck_size,
|
|
CHCR_KEYCTX_MAC_KEY_SIZE_128,
|
|
0, 0, key_ctx);
|
|
chtls_rxkey_ivauth(kctx);
|
|
} else {
|
|
kctx->ctx_hdr = FILL_KEY_CTX_HDR(ck_size,
|
|
CHCR_KEYCTX_MAC_KEY_SIZE_128,
|
|
0, 0, key_ctx_size >> 4);
|
|
}
|
|
|
|
memcpy(kctx->salt, gcm_ctx->salt, TLS_CIPHER_AES_GCM_128_SALT_SIZE);
|
|
memcpy(kctx->key, gcm_ctx->key, keylen);
|
|
memcpy(kctx->key + keylen, ghash_h, AEAD_H_SIZE);
|
|
/* erase key info from driver */
|
|
memset(gcm_ctx->key, 0, keylen);
|
|
|
|
out1:
|
|
crypto_free_cipher(cipher);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void chtls_set_scmd(struct chtls_sock *csk)
|
|
{
|
|
struct chtls_hws *hws = &csk->tlshws;
|
|
|
|
hws->scmd.seqno_numivs =
|
|
SCMD_SEQ_NO_CTRL_V(3) |
|
|
SCMD_PROTO_VERSION_V(0) |
|
|
SCMD_ENC_DEC_CTRL_V(0) |
|
|
SCMD_CIPH_AUTH_SEQ_CTRL_V(1) |
|
|
SCMD_CIPH_MODE_V(2) |
|
|
SCMD_AUTH_MODE_V(4) |
|
|
SCMD_HMAC_CTRL_V(0) |
|
|
SCMD_IV_SIZE_V(4) |
|
|
SCMD_NUM_IVS_V(1);
|
|
|
|
hws->scmd.ivgen_hdrlen =
|
|
SCMD_IV_GEN_CTRL_V(1) |
|
|
SCMD_KEY_CTX_INLINE_V(0) |
|
|
SCMD_TLS_FRAG_ENABLE_V(1);
|
|
}
|
|
|
|
int chtls_setkey(struct chtls_sock *csk, u32 keylen, u32 optname)
|
|
{
|
|
struct tls_key_req *kwr;
|
|
struct chtls_dev *cdev;
|
|
struct _key_ctx *kctx;
|
|
int wrlen, klen, len;
|
|
struct sk_buff *skb;
|
|
struct sock *sk;
|
|
int keyid;
|
|
int kaddr;
|
|
int ret;
|
|
|
|
cdev = csk->cdev;
|
|
sk = csk->sk;
|
|
|
|
klen = roundup((keylen + AEAD_H_SIZE) + sizeof(*kctx), 32);
|
|
wrlen = roundup(sizeof(*kwr), 16);
|
|
len = klen + wrlen;
|
|
|
|
/* Flush out-standing data before new key takes effect */
|
|
if (optname == TLS_TX) {
|
|
lock_sock(sk);
|
|
if (skb_queue_len(&csk->txq))
|
|
chtls_push_frames(csk, 0);
|
|
release_sock(sk);
|
|
}
|
|
|
|
skb = alloc_skb(len, GFP_KERNEL);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
keyid = get_new_keyid(csk, optname);
|
|
if (keyid < 0) {
|
|
ret = -ENOSPC;
|
|
goto out_nokey;
|
|
}
|
|
|
|
kaddr = keyid_to_addr(cdev->kmap.start, keyid);
|
|
kwr = (struct tls_key_req *)__skb_put_zero(skb, len);
|
|
kwr->wr.op_to_compl =
|
|
cpu_to_be32(FW_WR_OP_V(FW_ULPTX_WR) | FW_WR_COMPL_F |
|
|
FW_WR_ATOMIC_V(1U));
|
|
kwr->wr.flowid_len16 =
|
|
cpu_to_be32(FW_WR_LEN16_V(DIV_ROUND_UP(len, 16) |
|
|
FW_WR_FLOWID_V(csk->tid)));
|
|
kwr->wr.protocol = 0;
|
|
kwr->wr.mfs = htons(TLS_MFS);
|
|
kwr->wr.reneg_to_write_rx = optname;
|
|
|
|
/* ulptx command */
|
|
kwr->req.cmd = cpu_to_be32(ULPTX_CMD_V(ULP_TX_MEM_WRITE) |
|
|
T5_ULP_MEMIO_ORDER_V(1) |
|
|
T5_ULP_MEMIO_IMM_V(1));
|
|
kwr->req.len16 = cpu_to_be32((csk->tid << 8) |
|
|
DIV_ROUND_UP(len - sizeof(kwr->wr), 16));
|
|
kwr->req.dlen = cpu_to_be32(ULP_MEMIO_DATA_LEN_V(klen >> 5));
|
|
kwr->req.lock_addr = cpu_to_be32(ULP_MEMIO_ADDR_V(kaddr));
|
|
|
|
/* sub command */
|
|
kwr->sc_imm.cmd_more = cpu_to_be32(ULPTX_CMD_V(ULP_TX_SC_IMM));
|
|
kwr->sc_imm.len = cpu_to_be32(klen);
|
|
|
|
/* key info */
|
|
kctx = (struct _key_ctx *)(kwr + 1);
|
|
ret = chtls_key_info(csk, kctx, keylen, optname);
|
|
if (ret)
|
|
goto out_notcb;
|
|
|
|
set_wr_txq(skb, CPL_PRIORITY_DATA, csk->tlshws.txqid);
|
|
csk->wr_credits -= DIV_ROUND_UP(len, 16);
|
|
csk->wr_unacked += DIV_ROUND_UP(len, 16);
|
|
enqueue_wr(csk, skb);
|
|
cxgb4_ofld_send(csk->egress_dev, skb);
|
|
|
|
chtls_set_scmd(csk);
|
|
/* Clear quiesce for Rx key */
|
|
if (optname == TLS_RX) {
|
|
ret = chtls_set_tcb_keyid(sk, keyid);
|
|
if (ret)
|
|
goto out_notcb;
|
|
ret = chtls_set_tcb_field(sk, 0,
|
|
TCB_ULP_RAW_V(TCB_ULP_RAW_M),
|
|
TCB_ULP_RAW_V((TF_TLS_KEY_SIZE_V(1) |
|
|
TF_TLS_CONTROL_V(1) |
|
|
TF_TLS_ACTIVE_V(1) |
|
|
TF_TLS_ENABLE_V(1))));
|
|
if (ret)
|
|
goto out_notcb;
|
|
ret = chtls_set_tcb_seqno(sk);
|
|
if (ret)
|
|
goto out_notcb;
|
|
ret = chtls_set_tcb_quiesce(sk, 0);
|
|
if (ret)
|
|
goto out_notcb;
|
|
csk->tlshws.rxkey = keyid;
|
|
} else {
|
|
csk->tlshws.tx_seq_no = 0;
|
|
csk->tlshws.txkey = keyid;
|
|
}
|
|
|
|
return ret;
|
|
out_notcb:
|
|
free_tls_keyid(sk);
|
|
out_nokey:
|
|
kfree_skb(skb);
|
|
return ret;
|
|
}
|