diff options
author | Angelos D. Keromytis <angelos@cvs.openbsd.org> | 2000-01-27 08:09:13 +0000 |
---|---|---|
committer | Angelos D. Keromytis <angelos@cvs.openbsd.org> | 2000-01-27 08:09:13 +0000 |
commit | 98f40bbc84e70cdf4b25f5ee817c6eebe3d46a3d (patch) | |
tree | d43e5507a833c1b2d60fea7c9e4363d15715be06 /sys/netinet/ip_ah.c | |
parent | 95d6b976dfe454179868d92f7eab5c4bcdd4ab5a (diff) |
Merge "old" and "new" ESP and AH in two files (one for each).
Fix a couple of buglets with ingress flow deletion.
tcpdump on enc0 should now show all outgoing packets *before* being
processed, and all incoming packets *after* being processed.
Good to be in Canada (land of the free commits).
Diffstat (limited to 'sys/netinet/ip_ah.c')
-rw-r--r-- | sys/netinet/ip_ah.c | 1241 |
1 files changed, 1241 insertions, 0 deletions
diff --git a/sys/netinet/ip_ah.c b/sys/netinet/ip_ah.c new file mode 100644 index 00000000000..39e54483cb9 --- /dev/null +++ b/sys/netinet/ip_ah.c @@ -0,0 +1,1241 @@ +/* $OpenBSD: ip_ah.c,v 1.33 2000/01/27 08:09:08 angelos Exp $ */ + +/* + * The authors of this code are John Ioannidis (ji@tla.org), + * Angelos D. Keromytis (kermit@csd.uch.gr) and + * Niels Provos (provos@physnet.uni-hamburg.de). + * + * This code was written by John Ioannidis for BSD/OS in Athens, Greece, + * in November 1995. + * + * Ported to OpenBSD and NetBSD, with additional transforms, in December 1996, + * by Angelos D. Keromytis. + * + * Additional transforms and features in 1997 and 1998 by Angelos D. Keromytis + * and Niels Provos. + * + * Additional features in 1999 by Angelos D. Keromytis. + * + * Copyright (C) 1995, 1996, 1997, 1998, 1999 by John Ioannidis, + * Angelos D. Keromytis and Niels Provos. + * + * Permission to use, copy, and modify this software without fee + * is hereby granted, provided that this entire notice is included in + * all copies of any software which is or includes a copy or + * modification of this software. + * You may use this code under the GNU public license if you so wish. Please + * contribute changes back to the authors under this freer than GPL license + * so that we may further the use of strong encryption without limitations to + * all. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE + * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR + * PURPOSE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/socketvar.h> +#include <machine/cpu.h> +#include <machine/endian.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <net/raw_cb.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#ifdef INET6 +#include <netinet6/in6.h> +#include <netinet6/ip6.h> +#endif /* INET6 */ + +#include <netinet/ip_ipsp.h> +#include <netinet/ip_ah.h> +#include <net/pfkeyv2.h> +#include <net/if_enc.h> + +#include "bpfilter.h" + +#ifdef ENCDEBUG +#define DPRINTF(x) if (encdebug) printf x +#else +#define DPRINTF(x) +#endif + +#ifndef offsetof +#define offsetof(s, e) ((int)&((s *)0)->e) +#endif + +extern struct auth_hash auth_hash_hmac_md5_96; +extern struct auth_hash auth_hash_hmac_sha1_96; +extern struct auth_hash auth_hash_hmac_ripemd_160_96; +extern struct auth_hash auth_hash_key_md5; +extern struct auth_hash auth_hash_key_sha1; + +struct auth_hash *ah_hash[] = { + &auth_hash_hmac_md5_96, + &auth_hash_hmac_sha1_96, + &auth_hash_hmac_ripemd_160_96, + &auth_hash_key_md5, + &auth_hash_key_sha1, +}; + +/* + * ah_attach() is called from the transformation initialization code + */ +int +ah_attach() +{ + return 0; +} + +/* + * ah_init() is called when an SPI is being set up. + */ +int +ah_init(struct tdb *tdbp, struct xformsw *xsp, struct ipsecinit *ii) +{ + struct auth_hash *thash = NULL; + int i; + + for (i = sizeof(ah_hash) / sizeof(ah_hash[0]) - 1; i >= 0; i--) + if (ii->ii_authalg == ah_hash[i]->type) + break; + + if (i < 0) + { + DPRINTF(("ah_init(): unsupported authentication algorithm %d specified\n", ii->ii_authalg)); + return EINVAL; + } + + thash = ah_hash[i]; + + if ((ii->ii_authkeylen != thash->keysize) && (thash->keysize != 0)) + { + DPRINTF(("ah_init(): keylength %d doesn't match algorithm %s keysize (%d)\n", ii->ii_authkeylen, thash->name, thash->keysize)); + return EINVAL; + } + + tdbp->tdb_xform = xsp; + tdbp->tdb_authalgxform = thash; + tdbp->tdb_bitmap = 0; + tdbp->tdb_rpl = AH_HMAC_INITIAL_RPL; + + DPRINTF(("ah_init(): initialized TDB with hash algorithm %s\n", + thash->name)); + + tdbp->tdb_amxkeylen = ii->ii_authkeylen; + MALLOC(tdbp->tdb_amxkey, u_int8_t *, tdbp->tdb_amxkeylen, M_XDATA, + M_WAITOK); + + bcopy(ii->ii_authkey, tdbp->tdb_amxkey, tdbp->tdb_amxkeylen); + + /* "Old" AH */ + if ((thash->type == SADB_X_AALG_MD5) || (thash->type == SADB_X_AALG_SHA1)) + { + MALLOC(tdbp->tdb_ictx, u_int8_t *, thash->ctxsize, M_XDATA, M_WAITOK); + bzero(tdbp->tdb_ictx, thash->ctxsize); + thash->Init(tdbp->tdb_ictx); + thash->Update(tdbp->tdb_ictx, tdbp->tdb_amxkey, tdbp->tdb_amxkeylen); + thash->Final(NULL, tdbp->tdb_ictx); + } + else /* HMAC */ + { + /* Precompute the I and O pads of the HMAC */ + for (i = 0; i < ii->ii_authkeylen; i++) + ii->ii_authkey[i] ^= HMAC_IPAD_VAL; + + MALLOC(tdbp->tdb_ictx, u_int8_t *, thash->ctxsize, M_XDATA, M_WAITOK); + bzero(tdbp->tdb_ictx, thash->ctxsize); + thash->Init(tdbp->tdb_ictx); + thash->Update(tdbp->tdb_ictx, ii->ii_authkey, ii->ii_authkeylen); + thash->Update(tdbp->tdb_ictx, hmac_ipad_buffer, + HMAC_BLOCK_LEN - ii->ii_authkeylen); + + for (i = 0; i < ii->ii_authkeylen; i++) + ii->ii_authkey[i] ^= (HMAC_IPAD_VAL ^ HMAC_OPAD_VAL); + + MALLOC(tdbp->tdb_octx, u_int8_t *, thash->ctxsize, M_XDATA, M_WAITOK); + bzero(tdbp->tdb_octx, thash->ctxsize); + thash->Init(tdbp->tdb_octx); + thash->Update(tdbp->tdb_octx, ii->ii_authkey, ii->ii_authkeylen); + thash->Update(tdbp->tdb_octx, hmac_opad_buffer, + HMAC_BLOCK_LEN - ii->ii_authkeylen); + } + + bzero(ipseczeroes, IPSEC_ZEROES_SIZE); /* paranoid */ + + return 0; +} + +/* Free memory */ +int +ah_zeroize(struct tdb *tdbp) +{ + if (tdbp->tdb_amxkey) + { + bzero(tdbp->tdb_amxkey, tdbp->tdb_amxkeylen); + FREE(tdbp->tdb_amxkey, M_XDATA); + tdbp->tdb_amxkey = NULL; + } + + if (tdbp->tdb_ictx) + { + if (tdbp->tdb_authalgxform) + bzero(tdbp->tdb_ictx, tdbp->tdb_authalgxform->ctxsize); + + FREE(tdbp->tdb_ictx, M_XDATA); + tdbp->tdb_ictx = NULL; + } + + if (tdbp->tdb_octx) + { + if (tdbp->tdb_authalgxform) + bzero(tdbp->tdb_octx, tdbp->tdb_authalgxform->ctxsize); + + FREE(tdbp->tdb_octx, M_XDATA); + tdbp->tdb_octx = NULL; + } + + return 0; +} + +/* + * ah_input() gets called to verify that an input packet + * passes authentication + */ + +struct mbuf * +ah_input(struct mbuf *m, struct tdb *tdb, int skip, int protoff) +{ + struct auth_hash *ahx = (struct auth_hash *) tdb->tdb_authalgxform; + unsigned char calcauth[AH_MAX_HASHLEN], savauth[AH_MAX_HASHLEN]; + int len, count, off, roff, rplen; + struct mbuf *m0, *m1; + unsigned char *ptr; + union authctx ctx; + struct ah ah; + +#ifdef INET + struct ip ipo; +#endif /* INET */ + +#ifdef INET6 + struct ip6_ext *ip6e; + struct ip6_hdr ip6; + int last; +#endif /* INET6 */ + + if (!(tdb->tdb_flags & TDBF_NOREPLAY)) + rplen = sizeof(u_int32_t); + else + rplen = 0; + + /* Save the AH header, we use it throughout */ + m_copydata(m, skip, AH_FLENGTH + rplen, (unsigned char *) &ah); + + /* Save the Authenticator too */ + m_copydata(m, skip + AH_FLENGTH + rplen, ahx->authsize, savauth); + + /* Replay window checking, if applicable */ + if ((tdb->tdb_wnd > 0) && (!(tdb->tdb_flags & TDBF_NOREPLAY))) + { + switch (checkreplaywindow32(ntohl(ah.ah_rpl), 0, &(tdb->tdb_rpl), + tdb->tdb_wnd, &(tdb->tdb_bitmap))) + { + case 0: /* All's well */ + break; + + case 1: + DPRINTF(("ah_input(): replay counter wrapped for SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_wrap++; + m_freem(m); + return NULL; + + case 2: + case 3: + DPRINTF(("ah_input(): duplicate packet received in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_replay++; + m_freem(m); + return NULL; + + default: + DPRINTF(("ah_input(): bogus value from checkreplaywindow32() in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_replay++; + m_freem(m); + return NULL; + } + } + + /* Verify AH header length */ + if (ah.ah_hl * sizeof(u_int32_t) != ahx->authsize + rplen) + { + DPRINTF(("ah_input(): bad authenticator length %d for packet in SA %s/%08x\n", ah.ah_hl * sizeof(u_int32_t), ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_badauthl++; + m_freem(m); + return NULL; + } + + /* Update the counters */ + tdb->tdb_cur_bytes += (m->m_pkthdr.len - skip - + ah.ah_hl * sizeof(u_int32_t)); + ahstat.ahs_ibytes += (m->m_pkthdr.len - skip - + ah.ah_hl * sizeof(u_int32_t)); + + /* Hard expiration */ + if ((tdb->tdb_flags & TDBF_BYTES) && + (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) + { + pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); + tdb_delete(tdb, 0, TDBEXP_TIMEOUT); + m_freem(m); + return NULL; + } + + /* Notify on expiration */ + if ((tdb->tdb_flags & TDBF_SOFT_BYTES) && + (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) + { + pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); + tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */ + } + + bcopy(tdb->tdb_ictx, &ctx, ahx->ctxsize); + + switch (tdb->tdb_dst.sa.sa_family) + { +#ifdef INET + case AF_INET: + /* + * This is the least painful way of dealing with IPv4 header + * and option processing -- just make sure they're in + * contiguous memory. + */ + m = m_pullup(m, skip); + if (m == NULL) + { + DPRINTF(("ah_input(): m_pullup() failed, SA %s/%08x\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + return NULL; + } + + ptr = mtod(m, unsigned char *) + sizeof(struct ip); + + bcopy(mtod(m, unsigned char *), (unsigned char *) &ipo, + sizeof(struct ip)); + + ipo.ip_tos = 0; + ipo.ip_len += skip; /* adjusted in ip_intr() */ + HTONS(ipo.ip_len); + HTONS(ipo.ip_id); + + if ((ahx->type == SADB_X_AALG_MD5) || + (ahx->type == SADB_X_AALG_SHA1)) + ipo.ip_off = htons(ipo.ip_off & IP_DF); + else + ipo.ip_off = 0; + ipo.ip_ttl = 0; + ipo.ip_sum = 0; + + /* Include IP header in authenticator computation */ + ahx->Update(&ctx, (unsigned char *) &ipo, sizeof(struct ip)); + + /* IPv4 option processing */ + for (off = sizeof(struct ip); off < skip;) + { + switch (ptr[off]) + { + case IPOPT_EOL: + ahx->Update(&ctx, ptr + off, 1); + off = skip; /* End the loop */ + break; + + case IPOPT_NOP: + ahx->Update(&ctx, ptr + off, 1); + off++; + break; + + case IPOPT_SECURITY: /* 0x82 */ + case 0x85: /* Extended security */ + case 0x86: /* Commercial security */ + case 0x94: /* Router alert */ + case 0x95: /* RFC1770 */ + ahx->Update(&ctx, ptr + off, ptr[off + 1]); + /* Sanity check for zero-length options */ + if (ptr[off + 1] == 0) + { + DPRINTF(("ah_input(): illegal zero-length IPv4 option %d in SA %s/%08x\n", ptr[off], ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + + off += ptr[off + 1]; + break; + + default: + ahx->Update(&ctx, ipseczeroes, ptr[off + 1]); + off += ptr[off + 1]; + break; + } + + /* Sanity check */ + if (off > skip) + { + DPRINTF(("ah_input(): malformed IPv4 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + } + + break; +#endif /* INET */ + +#ifdef INET6 + case AF_INET6: /* Ugly... */ + /* Copy and "cook" (later on) the IPv6 header */ + m_copydata(m, 0, sizeof(ip6), (unsigned char *) &ip6); + + /* We don't do IPv6 Jumbograms */ + if (ip6.ip6_plen == 0) + { + DPRINTF(("ah_input(): unsupported IPv6 jumbogram in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + + ip6.ip6_flow = 0; + ip6.ip6_hlim = 0; + ip6.ip6_vfc &= ~IPV6_VERSION_MASK; + ip6.ip6_vfc |= IPV6_VERSION; + + /* Include IPv6 header in authenticator computation */ + ahx->Update(&ctx, (unsigned char *) &ip6, sizeof(ip6)); + + /* Let's deal with the remaining headers (if any) */ + if (skip - sizeof(struct ip6_hdr) > 0) + { + if (m->m_len <= skip) + { + MALLOC(ptr, unsigned char *, skip - sizeof(struct ip6_hdr), + M_XDATA, M_WAITOK); + + /* Copy all the protocol headers after the IPv6 header */ + m_copydata(m, sizeof(struct ip6_hdr), + skip - sizeof(struct ip6_hdr), ptr); + } + else + ptr = mtod(m, unsigned char *) + sizeof(struct ip6_hdr); + } + else + break; + + off = ip6.ip6_nxt & 0xff; /* Next header type */ + + for (len = 0; len < skip - sizeof(struct ip6_hdr);) + switch (off) + { + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + ip6e = (struct ip6_ext *) (ptr + len); + + /* + * Process the mutable/immutable options -- borrows + * heavily from the KAME code. + */ + for (last = len, count = len + sizeof(struct ip6_ext); + count < len + ((ip6e->ip6e_len + 1) << 3);) + { + if (ptr[count] == IP6OPT_PAD1) + { + count++; + continue; + } + + /* Sanity check */ + if (count + sizeof(struct ip6_ext) > len + + ((ip6e->ip6e_len + 1) << 3)) + { + DPRINTF(("ah_input(): malformed IPv6 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + + /* Free, if we allocated */ + if (m->m_len < skip) + { + FREE(ptr, M_XDATA); + ptr = NULL; + } + return NULL; + } + + /* + * If mutable option, calculate authenticator + * for all immutable fields so far, then include + * a zeroed-out version of this option. + */ + if (ptr[count] & IP6OPT_MUTABLE) + { + /* Calculate immutables */ + ahx->Update(&ctx, ptr + last, + count + sizeof(struct ip6_ext) - + last); + last = count + ptr[count + 1] + + sizeof(struct ip6_ext); + + /* Calculate "zeroed-out" immutables */ + ahx->Update(&ctx, ipseczeroes, ptr[count + 1] - + sizeof(struct ip6_ext)); + } + + count += ptr[count + 1] + sizeof(struct ip6_ext); + + /* Sanity check */ + if (count > skip - sizeof(struct ip6_hdr)) + { + DPRINTF(("ah_input(): malformed IPv6 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + + /* Free, if we allocated */ + if (m->m_len < skip) + { + FREE(ptr, M_XDATA); + ptr = NULL; + } + return NULL; + } + } + + /* Include any trailing immutable options */ + ahx->Update(&ctx, ptr + last, + len + ((ip6e->ip6e_len + 1) << 3) - last); + + len += ((ip6e->ip6e_len + 1) << 3); /* Advance */ + off = ip6e->ip6e_nxt; + break; + + case IPPROTO_ROUTING: + ip6e = (struct ip6_ext *) (ptr + len); + ahx->Update(&ctx, ptr + len, (ip6e->ip6e_len + 1) << 3); + len += ((ip6e->ip6e_len + 1) << 3); /* Advance */ + off = ip6e->ip6e_nxt; + break; + + default: + DPRINTF(("ah_input(): unexpected IPv6 header type %d in SA %s/%08x\n", off, ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + len = skip - sizeof(struct ip6_hdr); + break; + } + + /* Free, if we allocated */ + if (m->m_len < skip) + { + FREE(ptr, M_XDATA); + ptr = NULL; + } + + break; +#endif /* INET6 */ + + default: + DPRINTF(("ah_input(): unsupported protocol family %d in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + + /* Record the beginning of the AH header */ + for (len = 0, m1 = m; m1 && (len + m1->m_len <= skip); m1 = m1->m_next) + len += m1->m_len; + + if (m1 == NULL) + { + DPRINTF(("ah_input(): bad mbuf chain for packet in SA %s/%08x\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + else + roff = skip - len; + + /* Skip the AH header */ + for (len = 0, m0 = m1; + m0 && (len + m0->m_len <= AH_FLENGTH + rplen + ahx->authsize + roff); + m0 = m0->m_next) + len += m0->m_len; + + if (m0 == NULL) + { + DPRINTF(("ah_input(): bad mbuf chain for packet in SA %s/%08x\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + else + off = (AH_FLENGTH + rplen + ahx->authsize + roff) - len; + + /* Include the AH header (minus the authenticator) in the computation */ + ahx->Update(&ctx, (unsigned char *) &ah, AH_FLENGTH + rplen); + + /* All-zeroes for the authenticator */ + ahx->Update(&ctx, ipseczeroes, ahx->authsize); + + /* Amount of data to be verified */ + len = m->m_pkthdr.len - skip - AH_FLENGTH - rplen - ahx->authsize; + + /* Loop through the mbuf chain computing the HMAC */ + while (len > 0) + { + if (m0 == NULL) + { + DPRINTF(("ah_input(): bad mbuf chain for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return NULL; + } + + count = min(m0->m_len - off, len); + + ahx->Update(&ctx, mtod(m0, unsigned char *) + off, count); + + len -= count; + off = 0; + m0 = m0->m_next; + } + + /* Finish computation */ + if ((ahx->type == SADB_X_AALG_MD5) || (ahx->type == SADB_X_AALG_SHA1)) + { + ahx->Update(&ctx, (unsigned char *) tdb->tdb_amxkey, + tdb->tdb_amxkeylen); + ahx->Final(calcauth, &ctx); + } + else + { + /* Finish HMAC computation */ + ahx->Final(calcauth, &ctx); + bcopy(tdb->tdb_octx, &ctx, ahx->ctxsize); + ahx->Update(&ctx, calcauth, ahx->hashsize); + ahx->Final(calcauth, &ctx); + } + + /* Verify */ + if (bcmp(savauth, calcauth, ahx->authsize)) + { + DPRINTF(("ah_input(): authentication failed for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_badauth++; + m_freem(m); + return NULL; + } + + /* Fix the Next Protocol field */ + m_copyback(m, protoff, sizeof(u_int8_t), (u_char *) &(ah.ah_nh)); + + /* + * Remove the AH header from the mbuf. + */ + if (roff == 0) + { + /* The AH header was conveniently at the beginning of the mbuf */ + m_adj(m1, AH_FLENGTH + rplen + ahx->authsize); + if (!(m1->m_flags & M_PKTHDR)) + m->m_pkthdr.len -= AH_FLENGTH + rplen + ahx->authsize; + } + else + if (roff + AH_FLENGTH + rplen + ahx->authsize >= m1->m_len) + { + /* + * Part or all of the AH header is at the end of this mbuf, so first + * let's remove the remainder of the AH header from the + * beginning of the remainder of the mbuf chain, if any. + */ + if (roff + AH_FLENGTH + rplen + ahx->authsize > m1->m_len) + { + /* Adjust the next mbuf by the remainder */ + m_adj(m1->m_next, roff + AH_FLENGTH + rplen + + ahx->authsize - m1->m_len); + + /* The second mbuf is guaranteed not to have a pkthdr... */ + m->m_pkthdr.len -= (roff + AH_FLENGTH + rplen + + ahx->authsize - m1->m_len); + } + + /* Now, let's unlink the mbuf chain for a second...*/ + m0 = m1->m_next; + m1->m_next = NULL; + + /* ...and trim the end of the first part of the chain...sick */ + m_adj(m1, -(m1->m_len - roff)); + if (!(m1->m_flags & M_PKTHDR)) + m->m_pkthdr.len -= (m1->m_len - roff); + + /* Finally, let's relink */ + m1->m_next = m0; + } + else + { + /* + * The AH header lies in the "middle" of the mbuf...do an + * overlapping copy of the remainder of the mbuf over the ESP + * header. + */ + bcopy(mtod(m1, u_char *) + roff + AH_FLENGTH + rplen + ahx->authsize, + mtod(m1, u_char *) + roff, + m1->m_len - (roff + AH_FLENGTH + rplen + ahx->authsize)); + m1->m_len -= AH_FLENGTH + rplen + ahx->authsize; + m->m_pkthdr.len -= AH_FLENGTH + rplen + ahx->authsize; + } + + return m; +} + +int +ah_output(struct mbuf *m, struct tdb *tdb, struct mbuf **mp, int skip, + int protoff) +{ + struct auth_hash *ahx = (struct auth_hash *) tdb->tdb_authalgxform; + unsigned char calcauth[AH_MAX_HASHLEN]; + int len, off, count, rplen; + unsigned char *ptr; + union authctx ctx; + struct mbuf *mo; + struct ah *ah; + +#ifdef INET + struct ip ipo; +#endif /* INET */ + +#ifdef INET6 + struct ip6_ext *ip6e; + struct ip6_hdr ip6; + int last; +#endif /* INET6 */ + +#if NBPFILTER > 0 + { + struct ifnet *ifn; + struct enchdr hdr; + struct mbuf m1; + + bzero (&hdr, sizeof(hdr)); + + hdr.af = tdb->tdb_dst.sa.sa_family; + hdr.spi = tdb->tdb_spi; + hdr.flags |= M_AUTH; + + m1.m_next = m; + m1.m_len = ENC_HDRLEN; + m1.m_data = (char *) &hdr; + + if (tdb->tdb_interface) + ifn = (struct ifnet *) tdb->tdb_interface; + else + ifn = &(encif[0].sc_if); + + if (ifn->if_bpf) + bpf_mtap(ifn->if_bpf, &m1); + } +#endif + + ahstat.ahs_output++; + + /* Check for replay counter wrap-around in automatic (not manual) keying */ + if ((tdb->tdb_rpl == 0) && (tdb->tdb_wnd > 0) && + (!(tdb->tdb_flags & TDBF_NOREPLAY))) + { + DPRINTF(("ah_output(): SA %s/%08x should have expired\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + m_freem(m); + ahstat.ahs_wrap++; + return NULL; + } + + if (!(tdb->tdb_flags & TDBF_NOREPLAY)) + rplen = sizeof(u_int32_t); + else + rplen = 0; + +#ifdef INET + if ((tdb->tdb_dst.sa.sa_family == AF_INET) && + (AH_FLENGTH + rplen + ahx->authsize + m->m_pkthdr.len > IP_MAXPACKET)) + { + DPRINTF(("ah_output(): packet in SA %s/%08x got too big\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + m_freem(m); + ahstat.ahs_toobig++; + return EMSGSIZE; + } +#endif /* INET */ + +#ifdef INET6 + if ((tdb->tdb_dst.sa.sa_family == AF_INET6) && + (AH_FLENGTH + rplen + ahx->authsize + m->m_pkthdr.len > + IPV6_MAXPACKET)) + { + DPRINTF(("ah_output(): packet in SA %s/%08x got too big\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + m_freem(m); + ahstat.ahs_toobig++; + return EMSGSIZE; + } +#endif /* INET6 */ + + /* Update the counters */ + tdb->tdb_cur_bytes += m->m_pkthdr.len - skip; + ahstat.ahs_obytes += m->m_pkthdr.len - skip; + + /* Hard expiration */ + if ((tdb->tdb_flags & TDBF_BYTES) && + (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes)) + { + pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_HARD); + tdb_delete(tdb, 0, TDBEXP_TIMEOUT); + m_freem(m); + return EINVAL; + } + + /* Notify on expiration */ + if ((tdb->tdb_flags & TDBF_SOFT_BYTES) && + (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes)) + { + pfkeyv2_expire(tdb, SADB_EXT_LIFETIME_SOFT); + tdb->tdb_flags &= ~TDBF_SOFT_BYTES; /* Turn off checking */ + } + + /* + * Loop through mbuf chain; if we find an M_EXT mbuf with + * more than one reference, replace the rest of the chain. + * This may not be strictly necessary for AH packets, if we were + * careful with the rest of our processing (and made a lot of + * assumptions about the layout of the packets/mbufs). + */ + (*mp) = m; + while ((*mp) != NULL && + (!((*mp)->m_flags & M_EXT) || + ((*mp)->m_ext.ext_ref == NULL && + mclrefcnt[mtocl((*mp)->m_ext.ext_buf)] <= 1))) + { + mo = (*mp); + (*mp) = (*mp)->m_next; + } + + if ((*mp) != NULL) + { + /* Replace the rest of the mbuf chain. */ + struct mbuf *n = m_copym2((*mp), 0, M_COPYALL, M_DONTWAIT); + + if (n == NULL) + { + ahstat.ahs_hdrops++; + m_freem(m); + return ENOBUFS; + } + + if (mo != NULL) + mo->m_next = n; + else + m = n; + + m_freem((*mp)); + (*mp) = NULL; + } + + bcopy(tdb->tdb_ictx, (caddr_t) &ctx, ahx->ctxsize); + + switch (tdb->tdb_dst.sa.sa_family) + { +#ifdef INET + case AF_INET: + /* + * This is the most painless way of dealing with IPv4 header + * and option processing -- just make sure they're in + * contiguous memory. + */ + m = m_pullup(m, skip); + if (m == NULL) + { + DPRINTF(("ah_output(): m_pullup() failed, SA %s/%08x\n", + ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + return ENOBUFS; + } + + ptr = mtod(m, unsigned char *) + sizeof(struct ip); + + bcopy(mtod(m, unsigned char *), (unsigned char *) &ipo, + sizeof(struct ip)); + + ipo.ip_tos = 0; + if ((ahx->type == SADB_X_AALG_MD5) || + (ahx->type == SADB_X_AALG_SHA1)) + ipo.ip_off = htons(ntohs(ipo.ip_off) & IP_DF); + else + ipo.ip_off = 0; + ipo.ip_ttl = 0; + ipo.ip_sum = 0; + ipo.ip_p = IPPROTO_AH; + ipo.ip_len = htons(ntohs(ipo.ip_len) + AH_FLENGTH + rplen + + ahx->authsize); + + /* + * If we have a loose or strict routing option, we are + * supposed to use the last address in it as the + * destination address in the authenticated IPv4 header. + * + * Note that this is an issue only with the output routine; + * we will correctly process (in the AH input routine) incoming + * packets with these options without special consideration. + * + * We assume that the IP header contains the next hop's address, + * and that the last entry in the option is the final + * destination's address. + */ + if (skip > sizeof(struct ip)) + { + for (off = sizeof(struct ip); off < skip;) + { + /* First sanity check for zero-length options */ + if ((ptr[off] != IPOPT_EOL) && (ptr[off] != IPOPT_NOP) && + (ptr[off + 1] == 0)) + { + DPRINTF(("ah_output(): illegal zero-length IPv4 option %d in SA %s/%08x\n", ptr[off], ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return EMSGSIZE; + } + + switch (ptr[off]) + { + case IPOPT_LSRR: + case IPOPT_SSRR: + /* Sanity check for length */ + if (ptr[off + 1] < 2 + sizeof(struct in_addr)) + { + DPRINTF(("ah_output(): malformed LSRR or SSRR IPv4 option header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return EMSGSIZE; + } + + bcopy(ptr + off + ptr[off + 1] - + sizeof(struct in_addr), + &(ipo.ip_dst), sizeof(struct in_addr)); + off = skip; + break; + + case IPOPT_EOL: + off = skip; + break; + + case IPOPT_NOP: + off++; + break; + + default: /* Some other option, just skip it */ + off += ptr[off + 1]; + break; + } + + /* Sanity check */ + if (off > skip) + { + DPRINTF(("ah_output(): malformed IPv4 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return EMSGSIZE; + } + } + } + + /* Include IP header in authenticator computation */ + ahx->Update(&ctx, (unsigned char *) &ipo, sizeof(struct ip)); + + /* IPv4 option processing */ + for (off = sizeof(struct ip); off < skip;) + { + switch (ptr[off]) + { + case IPOPT_EOL: + ahx->Update(&ctx, ptr + off, 1); + off = skip; /* End the loop */ + break; + + case IPOPT_NOP: + ahx->Update(&ctx, ptr + off, 1); + off++; + break; + + case IPOPT_SECURITY: /* 0x82 */ + case 0x85: /* Extended security */ + case 0x86: /* Commercial security */ + case 0x94: /* Router alert */ + case 0x95: /* RFC1770 */ + ahx->Update(&ctx, ptr + off, ptr[off + 1]); + off += ptr[off + 1]; + break; + + default: + ahx->Update(&ctx, ipseczeroes, ptr[off + 1]); + off += ptr[off + 1]; + break; + } + + /* Sanity check */ + if (off > skip) + { + DPRINTF(("ah_output(): malformed IPv4 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return EMSGSIZE; + } + } + break; +#endif /* INET */ + +#ifdef INET6 + case AF_INET6: + /* Copy and "cook" the IPv6 header */ + m_copydata(m, 0, sizeof(ip6), (unsigned char *) &ip6); + + /* We don't do IPv6 Jumbograms */ + if (ip6.ip6_plen == 0) + { + DPRINTF(("ah_output(): unsupported IPv6 jumbogram in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + return EMSGSIZE; + } + + ip6.ip6_flow = 0; + ip6.ip6_hlim = 0; + ip6.ip6_vfc &= ~IPV6_VERSION_MASK; + ip6.ip6_vfc |= IPV6_VERSION; + + /* + * Note that here we assume that on output, the IPv6 header + * and any Type0 Routing Header present have been made to look + * like the will at the destination. Note that this is a + * different assumption than we made for IPv4 (because of + * different option processing in IPv4 and IPv6, and different + * code paths from IPv4/IPv6 to here). + */ + + /* Include IPv6 header in authenticator computation */ + ahx->Update(&ctx, (unsigned char *) &ip6, sizeof(ip6)); + + /* Let's deal with the remaining headers (if any) */ + if (skip - sizeof(struct ip6_hdr) > 0) + { + if (m->m_len <= skip) + { + MALLOC(ptr, unsigned char *, + skip - sizeof(struct ip6_hdr), M_XDATA, M_WAITOK); + + /* Copy all the protocol headers after the IPv6 header */ + m_copydata(m, sizeof(struct ip6_hdr), + skip - sizeof(struct ip6_hdr), ptr); + } + else + ptr = mtod(m, unsigned char *) + sizeof(struct ip6_hdr); + } + else + break; /* Done */ + + off = ip6.ip6_nxt & 0xff; /* Next header type */ + for (len = 0; len < skip - sizeof(struct ip6_hdr);) + switch (off) + { + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + ip6e = (struct ip6_ext *) (ptr + len); + + /* + * Process the mutable/immutable options -- borrows + * heavily from the KAME code. + */ + for (last = len, count = len + sizeof(struct ip6_ext); + count < len + ((ip6e->ip6e_len + 1) << 3);) + { + if (ptr[count] == IP6OPT_PAD1) + { + count++; + continue; + } + + /* Sanity check */ + if (count + sizeof(struct ip6_ext) > len + + ((ip6e->ip6e_len + 1) << 3)) + { + DPRINTF(("ah_output(): malformed IPv6 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + + /* Free, if we allocated */ + if (m->m_len < skip) + FREE(ptr, M_XDATA); + return EMSGSIZE; + } + + /* + * If mutable option, calculate authenticator + * for all immutable fields so far, then include + * a zeroed-out version of this option. + */ + if (ptr[count] & IP6OPT_MUTABLE) + { + /* Calculate immutables */ + ahx->Update(&ctx, ptr + last, count + 2 - last); + last = count + ptr[count + 1]; + + /* Calculate "zeroed-out" immutables */ + ahx->Update(&ctx, ipseczeroes, + ptr[count + 1] - 2); + } + + count += ptr[count + 1]; + + /* Sanity check */ + if (count > skip - sizeof(struct ip6_hdr)) + { + DPRINTF(("ah_output(): malformed IPv6 options header in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + + /* Free, if we allocated */ + if (m->m_len < skip) + FREE(ptr, M_XDATA); + return EMSGSIZE; + } + } + + /* Include any trailing immutable options */ + ahx->Update(&ctx, ptr + last, + len + ((ip6e->ip6e_len + 1) << 3) - last); + + len += ((ip6e->ip6e_len + 1) << 3); /* Advance */ + off = ip6e->ip6e_nxt; + break; + + case IPPROTO_ROUTING: + ip6e = (struct ip6_ext *) (ptr + len); + ahx->Update(&ctx, ptr + len, (ip6e->ip6e_len + 1) << 3); + len += ((ip6e->ip6e_len + 1) << 3); /* Advance */ + off = ip6e->ip6e_nxt; + break; + } + + /* Free, if we allocated */ + if (m->m_len < skip) + { + FREE(ptr, M_XDATA); + ptr = NULL; + } + + break; +#endif /* INET6 */ + + default: + DPRINTF(("ah_output(): unsupported protocol family %d in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_nopf++; + m_freem(m); + return EPFNOSUPPORT; + } + + /* Inject AH header */ + (*mp) = m_inject(m, skip, AH_FLENGTH + rplen + ahx->authsize, M_WAITOK); + if ((*mp) == NULL) + { + DPRINTF(("ah_output(): failed to inject AH header for SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + m_freem(m); + ahstat.ahs_wrap++; + return ENOBUFS; + } + + /* + * The AH header is guaranteed by m_inject() to be in contiguous memory, + * at the beginning of the returned mbuf. + */ + ah = mtod((*mp), struct ah *); + + /* Initialize the AH header */ + m_copydata(m, protoff, sizeof(u_int8_t), (caddr_t) &ah->ah_nh); + ah->ah_hl = (rplen + ahx->authsize) / sizeof(u_int32_t); + ah->ah_rv = 0; + ah->ah_spi = tdb->tdb_spi; + + if (!(tdb->tdb_flags & TDBF_NOREPLAY)) + ah->ah_rpl = htonl(tdb->tdb_rpl++); + + /* Update the Next Protocol field in the IP header */ + len = IPPROTO_AH; + m_copyback(m, protoff, sizeof(u_int8_t), (caddr_t) &len); + + /* Include the header AH in the authenticator computation */ + ahx->Update(&ctx, (unsigned char *) ah, AH_FLENGTH + rplen); + ahx->Update(&ctx, ipseczeroes, ahx->authsize); + + /* Calculate the authenticator over the rest of the packet */ + len = m->m_pkthdr.len - (skip + AH_FLENGTH + rplen + ahx->authsize); + off = AH_FLENGTH + rplen + ahx->authsize; + + while (len > 0) + { + if ((*mp) == 0) + { + DPRINTF(("ah_output(): bad mbuf chain for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi))); + ahstat.ahs_hdrops++; + m_freem(m); + (*mp) = NULL; + return EMSGSIZE; + } + + count = min((*mp)->m_len - off, len); + + ahx->Update(&ctx, mtod((*mp), unsigned char *) + off, count); + + len -= count; + off = 0; + (*mp) = (*mp)->m_next; + } + + if ((ahx->type == SADB_X_AALG_MD5) || (ahx->type == SADB_X_AALG_SHA1)) + ahx->Update(&ctx, (unsigned char *) tdb->tdb_amxkey, + tdb->tdb_amxkeylen); + else + { + /* HMAC */ + ahx->Final(calcauth, &ctx); + bcopy(tdb->tdb_octx, &ctx, ahx->ctxsize); + ahx->Update(&ctx, calcauth, ahx->hashsize); + } + + ahx->Final(calcauth, &ctx); + + /* Copy the authenticator */ + bcopy(calcauth, ((caddr_t) ah) + AH_FLENGTH + rplen, ahx->authsize); + + *mp = m; + + return 0; +} |