| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * INET An implementation of the TCP Authentication Option (TCP-AO). |
| * See RFC5925. |
| * |
| * Authors: Dmitry Safonov <dima@arista.com> |
| * Francesco Ruggeri <fruggeri@arista.com> |
| * Salam Noureddine <noureddine@arista.com> |
| */ |
| #include <crypto/hash.h> |
| #include <linux/tcp.h> |
| |
| #include <net/tcp.h> |
| #include <net/ipv6.h> |
| |
| static int tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, |
| const struct in6_addr *saddr, |
| const struct in6_addr *daddr, |
| __be16 sport, __be16 dport, |
| __be32 sisn, __be32 disn) |
| { |
| struct kdf_input_block { |
| u8 counter; |
| u8 label[6]; |
| struct tcp6_ao_context ctx; |
| __be16 outlen; |
| } __packed * tmp; |
| struct tcp_sigpool hp; |
| int err; |
| |
| 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; |
| } |
| |
| int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, |
| const struct sk_buff *skb, |
| __be32 sisn, __be32 disn) |
| { |
| const struct ipv6hdr *iph = ipv6_hdr(skb); |
| const struct tcphdr *th = tcp_hdr(skb); |
| |
| return tcp_v6_ao_calc_key(mkt, key, &iph->saddr, |
| &iph->daddr, th->source, |
| th->dest, sisn, disn); |
| } |
| |
| int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, |
| const struct sock *sk, __be32 sisn, |
| __be32 disn, bool send) |
| { |
| if (send) |
| return tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_rcv_saddr, |
| &sk->sk_v6_daddr, htons(sk->sk_num), |
| sk->sk_dport, sisn, disn); |
| else |
| return tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_daddr, |
| &sk->sk_v6_rcv_saddr, sk->sk_dport, |
| htons(sk->sk_num), disn, sisn); |
| } |
| |
| int tcp_v6_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, |
| struct request_sock *req) |
| { |
| struct inet_request_sock *ireq = inet_rsk(req); |
| |
| return tcp_v6_ao_calc_key(mkt, key, |
| &ireq->ir_v6_loc_addr, &ireq->ir_v6_rmt_addr, |
| htons(ireq->ir_num), ireq->ir_rmt_port, |
| htonl(tcp_rsk(req)->snt_isn), |
| htonl(tcp_rsk(req)->rcv_isn)); |
| } |
| |
| struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk, |
| struct sock *addr_sk, |
| int sndid, int rcvid) |
| { |
| int l3index = l3mdev_master_ifindex_by_index(sock_net(sk), |
| addr_sk->sk_bound_dev_if); |
| struct in6_addr *addr = &addr_sk->sk_v6_daddr; |
| |
| return tcp_ao_do_lookup(sk, l3index, (union tcp_ao_addr *)addr, |
| AF_INET6, sndid, rcvid); |
| } |
| |
| struct tcp_ao_key *tcp_v6_ao_lookup_rsk(const struct sock *sk, |
| struct request_sock *req, |
| int sndid, int rcvid) |
| { |
| struct inet_request_sock *ireq = inet_rsk(req); |
| struct in6_addr *addr = &ireq->ir_v6_rmt_addr; |
| int l3index; |
| |
| l3index = l3mdev_master_ifindex_by_index(sock_net(sk), ireq->ir_iif); |
| return tcp_ao_do_lookup(sk, l3index, (union tcp_ao_addr *)addr, |
| AF_INET6, sndid, rcvid); |
| } |
| |
| int tcp_v6_ao_hash_pseudoheader(struct tcp_sigpool *hp, |
| 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); |
| |
| sg_init_one(&sg, bp, sizeof(*bp)); |
| ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp)); |
| return crypto_ahash_update(hp->req); |
| } |
| |
| 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) |
| { |
| return tcp_ao_hash_skb(AF_INET6, ao_hash, key, sk, skb, tkey, |
| hash_offset, sne); |
| } |
| |
| int tcp_v6_parse_ao(struct sock *sk, int cmd, |
| sockptr_t optval, int optlen) |
| { |
| return tcp_parse_ao(sk, cmd, AF_INET6, optval, optlen); |
| } |
| |
| int tcp_v6_ao_synack_hash(char *ao_hash, struct tcp_ao_key *ao_key, |
| struct request_sock *req, const struct sk_buff *skb, |
| int hash_offset, u32 sne) |
| { |
| void *hash_buf = NULL; |
| int err; |
| |
| hash_buf = kmalloc(tcp_ao_digest_size(ao_key), GFP_ATOMIC); |
| if (!hash_buf) |
| return -ENOMEM; |
| |
| err = tcp_v6_ao_calc_key_rsk(ao_key, hash_buf, req); |
| if (err) |
| goto out; |
| |
| err = tcp_ao_hash_skb(AF_INET6, ao_hash, ao_key, req_to_sk(req), skb, |
| hash_buf, hash_offset, sne); |
| out: |
| kfree(hash_buf); |
| return err; |
| } |