/* $OpenBSD: ip_ah_new.c,v 1.18 1998/06/03 09:50:18 provos 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. * * Copyright (C) 1995, 1996, 1997, 1998 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. */ /* * Based on RFC 2085. */ #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 <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/in_pcb.h> #include <netinet/in_var.h> #include <netinet/ip_var.h> #include <netinet/ip_icmp.h> #include <net/encap.h> #include <netinet/ip_ipsp.h> #include <netinet/ip_ah.h> #include <sys/syslog.h> #ifdef ENCDEBUG #define DPRINTF(x) if (encdebug) printf x #else #define DPRINTF(x) #endif extern void encap_sendnotify(int, struct tdb *, void *); struct ah_hash ah_new_hash[] = { { ALG_AUTH_MD5, "HMAC-MD5-96", AH_MD5_ALEN, sizeof(MD5_CTX), (void (*)(void *)) MD5Init, (void (*)(void *, u_int8_t *, u_int16_t)) MD5Update, (void (*)(u_int8_t *, void *)) MD5Final }, { ALG_AUTH_SHA1, "HMAC-SHA1-96", AH_SHA1_ALEN, sizeof(SHA1_CTX), (void (*)(void *)) SHA1Init, (void (*)(void *, u_int8_t *, u_int16_t)) SHA1Update, (void (*)(u_int8_t *, void *)) SHA1Final }, { ALG_AUTH_RMD160, "HMAC-RIPEMD-160-96", AH_RMD160_ALEN, sizeof(RMD160_CTX), (void (*)(void *)) RMD160Init, (void (*)(void *, u_int8_t *, u_int16_t)) RMD160Update, (void (*)(u_int8_t *, void *)) RMD160Final } }; /* * ah_new_attach() is called from the transformation initialization code. * It just returns. */ int ah_new_attach() { DPRINTF(("ah_new_attach(): setting up\n")); return 0; } /* * ah_new_init() is called when an SPI is being set up. It interprets the * encap_msghdr present in m, and sets up the transformation data. */ int ah_new_init(struct tdb *tdbp, struct xformsw *xsp, struct mbuf *m) { struct ah_new_xdata *xd; struct ah_new_xencap txd; struct encap_msghdr *em; struct ah_hash *thash; caddr_t buffer = NULL; int blocklen, i; if (m->m_len < ENCAP_MSG_FIXED_LEN) { if ((m = m_pullup(m, ENCAP_MSG_FIXED_LEN)) == NULL) { DPRINTF(("ah_new_init(): m_pullup failed\n")); return ENOBUFS; } } em = mtod(m, struct encap_msghdr *); if (em->em_msglen - EMT_SETSPI_FLEN <= AH_NEW_XENCAP_LEN) { if (encdebug) log(LOG_WARNING, "ah_new_init() initialization failed\n"); return EINVAL; } /* Just copy the standard fields */ m_copydata(m, EMT_SETSPI_FLEN, AH_NEW_XENCAP_LEN, (caddr_t) &txd); /* Check whether the hash algorithm is supported */ for (i = sizeof(ah_new_hash) / sizeof(struct ah_hash) - 1; i >= 0; i--) if (txd.amx_hash_algorithm == ah_new_hash[i].type) break; if (i < 0) { if (encdebug) log(LOG_WARNING, "ah_new_init(): unsupported authentication algorithm %d specified\n", txd.amx_hash_algorithm); return EINVAL; } DPRINTF(("ah_new_init(): initalized TDB with hash algorithm %d: %s\n", txd.amx_hash_algorithm, ah_new_hash[i].name)); thash = &ah_new_hash[i]; blocklen = HMAC_BLOCK_LEN; if (txd.amx_keylen + EMT_SETSPI_FLEN + AH_NEW_XENCAP_LEN != em->em_msglen) { if (encdebug) log(LOG_WARNING, "ah_new_init(): message length (%d) doesn't match\n", em->em_msglen); return EINVAL; } MALLOC(tdbp->tdb_xdata, caddr_t, sizeof(struct ah_new_xdata), M_XDATA, M_WAITOK); if (tdbp->tdb_xdata == NULL) { DPRINTF(("ah_new_init(): MALLOC failed\n")); return ENOBUFS; } MALLOC(buffer, caddr_t, (txd.amx_keylen < blocklen ? blocklen : txd.amx_keylen), M_TEMP, M_WAITOK); if (buffer == NULL) { DPRINTF(("ah_new_init(): MALLOC failed\n")); free(tdbp->tdb_xdata, M_XDATA); return ENOBUFS; } bzero(buffer, (txd.amx_keylen < blocklen ? blocklen : txd.amx_keylen)); bzero(tdbp->tdb_xdata, sizeof(struct ah_new_xdata)); xd = (struct ah_new_xdata *) tdbp->tdb_xdata; /* Copy the key to the buffer */ m_copydata(m, EMT_SETSPI_FLEN + AH_NEW_XENCAP_LEN, txd.amx_keylen, buffer); xd->amx_hash = thash; /* Shorten the key if necessary */ if (txd.amx_keylen > blocklen) { xd->amx_hash->Init(&(xd->amx_ictx)); xd->amx_hash->Update(&(xd->amx_ictx), buffer, txd.amx_keylen); bzero(buffer, (txd.amx_keylen < blocklen ? blocklen : txd.amx_keylen)); xd->amx_hash->Final(buffer, &(xd->amx_ictx)); } /* Pointer to the transform */ tdbp->tdb_xform = xsp; /* Pass name of auth algorithm for kernfs */ tdbp->tdb_authname = xd->amx_hash->name; xd->amx_hash_algorithm = txd.amx_hash_algorithm; xd->amx_rpl = AH_HMAC_INITIAL_RPL; xd->amx_wnd = txd.amx_wnd; xd->amx_bitmap = 0; /* Precompute the I and O pads of the HMAC */ for (i = 0; i < blocklen; i++) buffer[i] ^= HMAC_IPAD_VAL; xd->amx_hash->Init(&(xd->amx_ictx)); xd->amx_hash->Update(&(xd->amx_ictx), buffer, blocklen); for (i = 0; i < blocklen; i++) buffer[i] ^= (HMAC_IPAD_VAL ^ HMAC_OPAD_VAL); xd->amx_hash->Init(&(xd->amx_octx)); xd->amx_hash->Update(&(xd->amx_octx), buffer, blocklen); bzero(buffer, blocklen); /* paranoid */ free(buffer, M_TEMP); bzero(ipseczeroes, IPSEC_ZEROES_SIZE); /* paranoid */ return 0; } /* Free memory */ int ah_new_zeroize(struct tdb *tdbp) { DPRINTF(("ah_new_zeroize(): freeing memory\n")); if (tdbp->tdb_xdata) { FREE(tdbp->tdb_xdata, M_XDATA); tdbp->tdb_xdata = NULL; } return 0; } /* * ah_new_input() gets called to verify that an input packet * passes authentication. */ struct mbuf * ah_new_input(struct mbuf *m, struct tdb *tdb) { struct ah_new_xdata *xd; struct ip *ip, ipo; struct ah_new *aho, *ah; int ohlen, len, count, off, errc; u_int32_t btsx; struct mbuf *m0; union { MD5_CTX md5ctx; SHA1_CTX sha1ctx; RMD160_CTX rmd160ctx; } ctx; u_int8_t optval; u_char buffer[40]; aho = (struct ah_new *) buffer; xd = (struct ah_new_xdata *) tdb->tdb_xdata; ohlen = sizeof(struct ip) + AH_NEW_FLENGTH; if (m->m_len < ohlen) { if ((m = m_pullup(m, ohlen)) == NULL) { ahstat.ahs_hdrops++; DPRINTF(("ah_new_input(): (possibly too short) packet dropped\n")); return NULL; } } ip = mtod(m, struct ip *); /* Adjust, if options are present */ if ((ip->ip_hl << 2) > sizeof(struct ip)) { if ((m = m_pullup(m, ohlen - sizeof (struct ip) + (ip->ip_hl << 2))) == NULL) { DPRINTF(("ah_new_input(): m_pullup() failed\n")); ahstat.ahs_hdrops++; return NULL; } ip = mtod(m, struct ip *); ah = (struct ah_new *) ((u_int8_t *) ip + (ip->ip_hl << 2)); ohlen += ((ip->ip_hl << 2) - sizeof(struct ip)); } else ah = (struct ah_new *) (ip + 1); if (ah->ah_hl * sizeof(u_int32_t) != AH_HMAC_HASHLEN + AH_HMAC_RPLENGTH) { DPRINTF(("ah_new_input(): bad authenticator length for packet from %x to %x, spi %08x\n", ip->ip_src, ip->ip_dst, ntohl(ah->ah_spi))); ahstat.ahs_badauthl++; m_freem(m); return NULL; } /* Replay window checking */ if (xd->amx_wnd >= 0) { btsx = ntohl(ah->ah_rpl); if ((errc = checkreplaywindow32(btsx, 0, &(xd->amx_rpl), xd->amx_wnd, &(xd->amx_bitmap))) != 0) { switch(errc) { case 1: if (encdebug) log(LOG_ERR, "ah_new_input(): replay counter wrapped for packets from %x to %x, spi %08x\n", ip->ip_src, ip->ip_dst, ntohl(ah->ah_spi)); ahstat.ahs_wrap++; break; case 2: case 3: if (encdebug) log(LOG_WARNING, "ah_new_input(): duplicate packet received, %x->%x spi %08x\n", ip->ip_src, ip->ip_dst, ntohl(ah->ah_spi)); ahstat.ahs_replay++; break; } m_freem(m); return NULL; } } ipo = *ip; ipo.ip_tos = 0; ipo.ip_len += (ip->ip_hl << 2); /* adjusted in ip_intr() */ HTONS(ipo.ip_len); HTONS(ipo.ip_id); ipo.ip_off = 0; ipo.ip_ttl = 0; ipo.ip_sum = 0; bcopy(&(xd->amx_ictx), &ctx, xd->amx_hash->ctxsize); xd->amx_hash->Update(&ctx, (unsigned char *) &ipo, sizeof(struct ip)); /* Options */ if ((ip->ip_hl << 2) > sizeof(struct ip)) for (off = sizeof(struct ip); off < (ip->ip_hl << 2);) { optval = ((u_int8_t *) ip)[off]; switch (optval) { case IPOPT_EOL: xd->amx_hash->Update(&ctx, ipseczeroes, 1); off = ip->ip_hl << 2; break; case IPOPT_NOP: xd->amx_hash->Update(&ctx, ipseczeroes, 1); off++; break; case IPOPT_SECURITY: case 133: case 134: optval = ((u_int8_t *) ip)[off + 1]; xd->amx_hash->Update(&ctx, (u_int8_t *) ip + off, optval); off += optval; break; default: optval = ((u_int8_t *) ip)[off + 1]; xd->amx_hash->Update(&ctx, ipseczeroes, optval); off += optval; break; } } xd->amx_hash->Update(&ctx, (unsigned char *) ah, AH_NEW_FLENGTH - AH_HMAC_HASHLEN); xd->amx_hash->Update(&ctx, ipseczeroes, AH_HMAC_HASHLEN); /* * Code shamelessly stolen from m_copydata */ off = ohlen; len = m->m_pkthdr.len - off; m0 = m; while (off > 0) { if (m0 == 0) panic("ah_new_input(): m_copydata (off)"); if (off < m0->m_len) break; off -= m0->m_len; m0 = m0->m_next; } while (len > 0) { if (m0 == 0) panic("ah_new_input(): m_copydata (copy)"); count = min(m0->m_len - off, len); xd->amx_hash->Update(&ctx, mtod(m0, unsigned char *) + off, count); len -= count; off = 0; m0 = m0->m_next; } xd->amx_hash->Final((unsigned char *) (aho->ah_data), &ctx); bcopy(&(xd->amx_octx), &ctx, xd->amx_hash->ctxsize); xd->amx_hash->Update(&ctx, (unsigned char *) (aho->ah_data), xd->amx_hash->hashsize); xd->amx_hash->Final((unsigned char *) (aho->ah_data), &ctx); if (bcmp(aho->ah_data, ah->ah_data, AH_HMAC_HASHLEN)) { if (encdebug) log(LOG_ALERT, "ah_new_input(): authentication failed for packet from %x to %x, spi %08x\n", ip->ip_src, ip->ip_dst, ntohl(ah->ah_spi)); #ifdef ENCDEBUG if (encdebug) { printf("Received authenticator: "); for (off = 0; off < AH_HMAC_HASHLEN; off++) printf("%02x ", ah->ah_data[off]); printf("\n"); printf("Computed authenticator: "); for (off = 0; off < AH_HMAC_HASHLEN; off++) printf("%02x ", aho->ah_data[off]); printf("\n"); } #endif ahstat.ahs_badauth++; m_freem(m); return NULL; } ipo = *ip; ipo.ip_p = ah->ah_nh; /* Save options */ m_copydata(m, sizeof(struct ip), (ip->ip_hl << 2) - sizeof(struct ip), (caddr_t) buffer); m->m_len -= AH_NEW_FLENGTH; m->m_data += AH_NEW_FLENGTH; m->m_pkthdr.len -= AH_NEW_FLENGTH; ip = mtod(m, struct ip *); *ip = ipo; ip->ip_len = htons(ip->ip_len - AH_NEW_FLENGTH + (ip->ip_hl << 2)); HTONS(ip->ip_id); HTONS(ip->ip_off); ip->ip_sum = 0; /* Copy the options back */ m_copyback(m, sizeof(struct ip), (ip->ip_hl << 2) - sizeof(struct ip), (caddr_t) buffer); ip->ip_sum = in_cksum(m, (ip->ip_hl << 2)); /* Update the counters */ tdb->tdb_cur_packets++; tdb->tdb_cur_bytes += ntohs(ip->ip_len) - (ip->ip_hl << 2); ahstat.ahs_ibytes += ntohs(ip->ip_len) - (ip->ip_hl << 2); /* Notify on expiration */ if (tdb->tdb_flags & TDBF_SOFT_PACKETS) { if (tdb->tdb_cur_packets >= tdb->tdb_soft_packets) { encap_sendnotify(NOTIFY_SOFT_EXPIRE, tdb, NULL); tdb->tdb_flags &= ~TDBF_SOFT_PACKETS; } else if (tdb->tdb_flags & TDBF_SOFT_BYTES) if (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes) { encap_sendnotify(NOTIFY_SOFT_EXPIRE, tdb, NULL); tdb->tdb_flags &= ~TDBF_SOFT_BYTES; } } if (tdb->tdb_flags & TDBF_PACKETS) { if (tdb->tdb_cur_packets >= tdb->tdb_exp_packets) { encap_sendnotify(NOTIFY_HARD_EXPIRE, tdb, NULL); tdb_delete(tdb, 0); } else if (tdb->tdb_flags & TDBF_BYTES) if (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes) { encap_sendnotify(NOTIFY_HARD_EXPIRE, tdb, NULL); tdb_delete(tdb, 0); } } return m; } int ah_new_output(struct mbuf *m, struct sockaddr_encap *gw, struct tdb *tdb, struct mbuf **mp) { struct ah_new_xdata *xd; struct ip *ip, ipo; struct ah_new aho, *ah; register int len, off, count; register struct mbuf *m0; union { MD5_CTX md5ctx; SHA1_CTX sha1ctx; RMD160_CTX rmd160ctx; } ctx; int ilen, ohlen; u_int8_t optval; u_char buffer[AH_ALEN_MAX], opts[40]; ahstat.ahs_output++; m = m_pullup(m, sizeof(struct ip)); if (m == NULL) { DPRINTF(("ah_new_output(): m_pullup() failed, SA %x/%08x\n", tdb->tdb_dst, ntohl(tdb->tdb_spi))); return ENOBUFS; } ip = mtod(m, struct ip *); xd = (struct ah_new_xdata *) tdb->tdb_xdata; if ((ip->ip_hl << 2) > sizeof(struct ip)) { if ((m = m_pullup(m, ip->ip_hl << 2)) == NULL) { DPRINTF(("ah_new_output(): m_pullup() failed, SA &x/%08x\n", tdb->tdb_dst, ntohl(tdb->tdb_spi))); ahstat.ahs_hdrops++; return NULL; } ip = mtod(m, struct ip *); } /* Save options */ m_copydata(m, sizeof(struct ip), (ip->ip_hl << 2) - sizeof(struct ip), (caddr_t) opts); DPRINTF(("ah_new_output(): using hash algorithm %s\n", xd->amx_hash->name)); ilen = ntohs(ip->ip_len); ohlen = AH_NEW_FLENGTH; ipo.ip_v = IPVERSION; ipo.ip_hl = ip->ip_hl; ipo.ip_tos = 0; ipo.ip_len = htons(ohlen + ilen); ipo.ip_id = ip->ip_id; ipo.ip_off = 0; ipo.ip_ttl = 0; ipo.ip_p = IPPROTO_AH; ipo.ip_sum = 0; ipo.ip_src = ip->ip_src; ipo.ip_dst = ip->ip_dst; bzero(&aho, sizeof(struct ah_new)); aho.ah_nh = ip->ip_p; aho.ah_hl = ((AH_HMAC_RPLENGTH + AH_HMAC_HASHLEN) >> 2); aho.ah_rv = 0; aho.ah_spi = tdb->tdb_spi; if (xd->amx_rpl == 0) { if (encdebug) log(LOG_ALERT, "ah_new_output(): SA %x/%0x8 should have expired\n", tdb->tdb_dst, ntohl(tdb->tdb_spi)); m_freem(m); ahstat.ahs_wrap++; return NULL; } aho.ah_rpl = htonl(xd->amx_rpl++); bcopy((caddr_t)&(xd->amx_ictx), (caddr_t)&ctx, xd->amx_hash->ctxsize); xd->amx_hash->Update(&ctx, (unsigned char *) &ipo, sizeof(struct ip)); /* Options */ if ((ip->ip_hl << 2) > sizeof(struct ip)) for (off = sizeof(struct ip); off < (ip->ip_hl << 2);) { optval = ((u_int8_t *) ip)[off]; switch (optval) { case IPOPT_EOL: xd->amx_hash->Update(&ctx, ipseczeroes, 1); off = ip->ip_hl << 2; break; case IPOPT_NOP: xd->amx_hash->Update(&ctx, ipseczeroes, 1); off++; break; case IPOPT_SECURITY: case 133: case 134: optval = ((u_int8_t *) ip)[off + 1]; xd->amx_hash->Update(&ctx, (u_int8_t *) ip + off, optval); off += optval; break; default: optval = ((u_int8_t *) ip)[off + 1]; xd->amx_hash->Update(&ctx, ipseczeroes, optval); off += optval; break; } } xd->amx_hash->Update(&ctx, (unsigned char *) &aho, AH_NEW_FLENGTH); off = ip->ip_hl << 2; /* * Code shamelessly stolen from m_copydata */ len = m->m_pkthdr.len - off; m0 = m; while (len > 0) { if (m0 == 0) panic("ah_new_output(): m_copydata"); count = min(m0->m_len - off, len); xd->amx_hash->Update(&ctx, mtod(m0, unsigned char *) + off, count); len -= count; off = 0; m0 = m0->m_next; } ipo.ip_tos = ip->ip_tos; ipo.ip_id = ip->ip_id; ipo.ip_off = ip->ip_off; ipo.ip_ttl = ip->ip_ttl; /* ipo.ip_len = ntohs(ipo.ip_len); */ M_PREPEND(m, ohlen, M_DONTWAIT); if (m == NULL) { DPRINTF(("ah_new_output(): M_PREPEND() failed for packet from %x to %x, spi %08x\n", ipo.ip_src, ipo.ip_dst, ntohl(tdb->tdb_spi))); return ENOBUFS; } m = m_pullup(m, ohlen + (ipo.ip_hl << 2)); if (m == NULL) { DPRINTF(("ah_new_output(): m_pullup() failed for packet from %x to %x, spi %08x\n", ipo.ip_src, ipo.ip_dst, ntohl(tdb->tdb_spi))); return ENOBUFS; } ip = mtod(m, struct ip *); ah = (struct ah_new *) ((u_int8_t *) ip + (ipo.ip_hl << 2)); *ip = ipo; ah->ah_nh = aho.ah_nh; ah->ah_hl = aho.ah_hl; ah->ah_rv = aho.ah_rv; ah->ah_spi = aho.ah_spi; ah->ah_rpl = aho.ah_rpl; xd->amx_hash->Final(buffer, &ctx); bcopy(&(xd->amx_octx), &ctx, xd->amx_hash->ctxsize); xd->amx_hash->Update(&ctx, buffer, xd->amx_hash->hashsize); xd->amx_hash->Final(buffer, &ctx); /* Restore the options */ m_copyback(m, sizeof(struct ip), (ip->ip_hl << 2) - sizeof(struct ip), (caddr_t) opts); /* Copy the authenticator */ bcopy(buffer, ah->ah_data, AH_HMAC_HASHLEN); *mp = m; /* Update the counters */ tdb->tdb_cur_packets++; tdb->tdb_cur_bytes += ntohs(ip->ip_len) - (ip->ip_hl << 2) - AH_NEW_FLENGTH; ahstat.ahs_obytes += ntohs(ip->ip_len) - (ip->ip_hl << 2) - AH_NEW_FLENGTH; /* Notify on expiration */ if (tdb->tdb_flags & TDBF_SOFT_PACKETS) { if (tdb->tdb_cur_packets >= tdb->tdb_soft_packets) { encap_sendnotify(NOTIFY_SOFT_EXPIRE, tdb, NULL); tdb->tdb_flags &= ~TDBF_SOFT_PACKETS; } else if (tdb->tdb_flags & TDBF_SOFT_BYTES) if (tdb->tdb_cur_bytes >= tdb->tdb_soft_bytes) { encap_sendnotify(NOTIFY_SOFT_EXPIRE, tdb, NULL); tdb->tdb_flags &= ~TDBF_SOFT_BYTES; } } if (tdb->tdb_flags & TDBF_PACKETS) { if (tdb->tdb_cur_packets >= tdb->tdb_exp_packets) { encap_sendnotify(NOTIFY_HARD_EXPIRE, tdb, NULL); tdb_delete(tdb, 0); } else if (tdb->tdb_flags & TDBF_BYTES) if (tdb->tdb_cur_bytes >= tdb->tdb_exp_bytes) { encap_sendnotify(NOTIFY_HARD_EXPIRE, tdb, NULL); tdb_delete(tdb, 0); } } return 0; }