Skip to content

Commit

Permalink
net/tcp: Add TCP-AO sign to outgoing packets
Browse files Browse the repository at this point in the history
Using precalculated traffic keys, sign TCP segments as prescribed by
RFC5925. Per RFC, TCP header options are included in sign calculation:
"The TCP header, by default including options, and where the TCP
checksum and TCP-AO MAC fields are set to zero, all in network-
byte order." (5.1.3)

tcp_ao_hash_header() has exclude_options parameter to optionally exclude
TCP header from hash calculation, as described in RFC5925 (9.1), this is
needed for interaction with middleboxes that may change "some TCP
options". This is wired up to AO key flags and setsockopt() later.

Similarly to TCP-MD5 hash TCP segment fragments.

From this moment a user can start sending TCP-AO signed segments with
one of crypto ahash algorithms from supported by Linux kernel. It can
have a user-specified MAC length, to either save TCP option header space
or provide higher protection using a longer signature.
The inbound segments are not yet verified, TCP-AO option is ignored and
they are accepted.

Co-developed-by: Francesco Ruggeri <fruggeri@arista.com>
Signed-off-by: Francesco Ruggeri <fruggeri@arista.com>
Co-developed-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Dmitry Safonov <dima@arista.com>
Acked-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Dmitry Safonov authored and David S. Miller committed Oct 27, 2023
1 parent 7c2ffaf commit 1e03d32
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 38 deletions.
64 changes: 64 additions & 0 deletions include/net/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ static_assert((1 << ATO_BITS) > TCP_DELACK_MAX);
#define TCPOPT_SACK 5 /* SACK Block */
#define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */
#define TCPOPT_MD5SIG 19 /* MD5 Signature (RFC2385) */
#define TCPOPT_AO 29 /* Authentication Option (RFC5925) */
#define TCPOPT_MPTCP 30 /* Multipath TCP (RFC6824) */
#define TCPOPT_FASTOPEN 34 /* Fast open (RFC7413) */
#define TCPOPT_EXP 254 /* Experimental */
Expand Down Expand Up @@ -2200,6 +2201,9 @@ struct tcp_sock_af_ops {
int (*ao_calc_key_sk)(struct tcp_ao_key *mkt, u8 *key,
const struct sock *sk,
__be32 sisn, __be32 disn, bool send);
int (*calc_ao_hash)(char *location, struct tcp_ao_key *ao,
const struct sock *sk, const struct sk_buff *skb,
const u8 *tkey, int hash_offset, u32 sne);
#endif
};

Expand Down Expand Up @@ -2253,6 +2257,66 @@ static inline __u32 cookie_init_sequence(const struct tcp_request_sock_ops *ops,
}
#endif

struct tcp_key {
union {
struct tcp_ao_key *ao_key;
struct tcp_md5sig_key *md5_key;
};
enum {
TCP_KEY_NONE = 0,
TCP_KEY_MD5,
TCP_KEY_AO,
} type;
};

static inline void tcp_get_current_key(const struct sock *sk,
struct tcp_key *out)
{
#if defined(CONFIG_TCP_AO) || defined(CONFIG_TCP_MD5SIG)
const struct tcp_sock *tp = tcp_sk(sk);
#endif
#ifdef CONFIG_TCP_AO
struct tcp_ao_info *ao;

ao = rcu_dereference_protected(tp->ao_info, lockdep_sock_is_held(sk));
if (ao) {
out->ao_key = READ_ONCE(ao->current_key);
out->type = TCP_KEY_AO;
return;
}
#endif
#ifdef CONFIG_TCP_MD5SIG
if (static_branch_unlikely(&tcp_md5_needed.key) &&
rcu_access_pointer(tp->md5sig_info)) {
out->md5_key = tp->af_specific->md5_lookup(sk, sk);
if (out->md5_key) {
out->type = TCP_KEY_MD5;
return;
}
}
#endif
out->type = TCP_KEY_NONE;
}

static inline bool tcp_key_is_md5(const struct tcp_key *key)
{
#ifdef CONFIG_TCP_MD5SIG
if (static_branch_unlikely(&tcp_md5_needed.key) &&
key->type == TCP_KEY_MD5)
return true;
#endif
return false;
}

static inline bool tcp_key_is_ao(const struct tcp_key *key)
{
#ifdef CONFIG_TCP_AO
if (key->type == TCP_KEY_AO)
return true;
#endif
return false;
}

int tcpv4_offload_init(void);

