diff options
Diffstat (limited to 'sys/netinet6/esp_output.c')
-rw-r--r-- | sys/netinet6/esp_output.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/sys/netinet6/esp_output.c b/sys/netinet6/esp_output.c new file mode 100644 index 0000000..2386bfd --- /dev/null +++ b/sys/netinet6/esp_output.c @@ -0,0 +1,683 @@ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#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/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> +#include <netinet/in_pcb.h> + +#ifdef INET6 +#include <netinet6/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#include <netinet6/ah6.h> +#endif +#ifdef IPSEC_ESP +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> +#ifdef IPSEC_DEBUG +#include <netkey/key_debug.h> +#else +#define KEYDEBUG(lev,arg) +#endif + +#include <net/net_osdep.h> + +static int esp_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *, int)); + +/* + * compute ESP header size. + */ +size_t +esp_hdrsiz(isr) + struct ipsecrequest *isr; +{ + struct secasvar *sav; + struct esp_algorithm *algo; + size_t ivlen; + size_t authlen; + size_t hdrsiz; + + /* sanity check */ + if (isr == NULL) + panic("esp_hdrsiz: NULL was passed.\n"); + + sav = isr->sav; + + if (isr->saidx.proto != IPPROTO_ESP) + panic("unsupported mode passed to esp_hdrsiz"); + + if (sav == NULL) + goto contrive; + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) + goto contrive; + + /* we need transport mode ESP. */ + algo = &esp_algorithms[sav->alg_enc]; + if (!algo) + goto contrive; + ivlen = sav->ivlen; + if (ivlen < 0) + goto contrive; + + /* + * XXX + * right now we don't calcurate the padding size. simply + * treat the padding size as constant, for simplicity. + * + * XXX variable size padding support + */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + hdrsiz = sizeof(struct esp) + ivlen + 9; + } else { + /* RFC 2406 */ + if (sav->replay && sav->alg_auth && sav->key_auth) + authlen = (*ah_algorithms[sav->alg_auth].sumsiz)(sav); + else + authlen = 0; + hdrsiz = sizeof(struct newesp) + ivlen + 9 + authlen; + } + + return hdrsiz; + + contrive: + /* + * ASSUMING: + * sizeof(struct newesp) > sizeof(struct esp). + * 8 = ivlen for CBC mode (RFC2451). + * 9 = (maximum padding length without random Padding length) + * + (Pad Length field) + (Next Header field). + * 16 = maximum ICV we supported. + */ + return sizeof(struct newesp) + 8 + 9 + 16; +} + +/* + * Modify the packet so that the payload is encrypted. + * The mbuf (m) must start with IPv4 or IPv6 header. + * On failure, free the given mbuf and return NULL. + * + * on invocation: + * m nexthdrp md + * v v v + * IP ......... payload + * during the encryption: + * m nexthdrp mprev md + * v v v v + * IP ............... esp iv payload pad padlen nxthdr + * <--><-><------><---------------> + * esplen plen extendsiz + * ivlen + * <-----> esphlen + * <-> hlen + * <-----------------> espoff + */ +static int +esp_output(m, nexthdrp, md, isr, af) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; + int af; +{ + struct mbuf *n; + struct mbuf *mprev; + struct esp *esp; + struct esptail *esptail; + struct secasvar *sav = isr->sav; + struct esp_algorithm *algo; + u_int32_t spi; + u_int8_t nxt = 0; + size_t plen; /*payload length to be encrypted*/ + size_t espoff; + int ivlen; + int afnumber; + size_t extendsiz; + int error = 0; + + switch (af) { +#ifdef INET + case AF_INET: + afnumber = 4; + break; +#endif +#ifdef INET6 + case AF_INET6: + afnumber = 6; + break; +#endif + default: + printf("esp_output: unsupported af %d\n", af); + return 0; /* no change at all */ + } + + /* some sanity check */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) { + switch (af) { +#ifdef INET + case AF_INET: + { + struct ip *ip; + + ip = mtod(m, struct ip *); + printf("esp4_output: internal error: " + "sav->replay is null: " + "%x->%x, SPI=%u\n", + (u_int32_t)ntohl(ip->ip_src.s_addr), + (u_int32_t)ntohl(ip->ip_dst.s_addr), + (u_int32_t)ntohl(sav->spi)); + ipsecstat.out_inval++; + m_freem(m); + return EINVAL; + } +#endif /*INET*/ +#ifdef INET6 + case AF_INET6: + { + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + printf("esp6_output: internal error: " + "sav->replay is null: SPI=%u\n", + (u_int32_t)ntohl(sav->spi)); + ipsec6stat.out_inval++; + m_freem(m); + return EINVAL; + } +#endif /*INET6*/ + } + } + + algo = &esp_algorithms[sav->alg_enc]; /*XXX*/ + spi = sav->spi; + ivlen = sav->ivlen; + /* should be okey */ + if (ivlen < 0) { + panic("invalid ivlen"); + } + + { + /* + * insert ESP header. + * XXX inserts ESP header right after IPv4 header. should + * chase the header chain. + * XXX sequential number + */ +#ifdef INET + struct ip *ip = NULL; +#endif +#ifdef INET6 + struct ip6_hdr *ip6 = NULL; +#endif + size_t esplen; /*sizeof(struct esp/newesp)*/ + size_t esphlen; /*sizeof(struct esp/newesp) + ivlen*/ + size_t hlen = 0; /*ip header len*/ + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + esplen = sizeof(struct esp); + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) + esplen = sizeof(struct esp); + else + esplen = sizeof(struct newesp); + } + esphlen = esplen + ivlen; + + for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) + ; + if (mprev == NULL || mprev->m_next != md) { + printf("esp%d_output: md is not in chain\n", afnumber); + m_freem(m); + return EINVAL; + } + + plen = 0; + for (n = md; n; n = n->m_next) + plen += n->m_len; + + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + break; +#endif +#ifdef INET6 + case AF_INET6: + ip6 = mtod(m, struct ip6_hdr *); + hlen = sizeof(*ip6); + break; +#endif + } + + /* make the packet over-writable */ + mprev->m_next = NULL; + if ((md = ipsec_copypkt(md)) == NULL) { + m_freem(m); + error = ENOBUFS; + goto fail; + } + mprev->m_next = md; + + espoff = m->m_pkthdr.len - plen; + + /* + * grow the mbuf to accomodate ESP header. + * before: IP ... payload + * after: IP ... ESP IV payload + */ + if (M_LEADINGSPACE(md) < esphlen) { + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + m_freem(m); + error = ENOBUFS; + goto fail; + } + n->m_len = esphlen; + mprev->m_next = n; + n->m_next = md; + m->m_pkthdr.len += esphlen; + esp = mtod(n, struct esp *); + } else { + md->m_len += esphlen; + md->m_data -= esphlen; + m->m_pkthdr.len += esphlen; + esp = mtod(md, struct esp *); + } + + nxt = *nexthdrp; + *nexthdrp = IPPROTO_ESP; + switch (af) { +#ifdef INET + case AF_INET: + if (esphlen < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + esphlen); + else { + printf("IPv4 ESP output: size exceeds limit\n"); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + + /* initialize esp header. */ + esp->esp_spi = spi; + if ((sav->flags & SADB_X_EXT_OLD) == 0) { + struct newesp *nesp; + nesp = (struct newesp *)esp; + sav->replay->count++; + /* + * XXX sequence number must not be cycled, if the SA is + * installed by IKE daemon. + */ + nesp->esp_seq = htonl(sav->replay->count); + } + + { + /* + * find the last mbuf. make some room for ESP trailer. + * XXX new-esp authentication data + */ +#ifdef INET + struct ip *ip = NULL; +#endif + size_t padbound; + + if (algo->padbound) + padbound = algo->padbound; + else + padbound = 4; + /* ESP packet, including nxthdr field, must be length of 4n */ + if (padbound < 4) + padbound = 4; + + extendsiz = padbound - (plen % padbound); + if (extendsiz == 1) + extendsiz = padbound + 1; + + n = m; + while (n->m_next) + n = n->m_next; + + /* + * if M_EXT, the external part may be shared among + * two consequtive TCP packets. + */ + if (!(n->m_flags & M_EXT) && extendsiz < M_TRAILINGSPACE(n)) { + switch (sav->flags & SADB_X_EXT_PMASK) { + case SADB_X_EXT_PRAND: + break; + case SADB_X_EXT_PZERO: + bzero((caddr_t)(mtod(n, u_int8_t *) + n->m_len), + extendsiz); + break; + case SADB_X_EXT_PSEQ: + { + int i; + u_char *p; + p = mtod(n, u_char *) + n->m_len; + for (i = 0; i < extendsiz; i++) + p[i] = i + 1; + break; + } + } + n->m_len += extendsiz; + m->m_pkthdr.len += extendsiz; + } else { + struct mbuf *nn; + + MGET(nn, M_DONTWAIT, MT_DATA); + if (!nn) { + printf("esp%d_output: can't alloc mbuf", afnumber); + m_freem(m); + error = ENOBUFS; + goto fail; + } + nn->m_len = extendsiz; + switch (sav->flags & SADB_X_EXT_PMASK) { + case SADB_X_EXT_PRAND: + break; + case SADB_X_EXT_PZERO: + bzero(mtod(nn, caddr_t), extendsiz); + break; + case SADB_X_EXT_PSEQ: + { + int i; + u_char *p; + p = mtod(nn, u_char *); + for (i = 0; i < extendsiz; i++) + p[i] = i + 1; + break; + } + } + nn->m_next = NULL; + n->m_next = nn; + n = nn; + m->m_pkthdr.len += extendsiz; + } + + /* initialize esp trailer. */ + esptail = (struct esptail *) + (mtod(n, u_int8_t *) + n->m_len - sizeof(struct esptail)); + esptail->esp_nxt = nxt; + esptail->esp_padlen = extendsiz - 2; + + /* modify IP header (for ESP header part only) */ + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); + if (extendsiz < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + extendsiz); + else { + printf("IPv4 ESP output: size exceeds limit\n"); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + + /* + * encrypt the packet, based on security association + * and the algorithm specified. + */ + if (!algo->encrypt) + panic("internal error: no encrypt function"); + if ((*algo->encrypt)(m, espoff, plen + extendsiz, sav, algo, ivlen)) { + printf("packet encryption failure\n"); + m_freem(m); + switch (af) { +#ifdef INET + case AF_INET: + ipsecstat.out_inval++; + break; +#endif +#ifdef INET6 + case AF_INET6: + ipsec6stat.out_inval++; + break; +#endif + } + error = EINVAL; + goto fail; + } + + /* + * calculate ICV if required. + */ + if (!sav->replay) + goto noantireplay; + if (!sav->key_auth) + goto noantireplay; + if (!sav->alg_auth) + goto noantireplay; + { + u_char authbuf[AH_MAXSUMSIZE]; + struct mbuf *n; + u_char *p; + size_t siz; + struct ip *ip; + + siz = (((*ah_algorithms[sav->alg_auth].sumsiz)(sav) + 3) & ~(4 - 1)); + if (AH_MAXSUMSIZE < siz) + panic("assertion failed for AH_MAXSUMSIZE"); + + if (esp_auth(m, espoff, m->m_pkthdr.len - espoff, sav, authbuf)) + goto noantireplay; + + n = m; + while (n->m_next) + n = n->m_next; + + if (!(n->m_flags & M_EXT) && siz < M_TRAILINGSPACE(n)) { /*XXX*/ + n->m_len += siz; + m->m_pkthdr.len += siz; + p = mtod(n, u_char *) + n->m_len - siz; + } else { + struct mbuf *nn; + + MGET(nn, M_DONTWAIT, MT_DATA); + if (!nn) { + printf("can't alloc mbuf in esp%d_output", afnumber); + m_freem(m); + error = ENOBUFS; + goto fail; + } + nn->m_len = siz; + nn->m_next = NULL; + n->m_next = nn; + n = nn; + m->m_pkthdr.len += siz; + p = mtod(nn, u_char *); + } + bcopy(authbuf, p, siz); + + /* modify IP header (for ESP header part only) */ + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); + if (siz < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + siz); + else { + printf("IPv4 ESP output: size exceeds limit\n"); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + +noantireplay: + if (!m) + printf("NULL mbuf after encryption in esp%d_output", afnumber); + else { + switch (af) { +#ifdef INET + case AF_INET: + ipsecstat.out_success++; + break; +#endif +#ifdef INET6 + case AF_INET6: + ipsec6stat.out_success++; + break; +#endif + } + } + switch (af) { +#ifdef INET + case AF_INET: + ipsecstat.out_esphist[sav->alg_enc]++; + break; +#endif +#ifdef INET6 + case AF_INET6: + ipsec6stat.out_esphist[sav->alg_enc]++; + break; +#endif + } + key_sa_recordxfer(sav, m); + return 0; + +fail: +#if 1 + return error; +#else + panic("something bad in esp_output"); +#endif +} + +#ifdef INET +int +esp4_output(m, isr) + struct mbuf *m; + struct ipsecrequest *isr; +{ + struct ip *ip; + if (m->m_len < sizeof(struct ip)) { + printf("esp4_output: first mbuf too short\n"); + m_freem(m); + return NULL; + } + ip = mtod(m, struct ip *); + /* XXX assumes that m->m_next points to payload */ + return esp_output(m, &ip->ip_p, m->m_next, isr, AF_INET); +} +#endif /*INET*/ + +#ifdef INET6 +int +esp6_output(m, nexthdrp, md, isr) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; +{ + if (m->m_len < sizeof(struct ip6_hdr)) { + printf("esp6_output: first mbuf too short\n"); + m_freem(m); + return NULL; + } + return esp_output(m, nexthdrp, md, isr, AF_INET6); +} +#endif /*INET6*/ |