summaryrefslogtreecommitdiff
path: root/sys/netinet/ip_esp.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_esp.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_esp.c')
-rw-r--r--sys/netinet/ip_esp.c1143
1 files changed, 1143 insertions, 0 deletions
diff --git a/sys/netinet/ip_esp.c b/sys/netinet/ip_esp.c
new file mode 100644
index 00000000000..e9cc0356dae
--- /dev/null
+++ b/sys/netinet/ip_esp.c
@@ -0,0 +1,1143 @@
+/* $OpenBSD: ip_esp.c,v 1.31 2000/01/27 08:09:09 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 <machine/cpu.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/netisr.h>
+#include <net/bpf.h>
+
+#include <sys/socketvar.h>
+#include <net/raw_cb.h>
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#endif /* INET */
+
+#ifdef INET6
+#include <netinet6/in6.h>
+#include <netinet6/ip6.h>
+#endif /* INET6 */
+
+#include <netinet/ip_ipsp.h>
+#include <netinet/ip_esp.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;
+
+struct auth_hash *esp_hash[] = {
+ &auth_hash_hmac_md5_96,
+ &auth_hash_hmac_sha1_96,
+ &auth_hash_hmac_ripemd_160_96
+};
+
+extern struct enc_xform enc_xform_des;
+extern struct enc_xform enc_xform_3des;
+extern struct enc_xform enc_xform_blf;
+extern struct enc_xform enc_xform_cast5;
+extern struct enc_xform enc_xform_skipjack;
+
+struct enc_xform *esp_xform[] = {
+ &enc_xform_des,
+ &enc_xform_3des,
+ &enc_xform_blf,
+ &enc_xform_cast5,
+ &enc_xform_skipjack,
+};
+
+/*
+ * esp_attach() is called from the transformation initialization code.
+ */
+
+int
+esp_attach()
+{
+ return 0;
+}
+
+/*
+ * esp_init() is called when an SPI is being set up.
+ */
+
+int
+esp_init(struct tdb *tdbp, struct xformsw *xsp, struct ipsecinit *ii)
+{
+ struct enc_xform *txform = NULL;
+ struct auth_hash *thash = NULL;
+ int i;
+
+ /* Check whether the encryption algorithm is supported */
+ for (i = sizeof(esp_xform) / sizeof(esp_xform[0]) - 1;
+ i >= 0; i--)
+ if (ii->ii_encalg == esp_xform[i]->type)
+ break;
+
+ if (i < 0)
+ {
+ DPRINTF(("esp_init(): unsupported encryption algorithm %d specified\n", ii->ii_encalg));
+ return EINVAL;
+ }
+
+ txform = esp_xform[i];
+
+ if (ii->ii_enckeylen < txform->minkey)
+ {
+ DPRINTF(("esp_init(): keylength %d too small (min length is %d) for algorithm %s\n", ii->ii_enckeylen, txform->minkey, txform->name));
+ return EINVAL;
+ }
+
+ if (ii->ii_enckeylen > txform->maxkey)
+ {
+ DPRINTF(("esp_init(): keylength %d too large (max length is %d) for algorithm %s\n", ii->ii_enckeylen, txform->maxkey, txform->name));
+ return EINVAL;
+ }
+
+ if (ii->ii_authalg)
+ {
+ for (i = sizeof(esp_hash) / sizeof(esp_hash[0]) - 1;
+ i >= 0; i--)
+ if (ii->ii_authalg == esp_hash[i]->type)
+ break;
+
+ if (i < 0)
+ {
+ DPRINTF(("esp_init(): unsupported authentication algorithm %d specified\n", ii->ii_authalg));
+ return EINVAL;
+ }
+
+ thash = esp_hash[i];
+
+ if (ii->ii_authkeylen != thash->keysize)
+ {
+ DPRINTF(("esp_init(): keylength %d doesn't match algorithm %s keysize (%d)\n", ii->ii_authkeylen, thash->name, thash->keysize));
+ return EINVAL;
+ }
+
+ tdbp->tdb_authalgxform = thash;
+
+ DPRINTF(("esp_init(): initialized TDB with hash algorithm %s\n",
+ thash->name));
+ }
+
+ tdbp->tdb_xform = xsp;
+ tdbp->tdb_encalgxform = txform;
+ tdbp->tdb_bitmap = 0;
+ tdbp->tdb_rpl = AH_HMAC_INITIAL_RPL;
+
+ DPRINTF(("esp_init(): initialized TDB with enc algorithm %s\n",
+ txform->name));
+
+ tdbp->tdb_ivlen = txform->ivmask;
+ if (tdbp->tdb_flags & TDBF_HALFIV)
+ tdbp->tdb_ivlen /= 2;
+
+ /* Initialize the IV */
+ get_random_bytes(tdbp->tdb_iv, tdbp->tdb_ivlen);
+
+ /* Save the raw keys */
+ 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);
+
+ tdbp->tdb_emxkeylen = ii->ii_enckeylen;
+ MALLOC(tdbp->tdb_emxkey, u_int8_t *, tdbp->tdb_emxkeylen, M_XDATA,
+ M_WAITOK);
+ bcopy(ii->ii_enckey, tdbp->tdb_emxkey, tdbp->tdb_emxkeylen);
+
+ if (txform->setkey)
+ txform->setkey(&tdbp->tdb_key, ii->ii_enckey, ii->ii_enckeylen);
+
+ if (thash)
+ {
+ /* 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);
+ }
+
+ return 0;
+}
+
+int
+esp_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_emxkey)
+ {
+ bzero(tdbp->tdb_emxkey, tdbp->tdb_emxkeylen);
+ FREE(tdbp->tdb_emxkey, M_XDATA);
+ tdbp->tdb_emxkey = NULL;
+ }
+
+ if (tdbp->tdb_key && tdbp->tdb_encalgxform &&
+ tdbp->tdb_encalgxform->zerokey)
+ tdbp->tdb_encalgxform->zerokey(&tdbp->tdb_key);
+
+ 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;
+}
+
+#define MAXBUFSIZ (AH_ALEN_MAX > ESP_MAX_IVS ? AH_ALEN_MAX : ESP_MAX_IVS)
+
+struct mbuf *
+esp_input(struct mbuf *m, struct tdb *tdb, int skip, int protoff)
+{
+ struct auth_hash *esph = (struct auth_hash *) tdb->tdb_authalgxform;
+ struct enc_xform *espx = (struct enc_xform *) tdb->tdb_encalgxform;
+ int oplen, plen, alen, ilen, i, blks, rest, count, off, roff, hlen;
+ u_char iv[MAXBUFSIZ], niv[MAXBUFSIZ], blk[ESP_MAX_BLKS], *lblk;
+ u_char *idat, *odat, *ivp, *ivn;
+ struct mbuf *mi, *mo, *m1;
+ union authctx ctx;
+ u_int32_t btsx;
+
+ /* Determine the ESP header length */
+ if (tdb->tdb_flags & TDBF_NOREPLAY)
+ hlen = sizeof(u_int32_t) + tdb->tdb_ivlen; /* "old" ESP */
+ else
+ hlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen; /* "new" ESP */
+
+ blks = espx->blocksize;
+
+ if (esph)
+ alen = AH_HMAC_HASHLEN;
+ else
+ alen = 0;
+
+ /* Skip the IP header, IP options, SPI, IV, and any Replay and Auth Data */
+ plen = m->m_pkthdr.len - (skip + hlen + alen);
+ if ((plen & (blks - 1)) || (plen <= 0))
+ {
+ DPRINTF(("esp_input(): payload not a multiple of %d octets, SA %s/%08x\n", blks, ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_badilen++;
+ m_freem(m);
+ return NULL;
+ }
+
+ /* Auth covers SPI + SN + IV */
+ oplen = plen + hlen;
+
+ /* Replay window checking, if appropriate */
+ if ((tdb->tdb_wnd > 0) && (!(tdb->tdb_flags & TDBF_NOREPLAY)))
+ {
+ m_copydata(m, skip + sizeof(u_int32_t), sizeof(u_int32_t),
+ (unsigned char *) &btsx);
+ btsx = ntohl(btsx);
+
+ switch (checkreplaywindow32(btsx, 0, &(tdb->tdb_rpl), tdb->tdb_wnd,
+ &(tdb->tdb_bitmap)))
+ {
+ case 0: /* All's well */
+ break;
+
+ case 1:
+ DPRINTF(("esp_input(): replay counter wrapped for SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_wrap++;
+ m_freem(m);
+ return NULL;
+
+ case 2:
+ case 3:
+ DPRINTF(("esp_input(): duplicate packet received in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_replay++;
+ m_freem(m);
+ return NULL;
+
+ default:
+ DPRINTF(("esp_input(): bogus value from checkreplaywindow32() in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_replay++;
+ m_freem(m);
+ return NULL;
+ }
+ }
+
+ /* Update the counters */
+ tdb->tdb_cur_bytes += m->m_pkthdr.len - skip - hlen - alen;
+ espstat.esps_ibytes += m->m_pkthdr.len - skip - hlen - alen;
+
+ /* 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 */
+ }
+
+ /*
+ * Skip forward to the beginning of the ESP header. If we run out
+ * of mbufs in the process, the check inside the following while()
+ * loop will catch it.
+ */
+ for (mo = m, i = 0; mo && i + mo->m_len <= skip; mo = mo->m_next)
+ i += mo->m_len;
+
+ off = skip - i;
+
+ /* Preserve these for later processing */
+ roff = off;
+ m1 = mo;
+
+ /* Verify the authenticator, if applicable */
+ if (esph)
+ {
+ bcopy(tdb->tdb_ictx, &ctx, esph->ctxsize);
+
+ /* Copy the authentication data */
+ m_copydata(m, m->m_pkthdr.len - alen, alen, iv);
+
+ /* Compute authenticator over the mbuf chain */
+ while (oplen > 0)
+ {
+ if (mo == NULL)
+ {
+ DPRINTF(("esp_input(): bad mbuf chain, SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return NULL;
+ }
+
+ count = min(mo->m_len - off, oplen);
+ esph->Update(&ctx, mtod(mo, unsigned char *) + off, count);
+ oplen -= count;
+ off = 0;
+ mo = mo->m_next;
+ }
+
+ esph->Final(niv, &ctx);
+ bcopy(tdb->tdb_octx, &ctx, esph->ctxsize);
+ esph->Update(&ctx, niv, esph->hashsize);
+ esph->Final(niv, &ctx);
+
+ /* Verify */
+ if (bcmp(niv, iv, AH_HMAC_HASHLEN))
+ {
+ DPRINTF(("esp_input(): authentication failed for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_badauth++;
+ m_freem(m);
+ return NULL;
+ }
+ }
+
+ oplen = plen;
+
+ /* Find beginning of encrypted data (actually, the IV) */
+ mi = m1;
+ ilen = mi->m_len - roff - sizeof(u_int32_t);
+ if (!(tdb->tdb_flags & TDBF_NOREPLAY))
+ ilen -= sizeof(u_int32_t);
+ while (ilen <= 0)
+ {
+ mi = mi->m_next;
+ if (mi == NULL)
+ {
+ DPRINTF(("esp_input(): bad mbuf chain, SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return NULL;
+ }
+
+ ilen += mi->m_len;
+ }
+
+ idat = mtod(mi, unsigned char *) + (mi->m_len - ilen);
+ m_copydata(mi, mi->m_len - ilen, tdb->tdb_ivlen, iv);
+
+ /* If we're doing half-IV, generate full IV */
+ if (tdb->tdb_flags & TDBF_HALFIV)
+ {
+ for (i = 0; i < tdb->tdb_ivlen; i++)
+ iv[tdb->tdb_ivlen + i] = ~iv[i];
+ }
+
+ /* Now skip over the IV */
+ ilen -= tdb->tdb_ivlen;
+ while (ilen <= 0)
+ {
+ mi = mi->m_next;
+ if (mi == NULL)
+ {
+ DPRINTF(("esp_input(): bad mbuf chain, SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return NULL;
+ }
+
+ ilen += mi->m_len;
+ }
+
+ /*
+ * Remove the ESP header and IV from the mbuf.
+ */
+ if (roff == 0)
+ {
+ /* The ESP header was conveniently at the beginning of the mbuf */
+ m_adj(m1, hlen);
+ if (!(m1->m_flags & M_PKTHDR))
+ m->m_pkthdr.len -= hlen;
+ }
+ else
+ if (roff + hlen >= m1->m_len)
+ {
+ /*
+ * Part or all of the ESP header is at the end of this mbuf, so first
+ * let's remove the remainder of the ESP header from the
+ * beginning of the remainder of the mbuf chain, if any.
+ */
+ if (roff + hlen > m1->m_len)
+ {
+ /* Adjust the next mbuf by the remainder */
+ m_adj(m1->m_next, roff + hlen - m1->m_len);
+
+ /* The second mbuf is guaranteed not to have a pkthdr... */
+ m->m_pkthdr.len -= (roff + hlen - m1->m_len);
+ }
+
+ /* Now, let's unlink the mbuf chain for a second...*/
+ mo = 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 = mo;
+ }
+ else
+ {
+ /*
+ * The ESP 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 + hlen, mtod(m1, u_char *) + roff,
+ m1->m_len - (roff + hlen));
+ m1->m_len -= hlen;
+ m->m_pkthdr.len -= hlen;
+ }
+
+ /* Point to the encrypted data */
+ idat = mtod(mi, unsigned char *) + (mi->m_len - ilen);
+
+ /*
+ * At this point:
+ * plen is # of encapsulated payload octets
+ * ilen is # of octets left in this mbuf
+ * idat is first encapsulated payload octed in this mbuf
+ * same for olen and odat
+ * ivp points to the IV, ivn buffers the next IV.
+ * mi points to the first mbuf
+ *
+ * From now on until the end of the mbuf chain:
+ * . move the next eight octets of the chain into ivn
+ * . decrypt idat and xor with ivp
+ * . swap ivp and ivn.
+ * . repeat
+ */
+
+ ivp = iv;
+ ivn = niv;
+ rest = ilen % blks;
+ while (plen > 0) /* while not done */
+ {
+ if (ilen < blks)
+ {
+ if (rest)
+ {
+ bcopy(idat, blk, rest);
+ odat = idat;
+ }
+
+ do {
+ mi = (mo = mi)->m_next;
+ if (mi == NULL)
+ {
+ DPRINTF(("esp_input(): bad mbuf chain, SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return NULL;
+ }
+ } while (mi->m_len == 0);
+
+ if (mi->m_len < blks - rest)
+ {
+ if ((mi = m_pullup(mi, blks - rest)) == NULL)
+ {
+ DPRINTF(("esp_input(): m_pullup() failed, SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_hdrops++;
+ return NULL;
+ }
+ /*
+ * m_pullup was not called at the beginning of the chain
+ * but might return a new mbuf, link it into the chain.
+ */
+ mo->m_next = mi;
+ }
+
+ ilen = mi->m_len;
+ idat = mtod(mi, u_char *);
+
+ if (rest)
+ {
+ bcopy(idat, blk + rest, blks - rest);
+ bcopy(blk, ivn, blks);
+
+ espx->decrypt(tdb, blk);
+
+ for (i = 0; i < blks; i++)
+ blk[i] ^= ivp[i];
+
+ ivp = ivn;
+ ivn = (ivp == iv) ? niv : iv;
+
+ bcopy(blk, odat, rest);
+ bcopy(blk + rest, idat, blks - rest);
+
+ lblk = blk; /* last block touched */
+
+ idat += blks - rest;
+ ilen -= blks - rest;
+ plen -= blks;
+ }
+
+ rest = ilen % blks;
+ }
+
+ while (ilen >= blks && plen > 0)
+ {
+ bcopy(idat, ivn, blks);
+
+ espx->decrypt(tdb, idat);
+
+ for (i = 0; i < blks; i++)
+ idat[i] ^= ivp[i];
+
+ ivp = ivn;
+ ivn = (ivp == iv) ? niv : iv;
+
+ lblk = idat; /* last block touched */
+ idat += blks;
+
+ ilen -= blks;
+ plen -= blks;
+ }
+ }
+
+ /* Save last block (end of padding), if it was in-place decrypted */
+ if (lblk != blk)
+ bcopy(lblk, blk, blks);
+
+ /*
+ * Now, the entire chain has been decrypted. As a side effect,
+ * blk[blks - 1] contains the next protocol, and blk[blks - 2] contains
+ * the amount of padding the original chain had. Chop off the
+ * appropriate parts of the chain, and return.
+ */
+
+ if (blk[blks - 2] + 2 + alen > m->m_pkthdr.len - skip - hlen)
+ {
+ DPRINTF(("esp_input(): invalid padding length %d for packet in SA %s/%08x\n", blk[blks - 2], ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_badilen++;
+ m_freem(m);
+ return NULL;
+ }
+
+ /* Verify correct decryption by checking the last padding bytes. */
+ if (!(tdb->tdb_flags & TDBF_RANDOMPADDING))
+ {
+ if ((blk[blks - 2] != blk[blks - 3]) && (blk[blks - 2] != 0))
+ {
+ DPRINTF(("esp_input(): decryption failed for packet in SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_badenc++;
+ m_freem(m);
+ return NULL;
+ }
+ }
+
+ /* Trim the mbuf chain to remove the trailing authenticator */
+ m_adj(m, - blk[blks - 2] - 2 - alen);
+
+ /* Restore the Next Protocol field */
+ m_copyback(m, protoff, 1, &blk[blks - 1]);
+
+ return m;
+}
+
+int
+esp_output(struct mbuf *m, struct tdb *tdb, struct mbuf **mp, int skip,
+ int protoff)
+{
+ struct enc_xform *espx = (struct enc_xform *) tdb->tdb_encalgxform;
+ struct auth_hash *esph = (struct auth_hash *) tdb->tdb_authalgxform;
+ u_char iv[ESP_MAX_IVS], blk[ESP_MAX_BLKS], auth[AH_ALEN_MAX];
+ int i, ilen, hlen, rlen, plen, padding, rest, blks, alen;
+ struct mbuf *mi, *mo = (struct mbuf *) NULL;
+ u_char *pad, *idat, *odat, *ivp;
+ union authctx ctx;
+
+#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;
+ if (espx)
+ hdr.flags |= M_CONF;
+ if (esph)
+ 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
+
+ if (tdb->tdb_flags & TDBF_NOREPLAY)
+ hlen = sizeof(u_int32_t) + tdb->tdb_ivlen;
+ else
+ hlen = 2 * sizeof(u_int32_t) + tdb->tdb_ivlen;
+
+ blks = espx->blocksize;
+ rlen = m->m_pkthdr.len - skip; /* Raw payload length */
+ padding = ((blks - ((rlen + 2) % blks)) % blks) + 2;
+ plen = rlen + padding; /* Padded payload length */
+
+ if (esph)
+ alen = AH_HMAC_HASHLEN;
+ else
+ alen = 0;
+
+ espstat.esps_output++;
+
+ /* Check for replay counter wrap-around in automatic (not manual) keying */
+ if ((!(tdb->tdb_flags & TDBF_NOREPLAY)) &&
+ (tdb->tdb_rpl == 0) && (tdb->tdb_wnd > 0))
+ {
+ DPRINTF(("esp_output(): SA %s/%0x8 should have expired\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_wrap++;
+ return ENOBUFS;
+ }
+
+#ifdef INET
+ /* Check for IPv6 maximum packet size violations */
+ if (tdb->tdb_dst.sa.sa_family == AF_INET)
+ if (skip + hlen + rlen + padding + alen > IP_MAXPACKET)
+ {
+ DPRINTF(("esp_output(): packet in SA %s/%0x8 got too big\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_toobig++;
+ return EMSGSIZE;
+ }
+#endif /* INET */
+
+#ifdef INET6
+ /* Check for IPv6 maximum packet size violations */
+ if (tdb->tdb_dst.sa.sa_family == AF_INET6)
+ if (skip + hlen + rlen + padding + alen > IPV6_MAXPACKET)
+ {
+ DPRINTF(("esp_output(): packet in SA %s/%0x8 got too big\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_toobig++;
+ return EMSGSIZE;
+ }
+#endif /* INET6 */
+
+ /* Update the counters */
+ tdb->tdb_cur_bytes += m->m_pkthdr.len - skip;
+ espstat.esps_obytes += m->m_pkthdr.len - skip;
+
+ /* Hard byte 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;
+ }
+
+ /* Soft byte 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.
+ */
+ mi = m;
+ while (mi != NULL &&
+ (!(mi->m_flags & M_EXT) ||
+ (mi->m_ext.ext_ref == NULL &&
+ mclrefcnt[mtocl(mi->m_ext.ext_buf)] <= 1)))
+ {
+ mo = mi;
+ mi = mi->m_next;
+ }
+
+ if (mi != NULL)
+ {
+ /* Replace the rest of the mbuf chain. */
+ struct mbuf *n = m_copym2(mi, 0, M_COPYALL, M_DONTWAIT);
+
+ if (n == NULL)
+ {
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return ENOBUFS;
+ }
+
+ if (mo != NULL)
+ mo->m_next = n;
+ else
+ m = n;
+
+ m_freem(mi);
+ }
+
+ /* Inject ESP header */
+ mo = m_inject(m, skip, hlen, M_WAITOK);
+ if (mo == NULL)
+ {
+ DPRINTF(("esp_output(): failed to inject ESP header for SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_wrap++;
+ return ENOBUFS;
+ }
+
+ /* Initialize ESP header */
+ m_copyback(mo, 0, sizeof(u_int32_t), (caddr_t) &tdb->tdb_spi);
+ if (!(tdb->tdb_flags & TDBF_NOREPLAY))
+ {
+ u_int32_t replay = htonl(tdb->tdb_rpl++);
+ m_copyback(mo, sizeof(u_int32_t), sizeof(u_int32_t),
+ (caddr_t) &replay);
+ }
+
+ /* Initialize IV (cook the half-IV if applicable) */
+ bcopy(tdb->tdb_iv, iv, tdb->tdb_ivlen);
+ if (tdb->tdb_flags & TDBF_HALFIV)
+ {
+ for (i = 0; i < tdb->tdb_ivlen; i++)
+ iv[i + tdb->tdb_ivlen] = ~iv[i];
+ }
+
+ /* Copy IV in ESP header */
+ m_copyback(mo, hlen - tdb->tdb_ivlen, tdb->tdb_ivlen,
+ (caddr_t) &tdb->tdb_iv);
+
+ /* Add padding */
+ pad = (u_char *) m_pad(m, padding + alen,
+ tdb->tdb_flags & TDBF_RANDOMPADDING);
+ if (pad == NULL)
+ {
+ DPRINTF(("esp_output(): m_pad() failed for SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ return ENOBUFS;
+ }
+
+ /* Self-describing padding ? */
+ if (!(tdb->tdb_flags & TDBF_RANDOMPADDING))
+ {
+ for (ilen = 0; ilen < padding - 2; ilen++)
+ pad[ilen] = ilen + 1;
+ }
+
+ /* Fix padding length and Next Protocol in padding itself */
+ pad[padding - 2] = padding - 2;
+ m_copydata(m, protoff, 1, &pad[padding - 1]);
+
+ /* Fix Next Protocol in IPv4/IPv6 header */
+ ilen = IPPROTO_ESP;
+ m_copyback(m, protoff, sizeof(u_int8_t), (u_char *) &ilen);
+
+ mi = mo;
+
+ /* If it's just the ESP header, just skip to the next mbuf */
+ if (mi->m_len == hlen)
+ {
+ mi = mi->m_next;
+ ilen = mi->m_len;
+ idat = mtod(mi, u_char *);
+ }
+ else
+ { /* There's data at the end of this mbuf, skip over ESP header */
+ ilen = mi->m_len - hlen;
+ idat = mtod(mi, u_char *) + hlen;
+ }
+
+ /* Authenticate the ESP header if applicable */
+ if (esph)
+ {
+ bcopy(tdb->tdb_ictx, &ctx, esph->ctxsize);
+ esph->Update(&ctx, mtod(mo, unsigned char *), hlen);
+ }
+
+ /* Encrypt the payload */
+ ivp = iv;
+ rest = ilen % blks;
+ while (plen > 0) /* while not done */
+ {
+ if (ilen < blks)
+ {
+ if (rest)
+ {
+ if (ivp == blk)
+ {
+ bcopy(blk, iv, blks);
+ ivp = iv;
+ }
+
+ bcopy(idat, blk, rest);
+ odat = idat;
+ }
+
+ do {
+ mi = (mo = mi)->m_next;
+ if (mi == NULL)
+ {
+ DPRINTF(("esp_output(): bad mbuf chain, SA %s/%08x\n",
+ ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ espstat.esps_hdrops++;
+ m_freem(m);
+ return EINVAL;
+ }
+ } while (mi->m_len == 0);
+
+ if (mi->m_len < blks - rest)
+ {
+ if ((mi = m_pullup(mi, blks - rest)) == NULL)
+ {
+ DPRINTF(("esp_output(): m_pullup() failed, SA %s/%08x\n", ipsp_address(tdb->tdb_dst), ntohl(tdb->tdb_spi)));
+ m_freem(m);
+ espstat.esps_hdrops++;
+ return ENOBUFS;
+ }
+ /*
+ * m_pullup was not called at the beginning of the chain
+ * but might return a new mbuf, link it into the chain.
+ */
+ mo->m_next = mi;
+ }
+
+ ilen = mi->m_len;
+ idat = mtod(mi, u_char *);
+
+ if (rest)
+ {
+ bcopy(idat, blk + rest, blks - rest);
+
+ for (i = 0; i < blks; i++)
+ blk[i] ^= ivp[i];
+
+ espx->encrypt(tdb, blk);
+
+ if (esph)
+ esph->Update(&ctx, blk, blks);
+
+ ivp = blk;
+
+ bcopy(blk, odat, rest);
+ bcopy(blk + rest, idat, blks - rest);
+
+ idat += blks - rest;
+ ilen -= blks - rest;
+ plen -= blks;
+ }
+
+ rest = ilen % blks;
+ }
+
+ while (ilen >= blks && plen > 0)
+ {
+ for (i = 0; i < blks; i++)
+ idat[i] ^= ivp[i];
+
+ espx->encrypt(tdb, idat);
+
+ if (esph)
+ esph->Update(&ctx, idat, blks);
+
+ ivp = idat;
+ idat += blks;
+
+ ilen -= blks;
+ plen -= blks;
+ }
+ }
+
+ /* Put in authentication data */
+ if (esph)
+ {
+ esph->Final(auth, &ctx);
+ bcopy(tdb->tdb_octx, &ctx, esph->ctxsize);
+ esph->Update(&ctx, auth, esph->hashsize);
+ esph->Final(auth, &ctx);
+
+ /* Copy the final authenticator -- cheat and use bcopy() again */
+ bcopy(auth, pad + padding, alen);
+ }
+
+ /* Save the last encrypted block, to be used as the next IV */
+ bcopy(ivp, tdb->tdb_iv, tdb->tdb_ivlen);
+
+ *mp = m;
+
+ return 0;
+}
+
+/*
+ * return 0 on success
+ * return 1 for counter == 0
+ * return 2 for very old packet
+ * return 3 for packet within current window but already received
+ */
+int
+checkreplaywindow32(u_int32_t seq, u_int32_t initial, u_int32_t *lastseq,
+ u_int32_t window, u_int32_t *bitmap)
+{
+ u_int32_t diff;
+
+ seq -= initial;
+
+ if (seq == 0)
+ return 1;
+
+ if (seq > *lastseq - initial)
+ {
+ diff = seq - (*lastseq - initial);
+ if (diff < window)
+ *bitmap = ((*bitmap) << diff) | 1;
+ else
+ *bitmap = 1;
+ *lastseq = seq + initial;
+ return 0;
+ }
+
+ diff = *lastseq - initial - seq;
+ if (diff >= window)
+ {
+ espstat.esps_wrap++;
+ return 2;
+ }
+ if ((*bitmap) & (((u_int32_t) 1) << diff))
+ {
+ espstat.esps_replay++;
+ return 3;
+ }
+
+ *bitmap |= (((u_int32_t) 1) << diff);
+ return 0;
+}
+
+/*
+ *
+ *
+ * m_pad(m, n) pads <m> with <n> bytes at the end. The packet header
+ * length is updated, and a pointer to the first byte of the padding
+ * (which is guaranteed to be all in one mbuf) is returned. The third
+ * argument specifies whether we need randompadding or not.
+ *
+ */
+
+caddr_t
+m_pad(struct mbuf *m, int n, int randompadding)
+{
+ register struct mbuf *m0, *m1;
+ register int len, pad;
+ caddr_t retval;
+ u_int8_t dat;
+
+ if (n <= 0) /* no stupid arguments */
+ {
+ DPRINTF(("m_pad(): pad length invalid (%d)\n", n));
+ return NULL;
+ }
+
+ len = m->m_pkthdr.len;
+ pad = n;
+
+ m0 = m;
+
+ while (m0->m_len < len)
+ {
+ len -= m0->m_len;
+ m0 = m0->m_next;
+ }
+
+ if (m0->m_len != len)
+ {
+ DPRINTF(("m_pad(): length mismatch (should be %d instead of %d)\n",
+ m->m_pkthdr.len, m->m_pkthdr.len + m0->m_len - len));
+ m_freem(m);
+ return NULL;
+ }
+
+ if ((m0->m_flags & M_EXT) ||
+ (m0->m_data + m0->m_len + pad >= &(m0->m_dat[MLEN])))
+ {
+ /*
+ * Add an mbuf to the chain
+ */
+
+ MGET(m1, M_DONTWAIT, MT_DATA);
+ if (m1 == 0)
+ {
+ m_freem(m0);
+ DPRINTF(("m_pad(): cannot append\n"));
+ return NULL;
+ }
+
+ m0->m_next = m1;
+ m0 = m1;
+ m0->m_len = 0;
+ }
+
+ retval = m0->m_data + m0->m_len;
+ m0->m_len += pad;
+ m->m_pkthdr.len += pad;
+
+ if (randompadding)
+ for (len = 0; len < n; len++)
+ {
+ get_random_bytes((void *) &dat, sizeof(u_int8_t));
+ retval[len] = len + dat;
+ }
+
+ return retval;
+}