diff options
Diffstat (limited to 'net/ipv6/esp6.c')
-rw-r--r-- | net/ipv6/esp6.c | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c new file mode 100644 index 000000000000..be7095d6babe --- /dev/null +++ b/net/ipv6/esp6.c | |||
@@ -0,0 +1,424 @@ | |||
1 | /* | ||
2 | * Copyright (C)2002 USAGI/WIDE Project | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | * | ||
18 | * Authors | ||
19 | * | ||
20 | * Mitsuru KANDA @USAGI : IPv6 Support | ||
21 | * Kazunori MIYAZAWA @USAGI : | ||
22 | * Kunihiro Ishiguro <kunihiro@ipinfusion.com> | ||
23 | * | ||
24 | * This file is derived from net/ipv4/esp.c | ||
25 | */ | ||
26 | |||
27 | #include <linux/config.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <net/ip.h> | ||
30 | #include <net/xfrm.h> | ||
31 | #include <net/esp.h> | ||
32 | #include <asm/scatterlist.h> | ||
33 | #include <linux/crypto.h> | ||
34 | #include <linux/pfkeyv2.h> | ||
35 | #include <linux/random.h> | ||
36 | #include <net/icmp.h> | ||
37 | #include <net/ipv6.h> | ||
38 | #include <linux/icmpv6.h> | ||
39 | |||
40 | static int esp6_output(struct xfrm_state *x, struct sk_buff *skb) | ||
41 | { | ||
42 | int err; | ||
43 | int hdr_len; | ||
44 | struct ipv6hdr *top_iph; | ||
45 | struct ipv6_esp_hdr *esph; | ||
46 | struct crypto_tfm *tfm; | ||
47 | struct esp_data *esp; | ||
48 | struct sk_buff *trailer; | ||
49 | int blksize; | ||
50 | int clen; | ||
51 | int alen; | ||
52 | int nfrags; | ||
53 | |||
54 | esp = x->data; | ||
55 | hdr_len = skb->h.raw - skb->data + | ||
56 | sizeof(*esph) + esp->conf.ivlen; | ||
57 | |||
58 | /* Strip IP+ESP header. */ | ||
59 | __skb_pull(skb, hdr_len); | ||
60 | |||
61 | /* Now skb is pure payload to encrypt */ | ||
62 | err = -ENOMEM; | ||
63 | |||
64 | /* Round to block size */ | ||
65 | clen = skb->len; | ||
66 | |||
67 | alen = esp->auth.icv_trunc_len; | ||
68 | tfm = esp->conf.tfm; | ||
69 | blksize = (crypto_tfm_alg_blocksize(tfm) + 3) & ~3; | ||
70 | clen = (clen + 2 + blksize-1)&~(blksize-1); | ||
71 | if (esp->conf.padlen) | ||
72 | clen = (clen + esp->conf.padlen-1)&~(esp->conf.padlen-1); | ||
73 | |||
74 | if ((nfrags = skb_cow_data(skb, clen-skb->len+alen, &trailer)) < 0) { | ||
75 | goto error; | ||
76 | } | ||
77 | |||
78 | /* Fill padding... */ | ||
79 | do { | ||
80 | int i; | ||
81 | for (i=0; i<clen-skb->len - 2; i++) | ||
82 | *(u8*)(trailer->tail + i) = i+1; | ||
83 | } while (0); | ||
84 | *(u8*)(trailer->tail + clen-skb->len - 2) = (clen - skb->len)-2; | ||
85 | pskb_put(skb, trailer, clen - skb->len); | ||
86 | |||
87 | top_iph = (struct ipv6hdr *)__skb_push(skb, hdr_len); | ||
88 | esph = (struct ipv6_esp_hdr *)skb->h.raw; | ||
89 | top_iph->payload_len = htons(skb->len + alen - sizeof(*top_iph)); | ||
90 | *(u8*)(trailer->tail - 1) = *skb->nh.raw; | ||
91 | *skb->nh.raw = IPPROTO_ESP; | ||
92 | |||
93 | esph->spi = x->id.spi; | ||
94 | esph->seq_no = htonl(++x->replay.oseq); | ||
95 | |||
96 | if (esp->conf.ivlen) | ||
97 | crypto_cipher_set_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm)); | ||
98 | |||
99 | do { | ||
100 | struct scatterlist *sg = &esp->sgbuf[0]; | ||
101 | |||
102 | if (unlikely(nfrags > ESP_NUM_FAST_SG)) { | ||
103 | sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC); | ||
104 | if (!sg) | ||
105 | goto error; | ||
106 | } | ||
107 | skb_to_sgvec(skb, sg, esph->enc_data+esp->conf.ivlen-skb->data, clen); | ||
108 | crypto_cipher_encrypt(tfm, sg, sg, clen); | ||
109 | if (unlikely(sg != &esp->sgbuf[0])) | ||
110 | kfree(sg); | ||
111 | } while (0); | ||
112 | |||
113 | if (esp->conf.ivlen) { | ||
114 | memcpy(esph->enc_data, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm)); | ||
115 | crypto_cipher_get_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm)); | ||
116 | } | ||
117 | |||
118 | if (esp->auth.icv_full_len) { | ||
119 | esp->auth.icv(esp, skb, (u8*)esph-skb->data, | ||
120 | sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen+clen, trailer->tail); | ||
121 | pskb_put(skb, trailer, alen); | ||
122 | } | ||
123 | |||
124 | err = 0; | ||
125 | |||
126 | error: | ||
127 | return err; | ||
128 | } | ||
129 | |||
130 | static int esp6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb) | ||
131 | { | ||
132 | struct ipv6hdr *iph; | ||
133 | struct ipv6_esp_hdr *esph; | ||
134 | struct esp_data *esp = x->data; | ||
135 | struct sk_buff *trailer; | ||
136 | int blksize = crypto_tfm_alg_blocksize(esp->conf.tfm); | ||
137 | int alen = esp->auth.icv_trunc_len; | ||
138 | int elen = skb->len - sizeof(struct ipv6_esp_hdr) - esp->conf.ivlen - alen; | ||
139 | |||
140 | int hdr_len = skb->h.raw - skb->nh.raw; | ||
141 | int nfrags; | ||
142 | unsigned char *tmp_hdr = NULL; | ||
143 | int ret = 0; | ||
144 | |||
145 | if (!pskb_may_pull(skb, sizeof(struct ipv6_esp_hdr))) { | ||
146 | ret = -EINVAL; | ||
147 | goto out_nofree; | ||
148 | } | ||
149 | |||
150 | if (elen <= 0 || (elen & (blksize-1))) { | ||
151 | ret = -EINVAL; | ||
152 | goto out_nofree; | ||
153 | } | ||
154 | |||
155 | tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC); | ||
156 | if (!tmp_hdr) { | ||
157 | ret = -ENOMEM; | ||
158 | goto out_nofree; | ||
159 | } | ||
160 | memcpy(tmp_hdr, skb->nh.raw, hdr_len); | ||
161 | |||
162 | /* If integrity check is required, do this. */ | ||
163 | if (esp->auth.icv_full_len) { | ||
164 | u8 sum[esp->auth.icv_full_len]; | ||
165 | u8 sum1[alen]; | ||
166 | |||
167 | esp->auth.icv(esp, skb, 0, skb->len-alen, sum); | ||
168 | |||
169 | if (skb_copy_bits(skb, skb->len-alen, sum1, alen)) | ||
170 | BUG(); | ||
171 | |||
172 | if (unlikely(memcmp(sum, sum1, alen))) { | ||
173 | x->stats.integrity_failed++; | ||
174 | ret = -EINVAL; | ||
175 | goto out; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0) { | ||
180 | ret = -EINVAL; | ||
181 | goto out; | ||
182 | } | ||
183 | |||
184 | skb->ip_summed = CHECKSUM_NONE; | ||
185 | |||
186 | esph = (struct ipv6_esp_hdr*)skb->data; | ||
187 | iph = skb->nh.ipv6h; | ||
188 | |||
189 | /* Get ivec. This can be wrong, check against another impls. */ | ||
190 | if (esp->conf.ivlen) | ||
191 | crypto_cipher_set_iv(esp->conf.tfm, esph->enc_data, crypto_tfm_alg_ivsize(esp->conf.tfm)); | ||
192 | |||
193 | { | ||
194 | u8 nexthdr[2]; | ||
195 | struct scatterlist *sg = &esp->sgbuf[0]; | ||
196 | u8 padlen; | ||
197 | |||
198 | if (unlikely(nfrags > ESP_NUM_FAST_SG)) { | ||
199 | sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC); | ||
200 | if (!sg) { | ||
201 | ret = -ENOMEM; | ||
202 | goto out; | ||
203 | } | ||
204 | } | ||
205 | skb_to_sgvec(skb, sg, sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen, elen); | ||
206 | crypto_cipher_decrypt(esp->conf.tfm, sg, sg, elen); | ||
207 | if (unlikely(sg != &esp->sgbuf[0])) | ||
208 | kfree(sg); | ||
209 | |||
210 | if (skb_copy_bits(skb, skb->len-alen-2, nexthdr, 2)) | ||
211 | BUG(); | ||
212 | |||
213 | padlen = nexthdr[0]; | ||
214 | if (padlen+2 >= elen) { | ||
215 | LIMIT_NETDEBUG( | ||
216 | printk(KERN_WARNING "ipsec esp packet is garbage padlen=%d, elen=%d\n", padlen+2, elen)); | ||
217 | ret = -EINVAL; | ||
218 | goto out; | ||
219 | } | ||
220 | /* ... check padding bits here. Silly. :-) */ | ||
221 | |||
222 | pskb_trim(skb, skb->len - alen - padlen - 2); | ||
223 | skb->h.raw = skb_pull(skb, sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen); | ||
224 | skb->nh.raw += sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen; | ||
225 | memcpy(skb->nh.raw, tmp_hdr, hdr_len); | ||
226 | skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); | ||
227 | ret = nexthdr[1]; | ||
228 | } | ||
229 | |||
230 | out: | ||
231 | kfree(tmp_hdr); | ||
232 | out_nofree: | ||
233 | return ret; | ||
234 | } | ||
235 | |||
236 | static u32 esp6_get_max_size(struct xfrm_state *x, int mtu) | ||
237 | { | ||
238 | struct esp_data *esp = x->data; | ||
239 | u32 blksize = crypto_tfm_alg_blocksize(esp->conf.tfm); | ||
240 | |||
241 | if (x->props.mode) { | ||
242 | mtu = (mtu + 2 + blksize-1)&~(blksize-1); | ||
243 | } else { | ||
244 | /* The worst case. */ | ||
245 | mtu += 2 + blksize; | ||
246 | } | ||
247 | if (esp->conf.padlen) | ||
248 | mtu = (mtu + esp->conf.padlen-1)&~(esp->conf.padlen-1); | ||
249 | |||
250 | return mtu + x->props.header_len + esp->auth.icv_full_len; | ||
251 | } | ||
252 | |||
253 | static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, | ||
254 | int type, int code, int offset, __u32 info) | ||
255 | { | ||
256 | struct ipv6hdr *iph = (struct ipv6hdr*)skb->data; | ||
257 | struct ipv6_esp_hdr *esph = (struct ipv6_esp_hdr*)(skb->data+offset); | ||
258 | struct xfrm_state *x; | ||
259 | |||
260 | if (type != ICMPV6_DEST_UNREACH && | ||
261 | type != ICMPV6_PKT_TOOBIG) | ||
262 | return; | ||
263 | |||
264 | x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, esph->spi, IPPROTO_ESP, AF_INET6); | ||
265 | if (!x) | ||
266 | return; | ||
267 | printk(KERN_DEBUG "pmtu discovery on SA ESP/%08x/" | ||
268 | "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", | ||
269 | ntohl(esph->spi), NIP6(iph->daddr)); | ||
270 | xfrm_state_put(x); | ||
271 | } | ||
272 | |||
273 | static void esp6_destroy(struct xfrm_state *x) | ||
274 | { | ||
275 | struct esp_data *esp = x->data; | ||
276 | |||
277 | if (!esp) | ||
278 | return; | ||
279 | |||
280 | if (esp->conf.tfm) { | ||
281 | crypto_free_tfm(esp->conf.tfm); | ||
282 | esp->conf.tfm = NULL; | ||
283 | } | ||
284 | if (esp->conf.ivec) { | ||
285 | kfree(esp->conf.ivec); | ||
286 | esp->conf.ivec = NULL; | ||
287 | } | ||
288 | if (esp->auth.tfm) { | ||
289 | crypto_free_tfm(esp->auth.tfm); | ||
290 | esp->auth.tfm = NULL; | ||
291 | } | ||
292 | if (esp->auth.work_icv) { | ||
293 | kfree(esp->auth.work_icv); | ||
294 | esp->auth.work_icv = NULL; | ||
295 | } | ||
296 | kfree(esp); | ||
297 | } | ||
298 | |||
299 | static int esp6_init_state(struct xfrm_state *x, void *args) | ||
300 | { | ||
301 | struct esp_data *esp = NULL; | ||
302 | |||
303 | /* null auth and encryption can have zero length keys */ | ||
304 | if (x->aalg) { | ||
305 | if (x->aalg->alg_key_len > 512) | ||
306 | goto error; | ||
307 | } | ||
308 | if (x->ealg == NULL) | ||
309 | goto error; | ||
310 | |||
311 | if (x->encap) | ||
312 | goto error; | ||
313 | |||
314 | esp = kmalloc(sizeof(*esp), GFP_KERNEL); | ||
315 | if (esp == NULL) | ||
316 | return -ENOMEM; | ||
317 | |||
318 | memset(esp, 0, sizeof(*esp)); | ||
319 | |||
320 | if (x->aalg) { | ||
321 | struct xfrm_algo_desc *aalg_desc; | ||
322 | |||
323 | esp->auth.key = x->aalg->alg_key; | ||
324 | esp->auth.key_len = (x->aalg->alg_key_len+7)/8; | ||
325 | esp->auth.tfm = crypto_alloc_tfm(x->aalg->alg_name, 0); | ||
326 | if (esp->auth.tfm == NULL) | ||
327 | goto error; | ||
328 | esp->auth.icv = esp_hmac_digest; | ||
329 | |||
330 | aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0); | ||
331 | BUG_ON(!aalg_desc); | ||
332 | |||
333 | if (aalg_desc->uinfo.auth.icv_fullbits/8 != | ||
334 | crypto_tfm_alg_digestsize(esp->auth.tfm)) { | ||
335 | printk(KERN_INFO "ESP: %s digestsize %u != %hu\n", | ||
336 | x->aalg->alg_name, | ||
337 | crypto_tfm_alg_digestsize(esp->auth.tfm), | ||
338 | aalg_desc->uinfo.auth.icv_fullbits/8); | ||
339 | goto error; | ||
340 | } | ||
341 | |||
342 | esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8; | ||
343 | esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8; | ||
344 | |||
345 | esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL); | ||
346 | if (!esp->auth.work_icv) | ||
347 | goto error; | ||
348 | } | ||
349 | esp->conf.key = x->ealg->alg_key; | ||
350 | esp->conf.key_len = (x->ealg->alg_key_len+7)/8; | ||
351 | if (x->props.ealgo == SADB_EALG_NULL) | ||
352 | esp->conf.tfm = crypto_alloc_tfm(x->ealg->alg_name, CRYPTO_TFM_MODE_ECB); | ||
353 | else | ||
354 | esp->conf.tfm = crypto_alloc_tfm(x->ealg->alg_name, CRYPTO_TFM_MODE_CBC); | ||
355 | if (esp->conf.tfm == NULL) | ||
356 | goto error; | ||
357 | esp->conf.ivlen = crypto_tfm_alg_ivsize(esp->conf.tfm); | ||
358 | esp->conf.padlen = 0; | ||
359 | if (esp->conf.ivlen) { | ||
360 | esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL); | ||
361 | if (unlikely(esp->conf.ivec == NULL)) | ||
362 | goto error; | ||
363 | get_random_bytes(esp->conf.ivec, esp->conf.ivlen); | ||
364 | } | ||
365 | if (crypto_cipher_setkey(esp->conf.tfm, esp->conf.key, esp->conf.key_len)) | ||
366 | goto error; | ||
367 | x->props.header_len = sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen; | ||
368 | if (x->props.mode) | ||
369 | x->props.header_len += sizeof(struct ipv6hdr); | ||
370 | x->data = esp; | ||
371 | return 0; | ||
372 | |||
373 | error: | ||
374 | x->data = esp; | ||
375 | esp6_destroy(x); | ||
376 | x->data = NULL; | ||
377 | return -EINVAL; | ||
378 | } | ||
379 | |||
380 | static struct xfrm_type esp6_type = | ||
381 | { | ||
382 | .description = "ESP6", | ||
383 | .owner = THIS_MODULE, | ||
384 | .proto = IPPROTO_ESP, | ||
385 | .init_state = esp6_init_state, | ||
386 | .destructor = esp6_destroy, | ||
387 | .get_max_size = esp6_get_max_size, | ||
388 | .input = esp6_input, | ||
389 | .output = esp6_output | ||
390 | }; | ||
391 | |||
392 | static struct inet6_protocol esp6_protocol = { | ||
393 | .handler = xfrm6_rcv, | ||
394 | .err_handler = esp6_err, | ||
395 | .flags = INET6_PROTO_NOPOLICY, | ||
396 | }; | ||
397 | |||
398 | static int __init esp6_init(void) | ||
399 | { | ||
400 | if (xfrm_register_type(&esp6_type, AF_INET6) < 0) { | ||
401 | printk(KERN_INFO "ipv6 esp init: can't add xfrm type\n"); | ||
402 | return -EAGAIN; | ||
403 | } | ||
404 | if (inet6_add_protocol(&esp6_protocol, IPPROTO_ESP) < 0) { | ||
405 | printk(KERN_INFO "ipv6 esp init: can't add protocol\n"); | ||
406 | xfrm_unregister_type(&esp6_type, AF_INET6); | ||
407 | return -EAGAIN; | ||
408 | } | ||
409 | |||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | static void __exit esp6_fini(void) | ||
414 | { | ||
415 | if (inet6_del_protocol(&esp6_protocol, IPPROTO_ESP) < 0) | ||
416 | printk(KERN_INFO "ipv6 esp close: can't remove protocol\n"); | ||
417 | if (xfrm_unregister_type(&esp6_type, AF_INET6) < 0) | ||
418 | printk(KERN_INFO "ipv6 esp close: can't remove xfrm type\n"); | ||
419 | } | ||
420 | |||
421 | module_init(esp6_init); | ||
422 | module_exit(esp6_fini); | ||
423 | |||
424 | MODULE_LICENSE("GPL"); | ||