]> git.saurik.com Git - apple/xnu.git/blob - bsd/netinet6/ah_output.c
xnu-1228.tar.gz
[apple/xnu.git] / bsd / netinet6 / ah_output.c
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
81 static struct in_addr *ah4_finaldst(struct mbuf *);
82 #endif
83
84 extern 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 */
91 size_t
92 ah_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
139 estimate:
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 */
158 int
159 ah4_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 */
345 int
346 ah_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 */
372 int
373 ah6_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 */
527 static struct in_addr *
528 ah4_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