]> git.saurik.com Git - apple/xnu.git/blame - bsd/netinet6/ah_output.c
xnu-517.3.7.tar.gz
[apple/xnu.git] / bsd / netinet6 / ah_output.c
CommitLineData
9bccf70c
A
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 $ */
1c79356b
A
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
1c79356b
A
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>
9bccf70c
A
68#if INET6
69#include <netinet6/ipsec6.h>
70#endif
1c79356b 71#include <netinet6/ah.h>
9bccf70c
A
72#if INET6
73#include <netinet6/ah6.h>
74#endif
1c79356b
A
75#include <netkey/key.h>
76#include <netkey/keydb.h>
1c79356b
A
77
78#include <net/net_osdep.h>
79
9bccf70c 80#if INET
1c79356b 81static struct in_addr *ah4_finaldst __P((struct mbuf *));
9bccf70c 82#endif
1c79356b
A
83
84/*
85 * compute AH header size.
86 * transport mode only. for tunnel mode, we should implement
87 * virtual interface, and control MTU/MSS by the interface MTU.
88 */
89size_t
90ah_hdrsiz(isr)
91 struct ipsecrequest *isr;
92{
9bccf70c 93 const struct ah_algorithm *algo;
1c79356b
A
94 size_t hdrsiz;
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 (isr->sav == NULL)
104 goto estimate;
105 if (isr->sav->state != SADB_SASTATE_MATURE
106 && isr->sav->state != SADB_SASTATE_DYING)
107 goto estimate;
108
109 /* we need transport mode AH. */
9bccf70c 110 algo = ah_algorithm_lookup(isr->sav->alg_auth);
1c79356b
A
111 if (!algo)
112 goto estimate;
113
114 /*
115 * XXX
116 * right now we don't calcurate the padding size. simply
117 * treat the padding size as constant, for simplicity.
118 *
119 * XXX variable size padding support
120 */
121 hdrsiz = (((*algo->sumsiz)(isr->sav) + 3) & ~(4 - 1));
122 if (isr->sav->flags & SADB_X_EXT_OLD)
123 hdrsiz += sizeof(struct ah);
124 else
125 hdrsiz += sizeof(struct newah);
126
127 return hdrsiz;
128
129 estimate:
130 /* ASSUMING:
131 * sizeof(struct newah) > sizeof(struct ah).
132 * 16 = (16 + 3) & ~(4 - 1).
133 */
134 return sizeof(struct newah) + 16;
135}
136
9bccf70c 137#if INET
1c79356b
A
138/*
139 * Modify the packet so that it includes the authentication data.
140 * The mbuf passed must start with IPv4 header.
141 *
142 * assumes that the first mbuf contains IPv4 header + option only.
143 * the function does not modify m.
144 */
145int
146ah4_output(m, isr)
147 struct mbuf *m;
148 struct ipsecrequest *isr;
149{
150 struct secasvar *sav = isr->sav;
9bccf70c 151 const struct ah_algorithm *algo;
1c79356b
A
152 u_int32_t spi;
153 u_char *ahdrpos;
154 u_char *ahsumpos = NULL;
155 size_t hlen = 0; /*IP header+option in bytes*/
156 size_t plen = 0; /*AH payload size in bytes*/
157 size_t ahlen = 0; /*plen + sizeof(ah)*/
158 struct ip *ip;
159 struct in_addr dst;
160 struct in_addr *finaldst;
161 int error;
162
163 /* sanity checks */
164 if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
165 struct ip *ip;
166
167 ip = mtod(m, struct ip *);
168 ipseclog((LOG_DEBUG, "ah4_output: internal error: "
169 "sav->replay is null: %x->%x, SPI=%u\n",
170 (u_int32_t)ntohl(ip->ip_src.s_addr),
171 (u_int32_t)ntohl(ip->ip_dst.s_addr),
172 (u_int32_t)ntohl(sav->spi)));
173 ipsecstat.out_inval++;
174 m_freem(m);
175 return EINVAL;
176 }
177
9bccf70c
A
178 algo = ah_algorithm_lookup(sav->alg_auth);
179 if (!algo) {
180 ipseclog((LOG_ERR, "ah4_output: unsupported algorithm: "
181 "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
182 ipsecstat.out_inval++;
183 m_freem(m);
184 return EINVAL;
185 }
1c79356b
A
186 spi = sav->spi;
187
188 /*
189 * determine the size to grow.
190 */
191 if (sav->flags & SADB_X_EXT_OLD) {
192 /* RFC 1826 */
193 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
194 ahlen = plen + sizeof(struct ah);
195 } else {
196 /* RFC 2402 */
197 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
198 ahlen = plen + sizeof(struct newah);
199 }
200
201 /*
202 * grow the mbuf to accomodate AH.
203 */
204 ip = mtod(m, struct ip *);
205#ifdef _IP_VHL
206 hlen = IP_VHL_HL(ip->ip_vhl) << 2;
207#else
208 hlen = ip->ip_hl << 2;
209#endif
210
211 if (m->m_len != hlen)
212 panic("ah4_output: assumption failed (first mbuf length)");
213 if (M_LEADINGSPACE(m->m_next) < ahlen) {
214 struct mbuf *n;
215 MGET(n, M_DONTWAIT, MT_DATA);
216 if (!n) {
217 ipseclog((LOG_DEBUG, "ENOBUFS in ah4_output %d\n",
218 __LINE__));
219 m_freem(m);
220 return ENOBUFS;
221 }
222 n->m_len = ahlen;
223 n->m_next = m->m_next;
224 m->m_next = n;
225 m->m_pkthdr.len += ahlen;
226 ahdrpos = mtod(n, u_char *);
227 } else {
228 m->m_next->m_len += ahlen;
229 m->m_next->m_data -= ahlen;
230 m->m_pkthdr.len += ahlen;
231 ahdrpos = mtod(m->m_next, u_char *);
232 }
233
234 ip = mtod(m, struct ip *); /*just to be sure*/
235
236 /*
237 * initialize AH.
238 */
239 if (sav->flags & SADB_X_EXT_OLD) {
240 struct ah *ahdr;
241
242 ahdr = (struct ah *)ahdrpos;
243 ahsumpos = (u_char *)(ahdr + 1);
244 ahdr->ah_len = plen >> 2;
245 ahdr->ah_nxt = ip->ip_p;
246 ahdr->ah_reserve = htons(0);
247 ahdr->ah_spi = spi;
248 bzero(ahdr + 1, plen);
249 } else {
250 struct newah *ahdr;
251
252 ahdr = (struct newah *)ahdrpos;
253 ahsumpos = (u_char *)(ahdr + 1);
254 ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */
255 ahdr->ah_nxt = ip->ip_p;
256 ahdr->ah_reserve = htons(0);
257 ahdr->ah_spi = spi;
258 if (sav->replay->count == ~0) {
259 if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
260 /* XXX Is it noisy ? */
261 ipseclog((LOG_WARNING,
262 "replay counter overflowed. %s\n",
263 ipsec_logsastr(sav)));
264 ipsecstat.out_inval++;
265 m_freem(m);
266 return EINVAL;
267 }
268 }
269 sav->replay->count++;
270 /*
271 * XXX sequence number must not be cycled, if the SA is
272 * installed by IKE daemon.
273 */
274 ahdr->ah_seq = htonl(sav->replay->count);
275 bzero(ahdr + 1, plen);
276 }
277
278 /*
279 * modify IPv4 header.
280 */
281 ip->ip_p = IPPROTO_AH;
282 if (ahlen < (IP_MAXPACKET - ntohs(ip->ip_len)))
283 ip->ip_len = htons(ntohs(ip->ip_len) + ahlen);
284 else {
285 ipseclog((LOG_ERR, "IPv4 AH output: size exceeds limit\n"));
286 ipsecstat.out_inval++;
287 m_freem(m);
288 return EMSGSIZE;
289 }
290
291 /*
292 * If there is source routing option, update destination field in
293 * the IPv4 header to the final destination.
294 * Note that we do not need to update source routing option itself
295 * (as done in IPv4 AH processing -- see ip6_output()), since
296 * source routing option is not part of the ICV computation.
297 */
298 finaldst = ah4_finaldst(m);
299 if (finaldst) {
300 dst.s_addr = ip->ip_dst.s_addr;
301 ip->ip_dst.s_addr = finaldst->s_addr;
302 }
303
304 /*
305 * calcurate the checksum, based on security association
306 * and the algorithm specified.
307 */
9bccf70c 308 error = ah4_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
1c79356b
A
309 if (error) {
310 ipseclog((LOG_ERR,
311 "error after ah4_calccksum, called from ah4_output"));
9bccf70c 312 m_freem(m);
1c79356b
A
313 m = NULL;
314 ipsecstat.out_inval++;
315 return error;
316 }
317
318 if (finaldst) {
319 ip = mtod(m, struct ip *); /*just to make sure*/
320 ip->ip_dst.s_addr = dst.s_addr;
321 }
322 ipsecstat.out_success++;
323 ipsecstat.out_ahhist[sav->alg_auth]++;
324 key_sa_recordxfer(sav, m);
325
326 return 0;
327}
9bccf70c 328#endif
1c79356b
A
329
330/* Calculate AH length */
331int
332ah_hdrlen(sav)
333 struct secasvar *sav;
334{
9bccf70c 335 const struct ah_algorithm *algo;
1c79356b
A
336 int plen, ahlen;
337
9bccf70c
A
338 algo = ah_algorithm_lookup(sav->alg_auth);
339 if (!algo)
340 return 0;
1c79356b
A
341 if (sav->flags & SADB_X_EXT_OLD) {
342 /* RFC 1826 */
343 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
344 ahlen = plen + sizeof(struct ah);
345 } else {
346 /* RFC 2402 */
347 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
348 ahlen = plen + sizeof(struct newah);
349 }
350
351 return(ahlen);
352}
353
354#if INET6
355/*
356 * Fill in the Authentication Header and calculate checksum.
357 */
358int
359ah6_output(m, nexthdrp, md, isr)
360 struct mbuf *m;
361 u_char *nexthdrp;
362 struct mbuf *md;
363 struct ipsecrequest *isr;
364{
365 struct mbuf *mprev;
366 struct mbuf *mah;
367 struct secasvar *sav = isr->sav;
9bccf70c 368 const struct ah_algorithm *algo;
1c79356b
A
369 u_int32_t spi;
370 u_char *ahsumpos = NULL;
371 size_t plen; /*AH payload size in bytes*/
372 int error = 0;
373 int ahlen;
374 struct ip6_hdr *ip6;
375
376 if (m->m_len < sizeof(struct ip6_hdr)) {
377 ipseclog((LOG_DEBUG, "ah6_output: first mbuf too short\n"));
378 m_freem(m);
379 return EINVAL;
380 }
381
382 ahlen = ah_hdrlen(sav);
383 if (ahlen == 0)
384 return 0;
385
386 for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next)
387 ;
388 if (!mprev || mprev->m_next != md) {
389 ipseclog((LOG_DEBUG, "ah6_output: md is not in chain\n"));
390 m_freem(m);
391 return EINVAL;
392 }
393
394 MGET(mah, M_DONTWAIT, MT_DATA);
395 if (!mah) {
396 m_freem(m);
397 return ENOBUFS;
398 }
399 if (ahlen > MLEN) {
400 MCLGET(mah, M_DONTWAIT);
401 if ((mah->m_flags & M_EXT) == 0) {
402 m_free(mah);
403 m_freem(m);
404 return ENOBUFS;
405 }
406 }
407 mah->m_len = ahlen;
408 mah->m_next = md;
409 mprev->m_next = mah;
410 m->m_pkthdr.len += ahlen;
411
412 /* fix plen */
413 if (m->m_pkthdr.len - sizeof(struct ip6_hdr) > IPV6_MAXPACKET) {
414 ipseclog((LOG_ERR,
415 "ip6_output: AH with IPv6 jumbogram is not supported\n"));
416 m_freem(m);
417 return EINVAL;
418 }
419 ip6 = mtod(m, struct ip6_hdr *);
420 ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr));
421
422 if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
423 ipseclog((LOG_DEBUG, "ah6_output: internal error: "
424 "sav->replay is null: SPI=%u\n",
425 (u_int32_t)ntohl(sav->spi)));
426 ipsec6stat.out_inval++;
427 m_freem(m);
428 return EINVAL;
429 }
430
9bccf70c
A
431 algo = ah_algorithm_lookup(sav->alg_auth);
432 if (!algo) {
433 ipseclog((LOG_ERR, "ah6_output: unsupported algorithm: "
434 "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
435 ipsec6stat.out_inval++;
436 m_freem(m);
437 return EINVAL;
438 }
1c79356b
A
439 spi = sav->spi;
440
441 /*
442 * initialize AH.
443 */
444 if (sav->flags & SADB_X_EXT_OLD) {
445 struct ah *ahdr = mtod(mah, struct ah *);
446
447 plen = mah->m_len - sizeof(struct ah);
448 ahsumpos = (u_char *)(ahdr + 1);
449 ahdr->ah_nxt = *nexthdrp;
450 *nexthdrp = IPPROTO_AH;
451 ahdr->ah_len = plen >> 2;
452 ahdr->ah_reserve = htons(0);
453 ahdr->ah_spi = spi;
454 bzero(ahdr + 1, plen);
455 } else {
456 struct newah *ahdr = mtod(mah, struct newah *);
457
458 plen = mah->m_len - sizeof(struct newah);
459 ahsumpos = (u_char *)(ahdr + 1);
460 ahdr->ah_nxt = *nexthdrp;
461 *nexthdrp = IPPROTO_AH;
462 ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */
463 ahdr->ah_reserve = htons(0);
464 ahdr->ah_spi = spi;
465 if (sav->replay->count == ~0) {
466 if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
467 /* XXX Is it noisy ? */
468 ipseclog((LOG_WARNING,
469 "replay counter overflowed. %s\n",
470 ipsec_logsastr(sav)));
9bccf70c 471 ipsec6stat.out_inval++;
1c79356b
A
472 m_freem(m);
473 return EINVAL;
474 }
475 }
476 sav->replay->count++;
477 /*
478 * XXX sequence number must not be cycled, if the SA is
479 * installed by IKE daemon.
480 */
481 ahdr->ah_seq = htonl(sav->replay->count);
482 bzero(ahdr + 1, plen);
483 }
484
485 /*
486 * calcurate the checksum, based on security association
487 * and the algorithm specified.
488 */
9bccf70c 489 error = ah6_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
1c79356b
A
490 if (error) {
491 ipsec6stat.out_inval++;
492 m_freem(m);
493 } else {
494 ipsec6stat.out_success++;
495 key_sa_recordxfer(sav, m);
496 }
497 ipsec6stat.out_ahhist[sav->alg_auth]++;
498
499 return(error);
500}
501#endif
502
9bccf70c 503#if INET
1c79356b
A
504/*
505 * Find the final destination if there is loose/strict source routing option.
506 * Returns NULL if there's no source routing options.
507 * Returns NULL on errors too.
508 * Note that this function will return a pointer INTO the given parameter,
509 * struct mbuf *m.
510 * The mbuf must be pulled up toward, at least, ip option part.
511 */
512static struct in_addr *
513ah4_finaldst(m)
514 struct mbuf *m;
515{
516 struct ip *ip;
517 int optlen;
518 u_char *q;
519 int i;
520 int hlen;
521
522 if (!m)
523 panic("ah4_finaldst: m == NULL");
524 ip = mtod(m, struct ip *);
525#ifdef _IP_VHL
526 hlen = IP_VHL_HL(ip->ip_vhl) << 2;
527#else
528 hlen = ip->ip_hl << 2;
529#endif
530
531 if (m->m_len < hlen) {
532 ipseclog((LOG_DEBUG,
533 "ah4_finaldst: parameter mbuf wrong (not pulled up)\n"));
534 return NULL;
535 }
536
537 if (hlen == sizeof(struct ip))
538 return NULL;
539
540 optlen = hlen - sizeof(struct ip);
541 if (optlen < 0) {
542 ipseclog((LOG_DEBUG, "ah4_finaldst: wrong optlen %d\n",
543 optlen));
544 return NULL;
545 }
546
547 q = (u_char *)(ip + 1);
548 i = 0;
549 while (i < optlen) {
9bccf70c
A
550 if (i + IPOPT_OPTVAL >= optlen)
551 return NULL;
552 if (q[i + IPOPT_OPTVAL] == IPOPT_EOL ||
553 q[i + IPOPT_OPTVAL] == IPOPT_NOP ||
554 i + IPOPT_OLEN < optlen)
555 ;
556 else
557 return NULL;
558
1c79356b
A
559 switch (q[i + IPOPT_OPTVAL]) {
560 case IPOPT_EOL:
561 i = optlen; /* bye */
562 break;
563 case IPOPT_NOP:
564 i++;
565 break;
566 case IPOPT_LSRR:
567 case IPOPT_SSRR:
9bccf70c
A
568 if (q[i + IPOPT_OLEN] < 2 + sizeof(struct in_addr) ||
569 optlen - i < q[i + IPOPT_OLEN]) {
1c79356b
A
570 ipseclog((LOG_ERR,
571 "ip_finaldst: invalid IP option "
572 "(code=%02x len=%02x)\n",
573 q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
574 return NULL;
575 }
576 i += q[i + IPOPT_OLEN] - sizeof(struct in_addr);
577 return (struct in_addr *)(q + i);
578 default:
9bccf70c
A
579 if (q[i + IPOPT_OLEN] < 2 ||
580 optlen - i < q[i + IPOPT_OLEN]) {
1c79356b
A
581 ipseclog((LOG_ERR,
582 "ip_finaldst: invalid IP option "
583 "(code=%02x len=%02x)\n",
584 q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
585 return NULL;
586 }
587 i += q[i + IPOPT_OLEN];
588 break;
589 }
590 }
591 return NULL;
592}
9bccf70c 593#endif