1/*	$FreeBSD: src/sys/netinet6/ah_output.c,v 1.1.2.3 2001/07/03 11:01:49 ume Exp $	*/
2/*	$KAME: ah_output.c,v 1.30 2001/02/21 00:50:53 itojun Exp $	*/
3
4/*
5 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the project nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33/*
34 * RFC1826/2402 authentication header.
35 */
36
37#define _IP_VHL
38
39#include <sys/param.h>
40#include <sys/systm.h>
41#include <sys/malloc.h>
42#include <sys/mbuf.h>
43#include <sys/domain.h>
44#include <sys/protosw.h>
45#include <sys/socket.h>
46#include <sys/socketvar.h>
47#include <sys/errno.h>
48#include <sys/time.h>
49#include <sys/kernel.h>
50#include <sys/syslog.h>
51
52#include <net/if.h>
53#include <net/route.h>
54
55#include <netinet/in.h>
56
57#include <netinet/in_systm.h>
58#include <netinet/ip.h>
59#include <netinet/in_var.h>
60
61#if INET6
62#include <netinet/ip6.h>
63#include <netinet6/ip6_var.h>
64#include <netinet/icmp6.h>
65#endif
66
67#include <netinet6/ipsec.h>
68#if INET6
69#include <netinet6/ipsec6.h>
70#endif
71#include <netinet6/ah.h>
72#if INET6
73#include <netinet6/ah6.h>
74#endif
75#include <netkey/key.h>
76#include <netkey/keydb.h>
77
78#include <net/net_osdep.h>
79
80#if INET
81static struct in_addr *ah4_finaldst(struct mbuf *);
82#endif
83
84extern lck_mtx_t *sadb_mutex;
85
86/*
87 * compute AH header size.
88 * transport mode only.  for tunnel mode, we should implement
89 * virtual interface, and control MTU/MSS by the interface MTU.
90 */
91size_t
92ah_hdrsiz(isr)
93	struct ipsecrequest *isr;
94{
95
96	/* sanity check */
97	if (isr == NULL)
98		panic("ah_hdrsiz: NULL was passed.\n");
99
100	if (isr->saidx.proto != IPPROTO_AH)
101		panic("unsupported mode passed to ah_hdrsiz");
102
103#if 0
104	{
105
106		lck_mtx_lock(sadb_mutex);
107		const struct ah_algorithm *algo;
108		size_t hdrsiz;
109
110		/*%%%%% this needs to change - no sav in ipsecrequest any more */
111		if (isr->sav == NULL)
112			goto estimate;
113		if (isr->sav->state != SADB_SASTATE_MATURE
114		 && isr->sav->state != SADB_SASTATE_DYING)
115			goto estimate;
116
117		/* we need transport mode AH. */
118		algo = ah_algorithm_lookup(isr->sav->alg_auth);
119		if (!algo)
120			goto estimate;
121
122		/*
123		 * XXX
124		 * right now we don't calcurate the padding size.  simply
125		 * treat the padding size as constant, for simplicity.
126		 *
127		 * XXX variable size padding support
128		 */
129		hdrsiz = (((*algo->sumsiz)(isr->sav) + 3) & ~(4 - 1));
130		if (isr->sav->flags & SADB_X_EXT_OLD)
131			hdrsiz += sizeof(struct ah);
132		else
133			hdrsiz += sizeof(struct newah);
134
135		lck_mtx_unlock(sadb_mutex);
136		return hdrsiz;
137	}
138
139estimate:
140#endif
141
142    //lck_mtx_unlock(sadb_mutex);
143	/* ASSUMING:
144	 *	sizeof(struct newah) > sizeof(struct ah).
145	 *	16 = (16 + 3) & ~(4 - 1).
146	 */
147	return sizeof(struct newah) + 16;
148}
149
150#if INET
151/*
152 * Modify the packet so that it includes the authentication data.
153 * The mbuf passed must start with IPv4 header.
154 *
155 * assumes that the first mbuf contains IPv4 header + option only.
156 * the function does not modify m.
157 */
158int
159ah4_output(m, sav)
160	struct mbuf *m;
161	struct secasvar *sav;
162{
163	const struct ah_algorithm *algo;
164	u_int32_t spi;
165	u_char *ahdrpos;
166	u_char *ahsumpos = NULL;
167	size_t hlen = 0;	/*IP header+option in bytes*/
168	size_t plen = 0;	/*AH payload size in bytes*/
169	size_t ahlen = 0;	/*plen + sizeof(ah)*/
170	struct ip *ip;
171	struct in_addr dst = { 0 };
172	struct in_addr *finaldst;
173	int error;
174
175	/* sanity checks */
176	if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
177		ip = mtod(m, struct ip *);
178		ipseclog((LOG_DEBUG, "ah4_output: internal error: "
179			"sav->replay is null: %x->%x, SPI=%u\n",
180			(u_int32_t)ntohl(ip->ip_src.s_addr),
181			(u_int32_t)ntohl(ip->ip_dst.s_addr),
182			(u_int32_t)ntohl(sav->spi)));
183		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
184		m_freem(m);
185		return EINVAL;
186	}
187
188	algo = ah_algorithm_lookup(sav->alg_auth);
189	if (!algo) {
190		ipseclog((LOG_ERR, "ah4_output: unsupported algorithm: "
191		    "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
192		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
193		m_freem(m);
194		return EINVAL;
195	}
196	spi = sav->spi;
197
198	/*
199	 * determine the size to grow.
200	 */
201	if (sav->flags & SADB_X_EXT_OLD) {
202		/* RFC 1826 */
203		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
204		ahlen = plen + sizeof(struct ah);
205	} else {
206		/* RFC 2402 */
207		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
208		ahlen = plen + sizeof(struct newah);
209	}
210
211	/*
212	 * grow the mbuf to accomodate AH.
213	 */
214	ip = mtod(m, struct ip *);
215#ifdef _IP_VHL
216	hlen = IP_VHL_HL(ip->ip_vhl) << 2;
217#else
218	hlen = ip->ip_hl << 2;
219#endif
220
221	if (m->m_len != hlen)
222		panic("ah4_output: assumption failed (first mbuf length)");
223	if (M_LEADINGSPACE(m->m_next) < ahlen) {
224		struct mbuf *n;
225		MGET(n, M_DONTWAIT, MT_DATA);
226		if (!n) {
227			ipseclog((LOG_DEBUG, "ENOBUFS in ah4_output %d\n",
228			    __LINE__));
229			m_freem(m);
230			return ENOBUFS;
231		}
232		n->m_len = ahlen;
233		n->m_next = m->m_next;
234		m->m_next = n;
235		m->m_pkthdr.len += ahlen;
236		ahdrpos = mtod(n, u_char *);
237	} else {
238		m->m_next->m_len += ahlen;
239		m->m_next->m_data -= ahlen;
240		m->m_pkthdr.len += ahlen;
241		ahdrpos = mtod(m->m_next, u_char *);
242	}
243
244	ip = mtod(m, struct ip *);	/*just to be sure*/
245
246	/*
247	 * initialize AH.
248	 */
249	if (sav->flags & SADB_X_EXT_OLD) {
250		struct ah *ahdr;
251
252		ahdr = (struct ah *)ahdrpos;
253		ahsumpos = (u_char *)(ahdr + 1);
254		ahdr->ah_len = plen >> 2;
255		ahdr->ah_nxt = ip->ip_p;
256		ahdr->ah_reserve = htons(0);
257		ahdr->ah_spi = spi;
258		bzero(ahdr + 1, plen);
259	} else {
260		struct newah *ahdr;
261
262		ahdr = (struct newah *)ahdrpos;
263		ahsumpos = (u_char *)(ahdr + 1);
264		ahdr->ah_len = (plen >> 2) + 1;	/* plus one for seq# */
265		ahdr->ah_nxt = ip->ip_p;
266		ahdr->ah_reserve = htons(0);
267		ahdr->ah_spi = spi;
268		if (sav->replay->count == ~0) {
269			if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
270				/* XXX Is it noisy ? */
271				ipseclog((LOG_WARNING,
272				    "replay counter overflowed. %s\n",
273				    ipsec_logsastr(sav)));
274				IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
275				m_freem(m);
276				return EINVAL;
277			}
278		}
279		lck_mtx_lock(sadb_mutex);
280		sav->replay->count++;
281		lck_mtx_unlock(sadb_mutex);
282		/*
283		 * XXX sequence number must not be cycled, if the SA is
284		 * installed by IKE daemon.
285		 */
286		ahdr->ah_seq = htonl(sav->replay->count);
287		bzero(ahdr + 1, plen);
288	}
289
290	/*
291	 * modify IPv4 header.
292	 */
293	ip->ip_p = IPPROTO_AH;
294	if (ahlen < (IP_MAXPACKET - ntohs(ip->ip_len)))
295		ip->ip_len = htons(ntohs(ip->ip_len) + ahlen);
296	else {
297		ipseclog((LOG_ERR, "IPv4 AH output: size exceeds limit\n"));
298		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
299		m_freem(m);
300		return EMSGSIZE;
301	}
302
303	/*
304	 * If there is source routing option, update destination field in
305	 * the IPv4 header to the final destination.
306	 * Note that we do not need to update source routing option itself
307	 * (as done in IPv4 AH processing -- see ip6_output()), since
308	 * source routing option is not part of the ICV computation.
309	 */
310	finaldst = ah4_finaldst(m);
311	if (finaldst) {
312		dst.s_addr = ip->ip_dst.s_addr;
313		ip->ip_dst.s_addr = finaldst->s_addr;
314	}
315
316	/*
317	 * calcurate the checksum, based on security association
318	 * and the algorithm specified.
319	 */
320	error = ah4_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
321	if (error) {
322		ipseclog((LOG_ERR,
323		    "error after ah4_calccksum, called from ah4_output"));
324		m_freem(m);
325		m = NULL;
326		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
327		return error;
328	}
329
330	if (finaldst) {
331		ip = mtod(m, struct ip *);	/*just to make sure*/
332		ip->ip_dst.s_addr = dst.s_addr;
333	}
334	lck_mtx_lock(sadb_stat_mutex);
335	ipsecstat.out_success++;
336	ipsecstat.out_ahhist[sav->alg_auth]++;
337	lck_mtx_unlock(sadb_stat_mutex);
338	key_sa_recordxfer(sav, m);
339
340	return 0;
341}
342#endif
343
344/* Calculate AH length */
345int
346ah_hdrlen(sav)
347	struct secasvar *sav;
348{
349	const struct ah_algorithm *algo;
350	int plen, ahlen;
351
352	algo = ah_algorithm_lookup(sav->alg_auth);
353	if (!algo)
354		return 0;
355	if (sav->flags & SADB_X_EXT_OLD) {
356		/* RFC 1826 */
357		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1);	/*XXX pad to 8byte?*/
358		ahlen = plen + sizeof(struct ah);
359	} else {
360		/* RFC 2402 */
361		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1);	/*XXX pad to 8byte?*/
362		ahlen = plen + sizeof(struct newah);
363	}
364
365	return(ahlen);
366}
367
368#if INET6
369/*
370 * Fill in the Authentication Header and calculate checksum.
371 */
372int
373ah6_output(m, nexthdrp, md, sav)
374	struct mbuf *m;
375	u_char *nexthdrp;
376	struct mbuf *md;
377	struct secasvar *sav;
378{
379	struct mbuf *mprev;
380	struct mbuf *mah;
381	const struct ah_algorithm *algo;
382	u_int32_t spi;
383	u_char *ahsumpos = NULL;
384	size_t plen;	/*AH payload size in bytes*/
385	int error = 0;
386	int ahlen;
387	struct ip6_hdr *ip6;
388
389	if (m->m_len < sizeof(struct ip6_hdr)) {
390		ipseclog((LOG_DEBUG, "ah6_output: first mbuf too short\n"));
391		m_freem(m);
392		return EINVAL;
393	}
394
395	ahlen = ah_hdrlen(sav);
396	if (ahlen == 0)
397		return 0;
398
399	for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next)
400		;
401	if (!mprev || mprev->m_next != md) {
402		ipseclog((LOG_DEBUG, "ah6_output: md is not in chain\n"));
403		m_freem(m);
404		return EINVAL;
405	}
406
407	MGET(mah, M_DONTWAIT, MT_DATA);
408	if (!mah) {
409		m_freem(m);
410		return ENOBUFS;
411	}
412	if (ahlen > MLEN) {
413		MCLGET(mah, M_DONTWAIT);
414		if ((mah->m_flags & M_EXT) == 0) {
415			m_free(mah);
416			m_freem(m);
417			return ENOBUFS;
418		}
419	}
420	mah->m_len = ahlen;
421	mah->m_next = md;
422	mprev->m_next = mah;
423	m->m_pkthdr.len += ahlen;
424
425	/* fix plen */
426	if (m->m_pkthdr.len - sizeof(struct ip6_hdr) > IPV6_MAXPACKET) {
427		ipseclog((LOG_ERR,
428		    "ip6_output: AH with IPv6 jumbogram is not supported\n"));
429		m_freem(m);
430		return EINVAL;
431	}
432	ip6 = mtod(m, struct ip6_hdr *);
433	ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr));
434
435	if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
436		ipseclog((LOG_DEBUG, "ah6_output: internal error: "
437			  "sav->replay is null: SPI=%u\n",
438			  (u_int32_t)ntohl(sav->spi)));
439		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
440		m_freem(m);
441		return EINVAL;
442	}
443
444	algo = ah_algorithm_lookup(sav->alg_auth);
445	if (!algo) {
446		ipseclog((LOG_ERR, "ah6_output: unsupported algorithm: "
447		    "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
448		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
449		m_freem(m);
450		return EINVAL;
451	}
452	spi = sav->spi;
453
454	/*
455	 * initialize AH.
456	 */
457	if (sav->flags & SADB_X_EXT_OLD) {
458		struct ah *ahdr = mtod(mah, struct ah *);
459
460		plen = mah->m_len - sizeof(struct ah);
461		ahsumpos = (u_char *)(ahdr + 1);
462		ahdr->ah_nxt = *nexthdrp;
463		*nexthdrp = IPPROTO_AH;
464		ahdr->ah_len = plen >> 2;
465		ahdr->ah_reserve = htons(0);
466		ahdr->ah_spi = spi;
467		bzero(ahdr + 1, plen);
468	} else {
469		struct newah *ahdr = mtod(mah, struct newah *);
470
471		plen = mah->m_len - sizeof(struct newah);
472		ahsumpos = (u_char *)(ahdr + 1);
473		ahdr->ah_nxt = *nexthdrp;
474		*nexthdrp = IPPROTO_AH;
475		ahdr->ah_len = (plen >> 2) + 1;	/* plus one for seq# */
476		ahdr->ah_reserve = htons(0);
477		ahdr->ah_spi = spi;
478		if (sav->replay->count == ~0) {
479			if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
480				/* XXX Is it noisy ? */
481				ipseclog((LOG_WARNING,
482				     "replay counter overflowed. %s\n",
483				    ipsec_logsastr(sav)));
484				IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
485				m_freem(m);
486				return EINVAL;
487			}
488		}
489		lck_mtx_lock(sadb_mutex);
490		sav->replay->count++;
491		lck_mtx_unlock(sadb_mutex);
492		/*
493		 * XXX sequence number must not be cycled, if the SA is
494		 * installed by IKE daemon.
495		 */
496		ahdr->ah_seq = htonl(sav->replay->count);
497		bzero(ahdr + 1, plen);
498	}
499
500	/*
501	 * calcurate the checksum, based on security association
502	 * and the algorithm specified.
503	 */
504	error = ah6_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
505	if (error) {
506		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
507		m_freem(m);
508	} else {
509		IPSEC_STAT_INCREMENT(ipsec6stat.out_success);
510		key_sa_recordxfer(sav, m);
511	}
512	IPSEC_STAT_INCREMENT(ipsec6stat.out_ahhist[sav->alg_auth]);
513
514	return(error);
515}
516#endif
517
518#if INET
519/*
520 * Find the final destination if there is loose/strict source routing option.
521 * Returns NULL if there's no source routing options.
522 * Returns NULL on errors too.
523 * Note that this function will return a pointer INTO the given parameter,
524 * struct mbuf *m.
525 * The mbuf must be pulled up toward, at least, ip option part.
526 */
527static struct in_addr *
528ah4_finaldst(m)
529	struct mbuf *m;
530{
531	struct ip *ip;
532	int optlen;
533	u_char *q;
534	int i;
535	int hlen;
536
537	if (!m)
538		panic("ah4_finaldst: m == NULL");
539	ip = mtod(m, struct ip *);
540#ifdef _IP_VHL
541	hlen = IP_VHL_HL(ip->ip_vhl) << 2;
542#else
543	hlen = ip->ip_hl << 2;
544#endif
545
546	if (m->m_len < hlen) {
547		ipseclog((LOG_DEBUG,
548		    "ah4_finaldst: parameter mbuf wrong (not pulled up)\n"));
549		return NULL;
550	}
551
552	if (hlen == sizeof(struct ip))
553		return NULL;
554
555	optlen = hlen - sizeof(struct ip);
556	if (optlen < 0) {
557		ipseclog((LOG_DEBUG, "ah4_finaldst: wrong optlen %d\n",
558		    optlen));
559		return NULL;
560	}
561
562	q = (u_char *)(ip + 1);
563	i = 0;
564	while (i < optlen) {
565		if (i + IPOPT_OPTVAL >= optlen)
566			return NULL;
567		if (q[i + IPOPT_OPTVAL] == IPOPT_EOL ||
568		    q[i + IPOPT_OPTVAL] == IPOPT_NOP ||
569		    i + IPOPT_OLEN < optlen)
570			;
571		else
572			return NULL;
573
574		switch (q[i + IPOPT_OPTVAL]) {
575		case IPOPT_EOL:
576			i = optlen;	/* bye */
577			break;
578		case IPOPT_NOP:
579			i++;
580			break;
581		case IPOPT_LSRR:
582		case IPOPT_SSRR:
583			if (q[i + IPOPT_OLEN] < 2 + sizeof(struct in_addr) ||
584			    optlen - i < q[i + IPOPT_OLEN]) {
585				ipseclog((LOG_ERR,
586				    "ip_finaldst: invalid IP option "
587				    "(code=%02x len=%02x)\n",
588				    q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
589				return NULL;
590			}
591			i += q[i + IPOPT_OLEN] - sizeof(struct in_addr);
592			return (struct in_addr *)(q + i);
593		default:
594			if (q[i + IPOPT_OLEN] < 2 ||
595			    optlen - i < q[i + IPOPT_OLEN]) {
596				ipseclog((LOG_ERR,
597				    "ip_finaldst: invalid IP option "
598				    "(code=%02x len=%02x)\n",
599				    q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
600				return NULL;
601			}
602			i += q[i + IPOPT_OLEN];
603			break;
604		}
605	}
606	return NULL;
607}
608#endif
609