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