net/tcp-ao: Use crypto library API instead of crypto_ahash

Currently the kernel's TCP-AO implementation does the MAC and KDF
computations using the crypto_ahash API.  This API is inefficient and
difficult to use, and it has required extensive workarounds in the form
of per-CPU preallocated objects (tcp_sigpool) to work at all.

Let's use lib/crypto/ instead.  This means switching to straightforward
stack-allocated structures, virtually addressed buffers, and direct
function calls.  It also means removing quite a bit of error handling.
This makes TCP-AO quite a bit faster.

This also enables many additional cleanups, which later commits will
handle: removing tcp-sigpool, removing support for crypto_tfm cloning,
removing more error handling, and replacing more dynamically-allocated
buffers with stack buffers based on the now-statically-known limits.

Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
Link: https://patch.msgid.link/20260427172727.9310-3-ebiggers@kernel.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Eric Biggers 2026-04-27 10:27:24 -07:00 committed by Paolo Abeni
parent 5eb0cfedb2
commit 068f5a0095
5 changed files with 306 additions and 314 deletions

View File

@ -2,8 +2,7 @@
#ifndef _TCP_AO_H
#define _TCP_AO_H
#define TCP_AO_KEY_ALIGN 1
#define __tcp_ao_key_align __aligned(TCP_AO_KEY_ALIGN)
#include <crypto/sha2.h> /* for SHA256_DIGEST_SIZE */
union tcp_ao_addr {
struct in_addr a4;
@ -32,11 +31,27 @@ struct tcp_ao_counters {
atomic64_t dropped_icmp;
};
enum tcp_ao_algo_id {
TCP_AO_ALGO_HMAC_SHA1 = 1, /* specified by RFC 5926 */
TCP_AO_ALGO_HMAC_SHA256, /* Linux extension */
TCP_AO_ALGO_AES_128_CMAC, /* specified by RFC 5926 */
};
/*
* This is the maximum untruncated MAC length, in bytes. Note that the MACs
* actually get truncated to 20 or fewer bytes to fit in the TCP options space.
*/
#define TCP_AO_MAX_MAC_LEN SHA256_DIGEST_SIZE
#define TCP_AO_MAX_TRAFFIC_KEY_LEN SHA256_DIGEST_SIZE
struct tcp_ao_mac_ctx;
struct tcp_ao_key {
struct hlist_node node;
union tcp_ao_addr addr;
u8 key[TCP_AO_MAXKEYLEN] __tcp_ao_key_align;
unsigned int tcp_sigpool_id;
u8 key[TCP_AO_MAXKEYLEN];
enum tcp_ao_algo_id algo;
unsigned int digest_size;
int l3index;
u8 prefixlen;
@ -168,7 +183,6 @@ struct tcp6_ao_context {
__be32 disn;
};
struct tcp_sigpool;
/* Established states are fast-path and there always is current_key/rnext_key */
#define TCP_AO_ESTABLISHED (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | \
TCPF_CLOSE_WAIT | TCPF_LAST_ACK | TCPF_CLOSING)
@ -176,6 +190,8 @@ struct tcp_sigpool;
int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
struct tcp_ao_key *key, struct tcphdr *th,
__u8 *hash_location);
void tcp_ao_mac_update(struct tcp_ao_mac_ctx *mac_ctx, const void *data,
size_t data_len);
int tcp_ao_hash_skb(unsigned short int family,
char *ao_hash, struct tcp_ao_key *key,
const struct sock *sk, const struct sk_buff *skb,
@ -188,8 +204,8 @@ struct tcp_ao_key *tcp_ao_established_key(const struct sock *sk,
int tcp_ao_copy_all_matching(const struct sock *sk, struct sock *newsk,
struct request_sock *req, struct sk_buff *skb,
int family);
int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
unsigned int len, struct tcp_sigpool *hp);
void tcp_ao_calc_traffic_key(const struct tcp_ao_key *mkt, u8 *traffic_key,
const void *input, unsigned int input_len);
void tcp_ao_destroy_sock(struct sock *sk, bool twsk);
void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp);
bool tcp_ao_ignore_icmp(const struct sock *sk, int family, int type, int code);
@ -234,7 +250,7 @@ int tcp_v4_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,
const struct sock *sk, const struct sk_buff *skb,
const u8 *tkey, int hash_offset, u32 sne);
/* ipv6 specific functions */
int tcp_v6_ao_hash_pseudoheader(struct tcp_sigpool *hp,
int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx,
const struct in6_addr *daddr,
const struct in6_addr *saddr, int nbytes);
int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key,

