merged tasking branch into trunk
[strongswan.git] / src / charon / sa / tasks / ike_init.c
1 /**
2 * @file ike_init.c
3 *
4 * @brief Implementation of the ike_init task.
5 *
6 */
7
8 /*
9 * Copyright (C) 2005-2007 Martin Willi
10 * Copyright (C) 2005 Jan Hutter
11 * Hochschule fuer Technik Rapperswil
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
17 *
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * for more details.
22 */
23
24 #include "ike_init.h"
25
26 #include <string.h>
27
28 #include <daemon.h>
29 #include <crypto/diffie_hellman.h>
30 #include <encoding/payloads/sa_payload.h>
31 #include <encoding/payloads/ke_payload.h>
32 #include <encoding/payloads/nonce_payload.h>
33
34
35 typedef struct private_ike_init_t private_ike_init_t;
36
37 /**
38 * Private members of a ike_init_t task.
39 */
40 struct private_ike_init_t {
41
42 /**
43 * Public methods and task_t interface.
44 */
45 ike_init_t public;
46
47 /**
48 * Assigned IKE_SA.
49 */
50 ike_sa_t *ike_sa;
51
52 /**
53 * Are we the initiator?
54 */
55 bool initiator;
56
57 /**
58 * Connection established by this IKE_SA
59 */
60 connection_t *connection;
61
62 /**
63 * diffie hellman group to use
64 */
65 diffie_hellman_group_t dh_group;
66
67 /**
68 * Diffie hellman object used to generate public DH value.
69 */
70 diffie_hellman_t *diffie_hellman;
71
72 /**
73 * nonce chosen by us
74 */
75 chunk_t my_nonce;
76
77 /**
78 * nonce chosen by peer
79 */
80 chunk_t other_nonce;
81
82 /**
83 * Negotiated proposal used for IKE_SA
84 */
85 proposal_t *proposal;
86
87 /**
88 * Old IKE_SA which gets rekeyed
89 */
90 ike_sa_t *old_sa;
91 };
92
93 /**
94 * build the payloads for the message
95 */
96 static void build_payloads(private_ike_init_t *this, message_t *message)
97 {
98 sa_payload_t *sa_payload;
99 ke_payload_t *ke_payload;
100 nonce_payload_t *nonce_payload;
101 linked_list_t *proposal_list;
102 ike_sa_id_t *id;
103 proposal_t *proposal;
104 iterator_t *iterator;
105
106 id = this->ike_sa->get_id(this->ike_sa);
107
108 this->connection = this->ike_sa->get_connection(this->ike_sa);
109
110 if (this->initiator)
111 {
112 proposal_list = this->connection->get_proposals(this->connection);
113 if (this->old_sa)
114 {
115 /* include SPI of new IKE_SA when we are rekeying */
116 iterator = proposal_list->create_iterator(proposal_list, TRUE);
117 while (iterator->iterate(iterator, (void**)&proposal))
118 {
119 proposal->set_spi(proposal, id->get_initiator_spi(id));
120 }
121 iterator->destroy(iterator);
122 }
123
124 sa_payload = sa_payload_create_from_proposal_list(proposal_list);
125 proposal_list->destroy_offset(proposal_list, offsetof(proposal_t, destroy));
126 }
127 else
128 {
129 if (this->old_sa)
130 {
131 /* include SPI of new IKE_SA when we are rekeying */
132 this->proposal->set_spi(this->proposal, id->get_responder_spi(id));
133 }
134 sa_payload = sa_payload_create_from_proposal(this->proposal);
135 }
136 message->add_payload(message, (payload_t*)sa_payload);
137
138 ke_payload = ke_payload_create_from_diffie_hellman(this->diffie_hellman);
139 message->add_payload(message, (payload_t*)ke_payload);
140
141 nonce_payload = nonce_payload_create();
142 nonce_payload->set_nonce(nonce_payload, this->my_nonce);
143
144 message->add_payload(message, (payload_t*)nonce_payload);
145 }
146
147 /**
148 * Read payloads from message
149 */
150 static void process_payloads(private_ike_init_t *this, message_t *message)
151 {
152 iterator_t *iterator;
153 payload_t *payload;
154
155 iterator = message->get_payload_iterator(message);
156 while (iterator->iterate(iterator, (void**)&payload))
157 {
158 switch (payload->get_type(payload))
159 {
160 case SECURITY_ASSOCIATION:
161 {
162 sa_payload_t *sa_payload = (sa_payload_t*)payload;
163 linked_list_t *proposal_list;
164
165 proposal_list = sa_payload->get_proposals(sa_payload);
166 this->proposal = this->connection->select_proposal(
167 this->connection, proposal_list);
168 proposal_list->destroy_offset(proposal_list,
169 offsetof(proposal_t, destroy));
170 break;
171 }
172 case KEY_EXCHANGE:
173 {
174 ke_payload_t *ke_payload = (ke_payload_t*)payload;
175 diffie_hellman_group_t dh_group;
176 chunk_t key_data;
177
178 dh_group = ke_payload->get_dh_group_number(ke_payload);
179
180 if (this->initiator)
181 {
182 if (dh_group != this->dh_group)
183 {
184 DBG1(DBG_IKE, "received a DH group not requested (%N)",
185 diffie_hellman_group_names, dh_group);
186 break;
187 }
188 }
189 else
190 {
191 this->dh_group = dh_group;
192 if (!this->connection->check_dh_group(this->connection,
193 dh_group))
194 {
195 break;
196 }
197 this->diffie_hellman = diffie_hellman_create(dh_group);
198 }
199 if (this->diffie_hellman)
200 {
201 key_data = ke_payload->get_key_exchange_data(ke_payload);
202 this->diffie_hellman->set_other_public_value(this->diffie_hellman, key_data);
203 }
204 break;
205 }
206 case NONCE:
207 {
208 nonce_payload_t *nonce_payload = (nonce_payload_t*)payload;
209 this->other_nonce = nonce_payload->get_nonce(nonce_payload);
210 break;
211 }
212 default:
213 break;
214 }
215 }
216 iterator->destroy(iterator);
217 }
218
219 /**
220 * Implementation of task_t.process for initiator
221 */
222 static status_t build_i(private_ike_init_t *this, message_t *message)
223 {
224 randomizer_t *randomizer;
225 status_t status;
226
227 this->connection = this->ike_sa->get_connection(this->ike_sa);
228 SIG(IKE_UP_START, "initiating IKE_SA to %H",
229 this->connection->get_other_host(this->connection));
230 this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING);
231
232 /* if the DH group is set via use_dh_group(), we already have a DH object */
233 if (!this->diffie_hellman)
234 {
235 this->dh_group = this->connection->get_dh_group(this->connection);
236 this->diffie_hellman = diffie_hellman_create(this->dh_group);
237 if (this->diffie_hellman == NULL)
238 {
239 SIG(IKE_UP_FAILED, "configured DH group %N not supported",
240 diffie_hellman_group_names, this->dh_group);
241 return FAILED;
242 }
243 }
244
245 randomizer = randomizer_create();
246 status = randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_SIZE,
247 &this->my_nonce);
248 randomizer->destroy(randomizer);
249 if (status != SUCCESS)
250 {
251 SIG(IKE_UP_FAILED, "error generating random nonce value");
252 return FAILED;
253 }
254
255 build_payloads(this, message);
256
257 return NEED_MORE;
258 }
259
260 /**
261 * Implementation of task_t.process for initiator
262 */
263 static status_t process_r(private_ike_init_t *this, message_t *message)
264 {
265 randomizer_t *randomizer;
266
267 this->connection = this->ike_sa->get_connection(this->ike_sa);
268 SIG(IKE_UP_FAILED, "%H is initiating an IKE_SA",
269 message->get_source(message));
270 this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING);
271
272 randomizer = randomizer_create();
273 if (randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_SIZE,
274 &this->my_nonce) != SUCCESS)
275 {
276 DBG1(DBG_IKE, "error generating random nonce value");
277 }
278 randomizer->destroy(randomizer);
279
280 process_payloads(this, message);
281
282 return NEED_MORE;
283 }
284
285 /**
286 * Implementation of task_t.build for responder
287 */
288 static status_t build_r(private_ike_init_t *this, message_t *message)
289 {
290 chunk_t secret;
291 status_t status;
292
293 /* check if we have everything we need */
294 if (this->proposal == NULL ||
295 this->other_nonce.len == 0 || this->my_nonce.len == 0)
296 {
297 SIG(IKE_UP_FAILED, "received proposals inacceptable");
298 message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
299 return FAILED;
300 }
301
302 if (this->diffie_hellman == NULL ||
303 this->diffie_hellman->get_shared_secret(this->diffie_hellman,
304 &secret) != SUCCESS)
305 {
306 chunk_t chunk;
307 u_int16_t dh_enc;
308
309 SIG(IKE_UP_FAILED, "received inacceptable DH group (%N)",
310 diffie_hellman_group_names, this->dh_group);
311 this->dh_group = this->connection->get_dh_group(this->connection);
312 dh_enc = htons(this->dh_group);
313 chunk.ptr = (u_int8_t*)&dh_enc;
314 chunk.len = sizeof(dh_enc);
315 message->add_notify(message, TRUE, INVALID_KE_PAYLOAD, chunk);
316 DBG1(DBG_IKE, "requesting DH group %N",
317 diffie_hellman_group_names, this->dh_group);
318 return FAILED;
319 }
320
321
322 if (this->old_sa)
323 {
324 ike_sa_id_t *id;
325 prf_t *prf, *child_prf;
326
327 /* Apply SPI if we are rekeying */
328 id = this->ike_sa->get_id(this->ike_sa);
329 id->set_initiator_spi(id, this->proposal->get_spi(this->proposal));
330
331 /* setup crypto keys for the rekeyed SA */
332 prf = this->old_sa->get_prf(this->old_sa);
333 child_prf = this->old_sa->get_child_prf(this->old_sa);
334 status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret,
335 this->other_nonce, this->my_nonce,
336 FALSE, child_prf, prf);
337 }
338 else
339 {
340 /* setup crypto keys */
341 status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret,
342 this->other_nonce, this->my_nonce,
343 FALSE, NULL, NULL);
344 }
345 if (status != SUCCESS)
346 {
347 SIG(IKE_UP_FAILED, "key derivation failed");
348 message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
349 return FAILED;
350 }
351
352 build_payloads(this, message);
353
354 return SUCCESS;
355 }
356
357 /**
358 * Implementation of task_t.process for initiator
359 */
360 static status_t process_i(private_ike_init_t *this, message_t *message)
361 {
362 chunk_t secret;
363 status_t status;
364 iterator_t *iterator;
365 payload_t *payload;
366
367 /* check for erronous notifies */
368 iterator = message->get_payload_iterator(message);
369 while (iterator->iterate(iterator, (void**)&payload))
370 {
371 if (payload->get_type(payload) == NOTIFY)
372 {
373 notify_payload_t *notify = (notify_payload_t*)payload;
374 notify_type_t type = notify->get_notify_type(notify);
375
376 switch (type)
377 {
378 case INVALID_KE_PAYLOAD:
379 {
380 chunk_t data;
381 diffie_hellman_group_t old_dh_group;
382
383 old_dh_group = this->dh_group;
384 data = notify->get_notification_data(notify);
385 this->dh_group = ntohs(*((u_int16_t*)data.ptr));
386
387 DBG1(DBG_IKE, "peer didn't accept DH group %N, it requested"
388 " %N", diffie_hellman_group_names, old_dh_group,
389 diffie_hellman_group_names, this->dh_group);
390 if (!this->connection->check_dh_group(this->connection,
391 this->dh_group))
392 {
393 SIG(IKE_UP_FAILED, "requested DH group %N not "
394 "acceptable, giving up", diffie_hellman_group_names,
395 this->dh_group);
396 iterator->destroy(iterator);
397 return FAILED;
398 }
399
400 this->ike_sa->reset(this->ike_sa);
401
402 iterator->destroy(iterator);
403 return NEED_MORE;
404 }
405 default:
406 {
407 if (type < 16383)
408 {
409 SIG(IKE_UP_FAILED, "received %N notify error",
410 notify_type_names, type);
411 iterator->destroy(iterator);
412 return FAILED;
413 }
414 }
415 }
416 }
417 }
418 iterator->destroy(iterator);
419
420 process_payloads(this, message);
421
422 /* check if we have everything */
423 if (this->proposal == NULL ||
424 this->other_nonce.len == 0 || this->my_nonce.len == 0)
425 {
426 SIG(IKE_UP_FAILED, "peers proposal selection invalid");
427 return FAILED;
428 }
429
430 if (this->diffie_hellman == NULL ||
431 this->diffie_hellman->get_shared_secret(this->diffie_hellman,
432 &secret) != SUCCESS)
433 {
434 SIG(IKE_UP_FAILED, "peers DH group selection invalid");
435 return FAILED;
436 }
437
438 /* Apply SPI if we are rekeying */
439 if (this->old_sa)
440 {
441 ike_sa_id_t *id;
442 prf_t *prf, *child_prf;
443
444 id = this->ike_sa->get_id(this->ike_sa);
445 id->set_responder_spi(id, this->proposal->get_spi(this->proposal));
446
447 /* setup crypto keys for the rekeyed SA */
448 prf = this->old_sa->get_prf(this->old_sa);
449 child_prf = this->old_sa->get_child_prf(this->old_sa);
450 status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret,
451 this->my_nonce, this->other_nonce,
452 TRUE, child_prf, prf);
453 }
454 else
455 {
456 /* setup crypto keys for a new SA */
457 status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret,
458 this->my_nonce, this->other_nonce,
459 TRUE, NULL, NULL);
460 }
461 if (status != SUCCESS)
462 {
463 SIG(IKE_UP_FAILED, "key derivation failed");
464 return FAILED;
465 }
466 return SUCCESS;
467 }
468
469 /**
470 * Implementation of task_t.get_type
471 */
472 static task_type_t get_type(private_ike_init_t *this)
473 {
474 return IKE_INIT;
475 }
476
477 /**
478 * Implementation of task_t.migrate
479 */
480 static void migrate(private_ike_init_t *this, ike_sa_t *ike_sa)
481 {
482 DESTROY_IF(this->proposal);
483 DESTROY_IF(this->diffie_hellman);
484 chunk_free(&this->my_nonce);
485 chunk_free(&this->other_nonce);
486
487 this->ike_sa = ike_sa;
488 this->proposal = NULL;
489 this->diffie_hellman = diffie_hellman_create(this->dh_group);
490 }
491
492 /**
493 * Implementation of task_t.destroy
494 */
495 static void destroy(private_ike_init_t *this)
496 {
497 DESTROY_IF(this->proposal);
498 DESTROY_IF(this->diffie_hellman);
499 chunk_free(&this->my_nonce);
500 chunk_free(&this->other_nonce);
501 free(this);
502 }
503
504 /*
505 * Described in header.
506 */
507 ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
508 {
509 private_ike_init_t *this = malloc_thing(private_ike_init_t);
510
511 this->public.task.get_type = (task_type_t(*)(task_t*))get_type;
512 this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate;
513 this->public.task.destroy = (void(*)(task_t*))destroy;
514 if (initiator)
515 {
516 this->public.task.build = (status_t(*)(task_t*,message_t*))build_i;
517 this->public.task.process = (status_t(*)(task_t*,message_t*))process_i;
518 }
519 else
520 {
521 this->public.task.build = (status_t(*)(task_t*,message_t*))build_r;
522 this->public.task.process = (status_t(*)(task_t*,message_t*))process_r;
523 }
524
525 this->ike_sa = ike_sa;
526 this->initiator = initiator;
527 this->dh_group = MODP_NONE;
528 this->diffie_hellman = NULL;
529 this->my_nonce = chunk_empty;
530 this->other_nonce = chunk_empty;
531 this->proposal = NULL;
532 this->connection = NULL;
533 this->old_sa = old_sa;
534
535 return &this->public;
536 }