void tcp_v4_init(void);
Expand Down
23 changes: 23 additions & 0 deletions include/net/tcp_ao.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ struct tcp6_ao_context {

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);
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);
int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family,
sockptr_t optval, int optlen);
int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
Expand All @@ -126,19 +133,35 @@ struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
const struct sock *sk,
__be32 sisn, __be32 disn, bool send);
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,
const struct in6_addr *daddr,
const struct in6_addr *saddr, int nbytes);
int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
const struct sock *sk, __be32 sisn,
__be32 disn, bool send);
struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk,
struct sock *addr_sk, int sndid, int rcvid);
int tcp_v6_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);
int tcp_v6_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen);
void tcp_ao_established(struct sock *sk);
void tcp_ao_finish_connect(struct sock *sk, struct sk_buff *skb);
void tcp_ao_connect_init(struct sock *sk);

#else /* CONFIG_TCP_AO */

static inline int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
struct tcp_ao_key *key, struct tcphdr *th,
__u8 *hash_location)
{
return 0;
}

static inline struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
const union tcp_ao_addr *addr, int family, int sndid, int rcvid)
{
Expand Down
199 changes: 199 additions & 0 deletions net/ipv4/tcp_ao.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,171 @@ static int tcp_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,
return -EOPNOTSUPP;
}

static int tcp_v4_ao_hash_pseudoheader(struct tcp_sigpool *hp,
__be32 daddr, __be32 saddr,
int nbytes)
{
struct tcp4_pseudohdr *bp;
struct scatterlist sg;

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);
}

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)
{
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,
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,
&sk->sk_v6_rcv_saddr, skb->len);
#endif
else
return -EAFNOSUPPORT;
}

if (family == AF_INET) {
const struct iphdr *iph = ip_hdr(skb);

return tcp_v4_ao_hash_pseudoheader(hp, 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,
&iph->saddr, skb->len);
#endif
}
return -EAFNOSUPPORT;
}

/* 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)
{
struct scatterlist sg;
__be32 *bp;

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);
}

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)
{
int err, len = th->doff << 2;
struct scatterlist sg;
u8 *hdr = hp->scratch;

/* We are not allowed to change tcphdr, make a local copy */
if (exclude_options) {
len = sizeof(*th) + sizeof(struct tcp_ao_hdr) + hash_len;
memcpy(hdr, th, sizeof(*th));
memcpy(hdr + sizeof(*th),
(u8 *)th + hash_offset - sizeof(struct tcp_ao_hdr),
sizeof(struct tcp_ao_hdr));
memset(hdr + sizeof(*th) + sizeof(struct tcp_ao_hdr),
0, hash_len);
((struct tcphdr *)hdr)->check = 0;
} else {
len = th->doff << 2;
memcpy(hdr, th, len);
/* zero out tcp-ao hash */
((struct tcphdr *)hdr)->check = 0;
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;
}

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;

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, false,
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))
goto clear_hash;

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;
}

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)
{
return tcp_ao_hash_skb(AF_INET, ao_hash, key, sk, skb,
tkey, hash_offset, sne);
}

struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
int sndid, int rcvid)
{
Expand All @@ -270,6 +435,40 @@ struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
return tcp_ao_do_lookup(sk, addr, AF_INET, sndid, rcvid);
}

int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
struct tcp_ao_key *key, struct tcphdr *th,
__u8 *hash_location)
{
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_ao_info *ao;
void *tkey_buf = NULL;
u8 *traffic_key;

ao = rcu_dereference_protected(tcp_sk(sk)->ao_info,
lockdep_sock_is_held(sk));
traffic_key = snd_other_key(key);
if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
__be32 disn;

if (!(tcb->tcp_flags & TCPHDR_ACK)) {
disn = 0;
tkey_buf = kmalloc(tcp_ao_digest_size(key), GFP_ATOMIC);
if (!tkey_buf)
return -ENOMEM;
traffic_key = tkey_buf;
} else {
disn = ao->risn;
}
tp->af_specific->ao_calc_key_sk(key, traffic_key,
sk, ao->lisn, disn, true);
}
tp->af_specific->calc_ao_hash(hash_location, key, sk, skb, traffic_key,
hash_location - (u8 *)th, 0);
kfree(tkey_buf);
return 0;
}

static int tcp_ao_cache_traffic_keys(const struct sock *sk,
struct tcp_ao_info *ao,
struct tcp_ao_key *ao_key)
Expand Down
1 change: 1 addition & 0 deletions net/ipv4/tcp_ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -2287,6 +2287,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv4_specific = {
#endif
#ifdef CONFIG_TCP_AO
.ao_lookup = tcp_v4_ao_lookup,
.calc_ao_hash = tcp_v4_ao_hash_skb,
.ao_parse = tcp_v4_parse_ao,
.ao_calc_key_sk = tcp_v4_ao_calc_key_sk,
#endif
Expand Down
Loading

0 comments on commit 1e03d32

Please sign in to comment.