View File

@ -746,9 +746,10 @@ config TCP_SIGPOOL
config TCP_AO
bool "TCP: Authentication Option (RFC5925)"
select CRYPTO
select CRYPTO_LIB_AES_CBC_MACS
select CRYPTO_LIB_SHA1
select CRYPTO_LIB_SHA256
select CRYPTO_LIB_UTILS
select TCP_SIGPOOL
depends on 64BIT # seq-number extension needs WRITE_ONCE(u64)
help
TCP-AO specifies the use of stronger Message Authentication Codes (MACs),

View File

@ -9,7 +9,9 @@
*/
#define pr_fmt(fmt) "TCP: " fmt
#include <crypto/hash.h>
#include <crypto/aes-cbc-macs.h>
#include <crypto/sha1.h>
#include <crypto/sha2.h>
#include <crypto/utils.h>
#include <linux/inetdevice.h>
#include <linux/tcp.h>
@ -21,32 +23,133 @@
DEFINE_STATIC_KEY_DEFERRED_FALSE(tcp_ao_needed, HZ);
int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
unsigned int len, struct tcp_sigpool *hp)
static const struct tcp_ao_algo {
const char *name;
unsigned int digest_size;
} tcp_ao_algos[] = {
[TCP_AO_ALGO_HMAC_SHA1] = {
.name = "hmac(sha1)",
.digest_size = SHA1_DIGEST_SIZE,
},
[TCP_AO_ALGO_HMAC_SHA256] = {
.name = "hmac(sha256)",
.digest_size = SHA256_DIGEST_SIZE,
},
[TCP_AO_ALGO_AES_128_CMAC] = {
.name = "cmac(aes128)",
.digest_size = AES_BLOCK_SIZE, /* same as AES_KEYSIZE_128 */
},
};
struct tcp_ao_mac_ctx {
enum tcp_ao_algo_id algo;
union {
struct hmac_sha1_ctx hmac_sha1;
struct hmac_sha256_ctx hmac_sha256;
struct {
struct aes_cmac_key key;
struct aes_cmac_ctx ctx;
} aes_cmac;
};
};
static const struct tcp_ao_algo *tcp_ao_find_algo(const char *name)
{
struct scatterlist sg;
int ret;
for (size_t i = 0; i < ARRAY_SIZE(tcp_ao_algos); i++) {
const struct tcp_ao_algo *algo = &tcp_ao_algos[i];
if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp->req),
mkt->key, mkt->keylen))
goto clear_hash;
if (!algo->name)
continue;
if (WARN_ON_ONCE(algo->digest_size > TCP_AO_MAX_MAC_LEN ||
algo->digest_size >
TCP_AO_MAX_TRAFFIC_KEY_LEN))
continue;
if (strcmp(name, algo->name) == 0)
return algo;
}
return NULL;
}
ret = crypto_ahash_init(hp->req);
if (ret)
goto clear_hash;
static void tcp_ao_mac_init(struct tcp_ao_mac_ctx *mac_ctx,
enum tcp_ao_algo_id algo, const u8 *traffic_key)
{
mac_ctx->algo = algo;
switch (mac_ctx->algo) {
case TCP_AO_ALGO_HMAC_SHA1:
hmac_sha1_init_usingrawkey(&mac_ctx->hmac_sha1, traffic_key,
SHA1_DIGEST_SIZE);
return;
case TCP_AO_ALGO_HMAC_SHA256:
hmac_sha256_init_usingrawkey(&mac_ctx->hmac_sha256, traffic_key,
SHA256_DIGEST_SIZE);
return;
case TCP_AO_ALGO_AES_128_CMAC:
aes_cmac_preparekey(&mac_ctx->aes_cmac.key, traffic_key,
AES_KEYSIZE_128);
aes_cmac_init(&mac_ctx->aes_cmac.ctx, &mac_ctx->aes_cmac.key);
return;
default:
WARN_ON_ONCE(1); /* algo was validated earlier. */
}
}
sg_init_one(&sg, ctx, len);
ahash_request_set_crypt(hp->req, &sg, key, len);
crypto_ahash_update(hp->req);
void tcp_ao_mac_update(struct tcp_ao_mac_ctx *mac_ctx, const void *data,
size_t data_len)
{
switch (mac_ctx->algo) {
case TCP_AO_ALGO_HMAC_SHA1:
hmac_sha1_update(&mac_ctx->hmac_sha1, data, data_len);
return;
case TCP_AO_ALGO_HMAC_SHA256:
hmac_sha256_update(&mac_ctx->hmac_sha256, data, data_len);
return;
case TCP_AO_ALGO_AES_128_CMAC:
aes_cmac_update(&mac_ctx->aes_cmac.ctx, data, data_len);
return;
default:
WARN_ON_ONCE(1); /* algo was validated earlier. */
}
}
ret = crypto_ahash_final(hp->req);
if (ret)
goto clear_hash;
static void tcp_ao_mac_final(struct tcp_ao_mac_ctx *mac_ctx, u8 *out)
{
switch (mac_ctx->algo) {
case TCP_AO_ALGO_HMAC_SHA1:
hmac_sha1_final(&mac_ctx->hmac_sha1, out);
return;
case TCP_AO_ALGO_HMAC_SHA256:
hmac_sha256_final(&mac_ctx->hmac_sha256, out);
return;
case TCP_AO_ALGO_AES_128_CMAC:
aes_cmac_final(&mac_ctx->aes_cmac.ctx, out);
return;
default:
WARN_ON_ONCE(1); /* algo was validated earlier. */
}
}
return 0;
clear_hash:
memset(key, 0, tcp_ao_digest_size(mkt));
return 1;
void tcp_ao_calc_traffic_key(const struct tcp_ao_key *mkt, u8 *traffic_key,
const void *input, unsigned int input_len)
{
switch (mkt->algo) {
case TCP_AO_ALGO_HMAC_SHA1:
hmac_sha1_usingrawkey(mkt->key, mkt->keylen, input, input_len,
traffic_key);
return;
case TCP_AO_ALGO_HMAC_SHA256:
hmac_sha256_usingrawkey(mkt->key, mkt->keylen, input, input_len,
traffic_key);
return;
case TCP_AO_ALGO_AES_128_CMAC: {
struct aes_cmac_key k;
aes_cmac_preparekey(&k, mkt->key, AES_KEYSIZE_128);
aes_cmac(&k, input, input_len, traffic_key);
return;
}
default:
WARN_ON_ONCE(1); /* algo was validated earlier. */
}
}
bool tcp_ao_ignore_icmp(const struct sock *sk, int family, int type, int code)
@ -254,7 +357,6 @@ static struct tcp_ao_key *tcp_ao_copy_key(struct sock *sk,
*new_key = *key;
INIT_HLIST_NODE(&new_key->node);
tcp_sigpool_get(new_key->tcp_sigpool_id);
atomic64_set(&new_key->pkt_good, 0);
atomic64_set(&new_key->pkt_bad, 0);
@ -265,7 +367,6 @@ static void tcp_ao_key_free_rcu(struct rcu_head *head)
{
struct tcp_ao_key *key = container_of(head, struct tcp_ao_key, rcu);
tcp_sigpool_release(key->tcp_sigpool_id);
kfree_sensitive(key);
}
@ -276,7 +377,6 @@ static void tcp_ao_info_free(struct tcp_ao_info *ao)
hlist_for_each_entry_safe(key, n, &ao->head, node) {
hlist_del(&key->node);
tcp_sigpool_release(key->tcp_sigpool_id);
kfree_sensitive(key);
}
kfree(ao);
@ -346,29 +446,22 @@ static int tcp_v4_ao_calc_key(struct tcp_ao_key *mkt, u8 *key,
u8 label[6];
struct tcp4_ao_context ctx;
__be16 outlen;
} __packed * tmp;
struct tcp_sigpool hp;
int err;
} __packed input = {
.counter = 1,
.label = "TCP-AO",
.ctx = {
.saddr = saddr,
.daddr = daddr,
.sport = sport,
.dport = dport,
.sisn = sisn,
.disn = disn,
},
.outlen = htons(tcp_ao_digest_size(mkt) * 8), /* in bits */
};
err = tcp_sigpool_start(mkt->tcp_sigpool_id, &hp);
if (err)
return err;
tmp = hp.scratch;
tmp->counter = 1;
memcpy(tmp->label, "TCP-AO", 6);
tmp->ctx.saddr = saddr;
tmp->ctx.daddr = daddr;
tmp->ctx.sport = sport;
tmp->ctx.dport = dport;
tmp->ctx.sisn = sisn;
tmp->ctx.disn = disn;
tmp->outlen = htons(tcp_ao_digest_size(mkt) * 8); /* in bits */
err = tcp_ao_calc_traffic_key(mkt, key, tmp, sizeof(*tmp), &hp);
tcp_sigpool_end(&hp);
return err;
tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input));
return 0;
}
int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
@ -435,40 +528,37 @@ static int tcp_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key,
return -EAFNOSUPPORT;
}
static int tcp_v4_ao_hash_pseudoheader(struct tcp_sigpool *hp,
static int tcp_v4_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx,
__be32 daddr, __be32 saddr,
int nbytes)
{
struct tcp4_pseudohdr *bp;
struct scatterlist sg;
struct tcp4_pseudohdr phdr = {
.saddr = saddr,
.daddr = daddr,
.pad = 0,
.protocol = IPPROTO_TCP,
.len = cpu_to_be16(nbytes),
};
bp = hp->scratch;
bp->saddr = saddr;
bp->daddr = daddr;
bp->pad = 0;
bp->protocol = IPPROTO_TCP;
bp->len = cpu_to_be16(nbytes);
sg_init_one(&sg, bp, sizeof(*bp));
ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
return crypto_ahash_update(hp->req);
tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr));
return 0;
}
static int tcp_ao_hash_pseudoheader(unsigned short int family,
const struct sock *sk,
const struct sk_buff *skb,
struct tcp_sigpool *hp, int nbytes)
struct tcp_ao_mac_ctx *mac_ctx, int nbytes)
{
const struct tcphdr *th = tcp_hdr(skb);
/* TODO: Can we rely on checksum being zero to mean outbound pkt? */
if (!th->check) {
if (family == AF_INET)
return tcp_v4_ao_hash_pseudoheader(hp, sk->sk_daddr,
return tcp_v4_ao_hash_pseudoheader(mac_ctx, sk->sk_daddr,
sk->sk_rcv_saddr, skb->len);
#if IS_ENABLED(CONFIG_IPV6)
else if (family == AF_INET6)
return tcp_v6_ao_hash_pseudoheader(hp, &sk->sk_v6_daddr,
return tcp_v6_ao_hash_pseudoheader(mac_ctx, &sk->sk_v6_daddr,
&sk->sk_v6_rcv_saddr, skb->len);
#endif
else
@ -478,13 +568,13 @@ static int tcp_ao_hash_pseudoheader(unsigned short int family,
if (family == AF_INET) {
const struct iphdr *iph = ip_hdr(skb);
return tcp_v4_ao_hash_pseudoheader(hp, iph->daddr,
return tcp_v4_ao_hash_pseudoheader(mac_ctx, iph->daddr,
iph->saddr, skb->len);
#if IS_ENABLED(CONFIG_IPV6)
} else if (family == AF_INET6) {
const struct ipv6hdr *iph = ipv6_hdr(skb);
return tcp_v6_ao_hash_pseudoheader(hp, &iph->daddr,
return tcp_v6_ao_hash_pseudoheader(mac_ctx, &iph->daddr,
&iph->saddr, skb->len);
#endif
}
@ -506,31 +596,20 @@ u32 tcp_ao_compute_sne(u32 next_sne, u32 next_seq, u32 seq)
return sne;
}
/* tcp_ao_hash_sne(struct tcp_sigpool *hp)
* @hp - used for hashing
* @sne - sne value
*/
static int tcp_ao_hash_sne(struct tcp_sigpool *hp, u32 sne)
static void tcp_ao_hash_sne(struct tcp_ao_mac_ctx *mac_ctx, u32 sne)
{
struct scatterlist sg;
__be32 *bp;
__be32 sne_be32 = htonl(sne);
bp = (__be32 *)hp->scratch;
*bp = htonl(sne);
sg_init_one(&sg, bp, sizeof(*bp));
ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
return crypto_ahash_update(hp->req);
tcp_ao_mac_update(mac_ctx, &sne_be32, sizeof(sne_be32));
}
static int tcp_ao_hash_header(struct tcp_sigpool *hp,
const struct tcphdr *th,
bool exclude_options, u8 *hash,
int hash_offset, int hash_len)
static void tcp_ao_hash_header(struct tcp_ao_mac_ctx *mac_ctx,
const struct tcphdr *th, bool exclude_options,
u8 *hash, int hash_offset, int hash_len)
{
struct scatterlist sg;
u8 *hdr = hp->scratch;
int err, len;
/* Full TCP header (th->doff << 2) should fit into scratch area. */
u8 hdr[60];
int len;
/* We are not allowed to change tcphdr, make a local copy */
if (exclude_options) {
@ -550,11 +629,7 @@ static int tcp_ao_hash_header(struct tcp_sigpool *hp,
memset(hdr + hash_offset, 0, hash_len);
}
sg_init_one(&sg, hdr, len);
ahash_request_set_crypt(hp->req, &sg, NULL, len);
err = crypto_ahash_update(hp->req);
WARN_ON_ONCE(err != 0);
return err;
tcp_ao_mac_update(mac_ctx, hdr, len);
}
int tcp_ao_hash_hdr(unsigned short int family, char *ao_hash,
@ -563,109 +638,92 @@ int tcp_ao_hash_hdr(unsigned short int family, char *ao_hash,
const union tcp_ao_addr *saddr,
const struct tcphdr *th, u32 sne)
{
int tkey_len = tcp_ao_digest_size(key);
int hash_offset = ao_hash - (char *)th;
struct tcp_sigpool hp;
void *hash_buf = NULL;
struct tcp_ao_mac_ctx mac_ctx;
u8 hash_buf[TCP_AO_MAX_MAC_LEN];
hash_buf = kmalloc(tkey_len, GFP_ATOMIC);
if (!hash_buf)
goto clear_hash_noput;
if (tcp_sigpool_start(key->tcp_sigpool_id, &hp))
goto clear_hash_noput;
if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp.req), tkey, tkey_len))
goto clear_hash;
if (crypto_ahash_init(hp.req))
goto clear_hash;
if (tcp_ao_hash_sne(&hp, sne))
goto clear_hash;
tcp_ao_mac_init(&mac_ctx, key->algo, tkey);
tcp_ao_hash_sne(&mac_ctx, sne);
if (family == AF_INET) {
if (tcp_v4_ao_hash_pseudoheader(&hp, daddr->a4.s_addr,
saddr->a4.s_addr, th->doff * 4))
goto clear_hash;
tcp_v4_ao_hash_pseudoheader(&mac_ctx, daddr->a4.s_addr,
saddr->a4.s_addr, th->doff * 4);
#if IS_ENABLED(CONFIG_IPV6)
} else if (family == AF_INET6) {
if (tcp_v6_ao_hash_pseudoheader(&hp, &daddr->a6,
&saddr->a6, th->doff * 4))
goto clear_hash;
tcp_v6_ao_hash_pseudoheader(&mac_ctx, &daddr->a6,
&saddr->a6, th->doff * 4);
#endif
} else {
WARN_ON_ONCE(1);
goto clear_hash;
}
if (tcp_ao_hash_header(&hp, th,
!!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT),
ao_hash, hash_offset, tcp_ao_maclen(key)))
goto clear_hash;
ahash_request_set_crypt(hp.req, NULL, hash_buf, 0);
if (crypto_ahash_final(hp.req))
goto clear_hash;
tcp_ao_hash_header(&mac_ctx, th,
!!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT),
ao_hash, hash_offset, tcp_ao_maclen(key));
tcp_ao_mac_final(&mac_ctx, hash_buf);
memcpy(ao_hash, hash_buf, tcp_ao_maclen(key));
tcp_sigpool_end(&hp);
kfree(hash_buf);
return 0;
clear_hash:
tcp_sigpool_end(&hp);
clear_hash_noput:
memset(ao_hash, 0, tcp_ao_maclen(key));
kfree(hash_buf);
return 1;
}
static void tcp_ao_hash_skb_data(struct tcp_ao_mac_ctx *mac_ctx,
const struct sk_buff *skb,
unsigned int header_len)
{
const unsigned int head_data_len = skb_headlen(skb) > header_len ?
skb_headlen(skb) - header_len : 0;
const struct skb_shared_info *shi = skb_shinfo(skb);
struct sk_buff *frag_iter;
unsigned int i;
tcp_ao_mac_update(mac_ctx, (const u8 *)tcp_hdr(skb) + header_len,
head_data_len);
for (i = 0; i < shi->nr_frags; ++i) {
const skb_frag_t *f = &shi->frags[i];
u32 p_off, p_len, copied;
const void *vaddr;
struct page *p;
skb_frag_foreach_page(f, skb_frag_off(f), skb_frag_size(f),
p, p_off, p_len, copied) {
vaddr = kmap_local_page(p);
tcp_ao_mac_update(mac_ctx, vaddr + p_off, p_len);
kunmap_local(vaddr);
}
}
skb_walk_frags(skb, frag_iter)
tcp_ao_hash_skb_data(mac_ctx, frag_iter, 0);
}
int tcp_ao_hash_skb(unsigned short int family,
char *ao_hash, struct tcp_ao_key *key,
const struct sock *sk, const struct sk_buff *skb,
const u8 *tkey, int hash_offset, u32 sne)
{
const struct tcphdr *th = tcp_hdr(skb);
int tkey_len = tcp_ao_digest_size(key);
struct tcp_sigpool hp;
void *hash_buf = NULL;
struct tcp_ao_mac_ctx mac_ctx;
u8 hash_buf[TCP_AO_MAX_MAC_LEN];
hash_buf = kmalloc(tkey_len, GFP_ATOMIC);
if (!hash_buf)
goto clear_hash_noput;
if (tcp_sigpool_start(key->tcp_sigpool_id, &hp))
goto clear_hash_noput;
if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp.req), tkey, tkey_len))
goto clear_hash;
/* For now use sha1 by default. Depends on alg in tcp_ao_key */
if (crypto_ahash_init(hp.req))
goto clear_hash;
if (tcp_ao_hash_sne(&hp, sne))
goto clear_hash;
if (tcp_ao_hash_pseudoheader(family, sk, skb, &hp, skb->len))
goto clear_hash;
if (tcp_ao_hash_header(&hp, th,
!!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT),
ao_hash, hash_offset, tcp_ao_maclen(key)))
goto clear_hash;
if (tcp_sigpool_hash_skb_data(&hp, skb, th->doff << 2))
goto clear_hash;
ahash_request_set_crypt(hp.req, NULL, hash_buf, 0);
if (crypto_ahash_final(hp.req))
tcp_ao_mac_init(&mac_ctx, key->algo, tkey);
tcp_ao_hash_sne(&mac_ctx, sne);
if (tcp_ao_hash_pseudoheader(family, sk, skb, &mac_ctx, skb->len))
goto clear_hash;
tcp_ao_hash_header(&mac_ctx, th,
!!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT),
ao_hash, hash_offset, tcp_ao_maclen(key));
tcp_ao_hash_skb_data(&mac_ctx, skb, th->doff << 2);
tcp_ao_mac_final(&mac_ctx, hash_buf);
memcpy(ao_hash, hash_buf, tcp_ao_maclen(key));
tcp_sigpool_end(&hp);
kfree(hash_buf);
return 0;
clear_hash:
tcp_sigpool_end(&hp);
clear_hash_noput:
memset(ao_hash, 0, tcp_ao_maclen(key));
kfree(hash_buf);
return 1;
}
@ -1280,7 +1338,6 @@ int tcp_ao_copy_all_matching(const struct sock *sk, struct sock *newsk,
free_and_exit:
hlist_for_each_entry_safe(key, key_head, &new_ao->head, node) {
hlist_del(&key->node);
tcp_sigpool_release(key->tcp_sigpool_id);
atomic_sub(tcp_ao_sizeof_key(key), &newsk->sk_omem_alloc);
kfree_sensitive(key);
}
@ -1336,23 +1393,10 @@ static int tcp_ao_verify_ipv4(struct sock *sk, struct tcp_ao_add *cmd,
return 0;
}
static int tcp_ao_parse_crypto(struct tcp_ao_add *cmd, struct tcp_ao_key *key)
static int tcp_ao_parse_crypto(const struct tcp_ao_add *cmd,
struct tcp_ao_key *key)
{
unsigned int syn_tcp_option_space;
bool is_kdf_aes_128_cmac = false;
struct crypto_ahash *tfm;
struct tcp_sigpool hp;
void *tmp_key = NULL;
int err;
/* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */
if (!strcmp("cmac(aes128)", cmd->alg_name)) {
strscpy(cmd->alg_name, "cmac(aes)", sizeof(cmd->alg_name));
is_kdf_aes_128_cmac = (cmd->keylen != 16);
tmp_key = kmalloc(cmd->keylen, GFP_KERNEL);
if (!tmp_key)
return -ENOMEM;
}
key->maclen = cmd->maclen ?: 12; /* 12 is the default in RFC5925 */
@ -1388,64 +1432,27 @@ static int tcp_ao_parse_crypto(struct tcp_ao_add *cmd, struct tcp_ao_key *key)
syn_tcp_option_space -= TCPOLEN_MSS_ALIGNED;
syn_tcp_option_space -= TCPOLEN_TSTAMP_ALIGNED;
syn_tcp_option_space -= TCPOLEN_WSCALE_ALIGNED;
if (tcp_ao_len_aligned(key) > syn_tcp_option_space) {
err = -EMSGSIZE;
goto err_kfree;
if (tcp_ao_len_aligned(key) > syn_tcp_option_space)
return -EMSGSIZE;
if (key->algo == TCP_AO_ALGO_AES_128_CMAC &&
cmd->keylen != AES_KEYSIZE_128) {
/* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */
static const u8 zeroes[AES_KEYSIZE_128];
struct aes_cmac_key extractor;
aes_cmac_preparekey(&extractor, zeroes, AES_KEYSIZE_128);
aes_cmac(&extractor, cmd->key, cmd->keylen, key->key);
key->keylen = AES_KEYSIZE_128;
} else {
memcpy(key->key, cmd->key, cmd->keylen);
key->keylen = cmd->keylen;
}
key->keylen = cmd->keylen;
memcpy(key->key, cmd->key, cmd->keylen);
err = tcp_sigpool_start(key->tcp_sigpool_id, &hp);
if (err)
goto err_kfree;
tfm = crypto_ahash_reqtfm(hp.req);
if (is_kdf_aes_128_cmac) {
void *scratch = hp.scratch;
struct scatterlist sg;
memcpy(tmp_key, cmd->key, cmd->keylen);
sg_init_one(&sg, tmp_key, cmd->keylen);
/* Using zero-key of 16 bytes as described in RFC5926 */
memset(scratch, 0, 16);
err = crypto_ahash_setkey(tfm, scratch, 16);
if (err)
goto err_pool_end;
err = crypto_ahash_init(hp.req);
if (err)
goto err_pool_end;
ahash_request_set_crypt(hp.req, &sg, key->key, cmd->keylen);
err = crypto_ahash_update(hp.req);
if (err)
goto err_pool_end;
err |= crypto_ahash_final(hp.req);
if (err)
goto err_pool_end;
key->keylen = 16;
}
err = crypto_ahash_setkey(tfm, key->key, key->keylen);
if (err)
goto err_pool_end;
tcp_sigpool_end(&hp);
kfree_sensitive(tmp_key);
if (tcp_ao_maclen(key) > key->digest_size)
return -EINVAL;
return 0;
err_pool_end:
tcp_sigpool_end(&hp);
err_kfree:
kfree_sensitive(tmp_key);
return err;
}
#if IS_ENABLED(CONFIG_IPV6)
@ -1549,54 +1556,33 @@ static struct tcp_ao_info *getsockopt_ao_info(struct sock *sk)
static struct tcp_ao_key *tcp_ao_key_alloc(struct sock *sk,
struct tcp_ao_add *cmd)
{
const char *algo = cmd->alg_name;
unsigned int digest_size;
struct crypto_ahash *tfm;
const struct tcp_ao_algo *algo;
struct tcp_ao_key *key;
struct tcp_sigpool hp;
int err, pool_id;
size_t size;
/* Force null-termination of alg_name */
cmd->alg_name[ARRAY_SIZE(cmd->alg_name) - 1] = '\0';
/* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */
if (!strcmp("cmac(aes128)", algo))
algo = "cmac(aes)";
else if (strcmp("hmac(sha1)", algo) &&
strcmp("hmac(sha256)", algo) &&
(strcmp("cmac(aes)", algo) || cmd->keylen != 16))
/*
* For backwards compatibility, accept "cmac(aes)" as an alias for
* "cmac(aes128)", provided that the key length is exactly 128 bits.
*/
if (strcmp(cmd->alg_name, "cmac(aes)") == 0 &&
cmd->keylen == AES_KEYSIZE_128)
strscpy(cmd->alg_name, "cmac(aes128)");
algo = tcp_ao_find_algo(cmd->alg_name);
if (!algo)
return ERR_PTR(-ENOENT);
/* Full TCP header (th->doff << 2) should fit into scratch area,
* see tcp_ao_hash_header().
*/
pool_id = tcp_sigpool_alloc_ahash(algo, 60);
if (pool_id < 0)
return ERR_PTR(pool_id);
err = tcp_sigpool_start(pool_id, &hp);
if (err)
goto err_free_pool;
tfm = crypto_ahash_reqtfm(hp.req);
digest_size = crypto_ahash_digestsize(tfm);
tcp_sigpool_end(&hp);
size = sizeof(struct tcp_ao_key) + (digest_size << 1);
size = sizeof(struct tcp_ao_key) + (algo->digest_size << 1);
key = sock_kmalloc(sk, size, GFP_KERNEL);
if (!key) {
err = -ENOMEM;
goto err_free_pool;
}
if (!key)
return ERR_PTR(-ENOMEM);
key->tcp_sigpool_id = pool_id;
key->digest_size = digest_size;
key->algo = algo - tcp_ao_algos;
key->digest_size = algo->digest_size;
return key;
err_free_pool:
tcp_sigpool_release(pool_id);
return ERR_PTR(err);
}
static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family,
@ -1757,7 +1743,6 @@ static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family,
err_free_sock:
atomic_sub(tcp_ao_sizeof_key(key), &sk->sk_omem_alloc);
tcp_sigpool_release(key->tcp_sigpool_id);
kfree_sensitive(key);
err_free_ao:
if (first)
@ -2288,7 +2273,11 @@ static int tcp_ao_copy_mkts_to_user(const struct sock *sk,
opt_out.pkt_good = atomic64_read(&key->pkt_good);
opt_out.pkt_bad = atomic64_read(&key->pkt_bad);
memcpy(&opt_out.key, key->key, key->keylen);
tcp_sigpool_algo(key->tcp_sigpool_id, opt_out.alg_name, 64);
if (key->algo == TCP_AO_ALGO_AES_128_CMAC)
/* This is needed for backwards compatibility. */
strscpy(opt_out.alg_name, "cmac(aes)");
else
strscpy(opt_out.alg_name, tcp_ao_algos[key->algo].name);
/* Copy key to user */
if (copy_to_sockptr_offset(optval, out_offset,

View File

@ -7,7 +7,6 @@
* Francesco Ruggeri <fruggeri@arista.com>
* Salam Noureddine <noureddine@arista.com>
*/
#include <crypto/hash.h>
#include <linux/tcp.h>
#include <net/tcp.h>
@ -24,29 +23,22 @@ static int tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key,
u8 label[6];
struct tcp6_ao_context ctx;
__be16 outlen;
} __packed * tmp;
struct tcp_sigpool hp;
int err;
} __packed input = {
.counter = 1,
.label = "TCP-AO",
.ctx = {
.saddr = *saddr,
.daddr = *daddr,
.sport = sport,
.dport = dport,
.sisn = sisn,
.disn = disn,
},
.outlen = htons(tcp_ao_digest_size(mkt) * 8), /* in bits */
};
err = tcp_sigpool_start(mkt->tcp_sigpool_id, &hp);
if (err)
return err;
tmp = hp.scratch;
tmp->counter = 1;
memcpy(tmp->label, "TCP-AO", 6);
tmp->ctx.saddr = *saddr;
tmp->ctx.daddr = *daddr;
tmp->ctx.sport = sport;
tmp->ctx.dport = dport;
tmp->ctx.sisn = sisn;
tmp->ctx.disn = disn;
tmp->outlen = htons(tcp_ao_digest_size(mkt) * 8); /* in bits */
err = tcp_ao_calc_traffic_key(mkt, key, tmp, sizeof(*tmp), &hp);
tcp_sigpool_end(&hp);
return err;
tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input));
return 0;
}
int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key,
@ -112,23 +104,20 @@ struct tcp_ao_key *tcp_v6_ao_lookup_rsk(const struct sock *sk,
AF_INET6, sndid, rcvid);
}
int tcp_v6_ao_hash_pseudoheader(struct tcp_sigpool *hp,
int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx,
const struct in6_addr *daddr,
const struct in6_addr *saddr, int nbytes)
{
struct tcp6_pseudohdr *bp;
struct scatterlist sg;
bp = hp->scratch;
/* 1. TCP pseudo-header (RFC2460) */
bp->saddr = *saddr;
bp->daddr = *daddr;
bp->len = cpu_to_be32(nbytes);
bp->protocol = cpu_to_be32(IPPROTO_TCP);
struct tcp6_pseudohdr phdr = {
.saddr = *saddr,
.daddr = *daddr,
.len = cpu_to_be32(nbytes),
.protocol = cpu_to_be32(IPPROTO_TCP),
};
sg_init_one(&sg, bp, sizeof(*bp));
ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp));
return crypto_ahash_update(hp->req);
tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr));
return 0;
}
int tcp_v6_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key,

View File

@ -1,6 +1,3 @@
CONFIG_CRYPTO_CMAC=y
CONFIG_CRYPTO_HMAC=y
CONFIG_CRYPTO_SHA1=y
CONFIG_IPV6=y
CONFIG_IPV6_MULTIPLE_TABLES=y
CONFIG_NET_L3_MASTER_DEV=y