root/net/ipv6/tcp_ao.c
// 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;
}