summaryrefslogtreecommitdiff
path: root/sys/netinet/ip_ah.c
diff options
context:
space:
mode:
authorAngelos D. Keromytis <angelos@cvs.openbsd.org>2000-01-27 08:09:13 +0000
committerAngelos D. Keromytis <angelos@cvs.openbsd.org>2000-01-27 08:09:13 +0000
commit98f40bbc84e70cdf4b25f5ee817c6eebe3d46a3d (patch)
treed43e5507a833c1b2d60fea7c9e4363d15715be06 /sys/netinet/ip_ah.c
parent95d6b976dfe454179868d92f7eab5c4bcdd4ab5a (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.c1241
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;
+}