eap-radius: Add support for some basic IPv6-specific RADIUS attributes
[strongswan.git] / src / libradius / radius_message.c
1 /*
2 * Copyright (C) 2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "radius_message.h"
17
18 #include <utils/debug.h>
19 #include <bio/bio_reader.h>
20 #include <crypto/hashers/hasher.h>
21
22 typedef struct private_radius_message_t private_radius_message_t;
23 typedef struct rmsg_t rmsg_t;
24 typedef struct rattr_t rattr_t;
25
26 /**
27 * RADIUS message header
28 */
29 struct rmsg_t {
30 /** message code, radius_message_code_t */
31 u_int8_t code;
32 /** message identifier */
33 u_int8_t identifier;
34 /** length of Code, Identifier, Length, Authenticator and Attributes */
35 u_int16_t length;
36 /** message authenticator, MD5 hash */
37 u_int8_t authenticator[HASH_SIZE_MD5];
38 /** variable list of packed attributes */
39 u_int8_t attributes[];
40 } __attribute__((packed));
41
42 /**
43 * RADIUS message attribute.
44 */
45 struct rattr_t {
46 /** attribute type, radius_attribute_type_t */
47 u_int8_t type;
48 /** length of the attriubte, including the Type, Length and Value fields */
49 u_int8_t length;
50 /** variable length attribute value */
51 u_int8_t value[];
52 } __attribute__((packed));
53
54 /**
55 * Private data of an radius_message_t object.
56 */
57 struct private_radius_message_t {
58
59 /**
60 * Public radius_message_t interface.
61 */
62 radius_message_t public;
63
64 /**
65 * message data, allocated
66 */
67 rmsg_t *msg;
68
69 /**
70 * User-Password to encrypt and encode, if any
71 */
72 chunk_t password;
73 };
74
75 /**
76 * Described in header.
77 */
78 void libradius_init(void)
79 {
80 /* empty */
81 }
82
83 ENUM_BEGIN(radius_message_code_names, RMC_ACCESS_REQUEST, RMC_ACCOUNTING_RESPONSE,
84 "Access-Request",
85 "Access-Accept",
86 "Access-Reject",
87 "Accounting-Request",
88 "Accounting-Response");
89 ENUM_NEXT(radius_message_code_names, RMC_ACCESS_CHALLENGE, RMC_ACCESS_CHALLENGE, RMC_ACCOUNTING_RESPONSE,
90 "Access-Challenge");
91 ENUM_NEXT(radius_message_code_names, RMC_DISCONNECT_REQUEST, RMC_COA_NAK, RMC_ACCESS_CHALLENGE,
92 "Disconnect-Request",
93 "Disconnect-ACK",
94 "Disconnect-NAK",
95 "CoA-Request",
96 "CoA-ACK",
97 "CoA-NAK");
98 ENUM_END(radius_message_code_names, RMC_COA_NAK);
99
100 ENUM_BEGIN(radius_attribute_type_names, RAT_USER_NAME, RAT_MIP6_HOME_LINK_PREFIX,
101 "User-Name",
102 "User-Password",
103 "CHAP-Password",
104 "NAS-IP-Address",
105 "NAS-Port",
106 "Service-Type",
107 "Framed-Protocol",
108 "Framed-IP-Address",
109 "Framed-IP-Netmask",
110 "Framed-Routing",
111 "Filter-Id",
112 "Framed-MTU",
113 "Framed-Compression",
114 "Login-IP-Host",
115 "Login-Service",
116 "Login-TCP-Port",
117 "Unassigned",
118 "Reply-Message",
119 "Callback-Number",
120 "Callback-Id",
121 "Unassigned",
122 "Framed-Route",
123 "Framed-IPX-Network",
124 "State",
125 "Class",
126 "Vendor-Specific",
127 "Session-Timeout",
128 "Idle-Timeout",
129 "Termination-Action",
130 "Called-Station-Id",
131 "Calling-Station-Id",
132 "NAS-Identifier",
133 "Proxy-State",
134 "Login-LAT-Service",
135 "Login-LAT-Node",
136 "Login-LAT-Group",
137 "Framed-AppleTalk-Link",
138 "Framed-AppleTalk-Network",
139 "Framed-AppleTalk-Zone",
140 "Acct-Status-Type",
141 "Acct-Delay-Time",
142 "Acct-Input-Octets",
143 "Acct-Output-Octets",
144 "Acct-Session-Id",
145 "Acct-Authentic",
146 "Acct-Session-Time",
147 "Acct-Input-Packets",
148 "Acct-Output-Packets",
149 "Acct-Terminate-Cause",
150 "Acct-Multi-Session-Id",
151 "Acct-Link-Count",
152 "Acct-Input-Gigawords",
153 "Acct-Output-Gigawords",
154 "Unassigned",
155 "Event-Timestamp",
156 "Egress-VLANID",
157 "Ingress-Filters",
158 "Egress-VLAN-Name",
159 "User-Priority-Table",
160 "CHAP-Challenge",
161 "NAS-Port-Type",
162 "Port-Limit",
163 "Login-LAT-Port",
164 "Tunnel-Type",
165 "Tunnel-Medium-Type",
166 "Tunnel-Client-Endpoint",
167 "Tunnel-Server-Endpoint",
168 "Acct-Tunnel-Connection",
169 "Tunnel-Password",
170 "ARAP-Password",
171 "ARAP-Features",
172 "ARAP-Zone-Access",
173 "ARAP-Security",
174 "ARAP-Security-Data",
175 "Password-Retry",
176 "Prompt",
177 "Connect-Info",
178 "Configuration-Token",
179 "EAP-Message",
180 "Message-Authenticator",
181 "Tunnel-Private-Group-ID",
182 "Tunnel-Assignment-ID",
183 "Tunnel-Preference",
184 "ARAP-Challenge-Response",
185 "Acct-Interim-Interval",
186 "Acct-Tunnel-Packets-Lost",
187 "NAS-Port-Id",
188 "Framed-Pool",
189 "CUI",
190 "Tunnel-Client-Auth-ID",
191 "Tunnel-Server-Auth-ID",
192 "NAS-Filter-Rule",
193 "Unassigned",
194 "Originating-Line-Info",
195 "NAS-IPv6-Address",
196 "Framed-Interface-Id",
197 "Framed-IPv6-Prefix",
198 "Login-IPv6-Host",
199 "Framed-IPv6-Route",
200 "Framed-IPv6-Pool",
201 "Error-Cause",
202 "EAP-Key-Name",
203 "Digest-Response",
204 "Digest-Realm",
205 "Digest-Nonce",
206 "Digest-Response-Auth",
207 "Digest-Nextnonce",
208 "Digest-Method",
209 "Digest-URI",
210 "Digest-Qop",
211 "Digest-Algorithm",
212 "Digest-Entity-Body-Hash",
213 "Digest-CNonce",
214 "Digest-Nonce-Count",
215 "Digest-Username",
216 "Digest-Opaque",
217 "Digest-Auth-Param",
218 "Digest-AKA-Auts",
219 "Digest-Domain",
220 "Digest-Stale",
221 "Digest-HA1",
222 "SIP-AOR",
223 "Delegated-IPv6-Prefix",
224 "MIP6-Feature-Vector",
225 "MIP6-Home-Link-Prefix");
226 ENUM_NEXT(radius_attribute_type_names, RAT_FRAMED_IPV6_ADDRESS, RAT_STATEFUL_IPV6_ADDRESS_POOL, RAT_MIP6_HOME_LINK_PREFIX,
227 "Framed-IPv6-Address",
228 "DNS-Server-IPv6-Address",
229 "Route-IPv6-Information",
230 "Delegated-IPv6-Prefix-Pool",
231 "Stateful-IPv6-Address-Pool");
232 ENUM_END(radius_attribute_type_names, RAT_STATEFUL_IPV6_ADDRESS_POOL);
233
234 /**
235 * Attribute enumerator implementation
236 */
237 typedef struct {
238 /** implements enumerator interface */
239 enumerator_t public;
240 /** currently pointing attribute */
241 rattr_t *next;
242 /** bytes left */
243 int left;
244 } attribute_enumerator_t;
245
246 METHOD(enumerator_t, attribute_enumerate, bool,
247 attribute_enumerator_t *this, int *type, chunk_t *data)
248 {
249 if (this->left == 0)
250 {
251 return FALSE;
252 }
253 if (this->left < sizeof(rattr_t) ||
254 this->left < this->next->length)
255 {
256 DBG1(DBG_IKE, "RADIUS message truncated");
257 return FALSE;
258 }
259 *type = this->next->type;
260 data->ptr = this->next->value;
261 data->len = this->next->length - sizeof(rattr_t);
262 this->left -= this->next->length;
263 this->next = ((void*)this->next) + this->next->length;
264 return TRUE;
265 }
266
267 METHOD(radius_message_t, create_enumerator, enumerator_t*,
268 private_radius_message_t *this)
269 {
270 attribute_enumerator_t *e;
271
272 if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t))
273 {
274 return enumerator_create_empty();
275 }
276 INIT(e,
277 .public = {
278 .enumerate = (void*)_attribute_enumerate,
279 .destroy = (void*)free,
280 },
281 .next = (rattr_t*)this->msg->attributes,
282 .left = ntohs(this->msg->length) - sizeof(rmsg_t),
283 );
284 return &e->public;
285 }
286
287 /**
288 * Vendor attribute enumerator implementation
289 */
290 typedef struct {
291 /** implements enumerator interface */
292 enumerator_t public;
293 /** inner attribute enumerator */
294 enumerator_t *inner;
295 /** current vendor ID */
296 u_int32_t vendor;
297 /** reader for current vendor ID */
298 bio_reader_t *reader;
299 } vendor_enumerator_t;
300
301 METHOD(enumerator_t, vendor_enumerate, bool,
302 vendor_enumerator_t *this, int *vendor, int *type, chunk_t *data)
303 {
304 chunk_t inner_data;
305 int inner_type;
306 u_int8_t type8, len;
307
308 while (TRUE)
309 {
310 if (this->reader)
311 {
312 if (this->reader->remaining(this->reader) >= 2 &&
313 this->reader->read_uint8(this->reader, &type8) &&
314 this->reader->read_uint8(this->reader, &len) && len >= 2 &&
315 this->reader->read_data(this->reader, len - 2, data))
316 {
317 *vendor = this->vendor;
318 *type = type8;
319 return TRUE;
320 }
321 this->reader->destroy(this->reader);
322 this->reader = NULL;
323 }
324 if (this->inner->enumerate(this->inner, &inner_type, &inner_data))
325 {
326 if (inner_type == RAT_VENDOR_SPECIFIC)
327 {
328 this->reader = bio_reader_create(inner_data);
329 if (!this->reader->read_uint32(this->reader, &this->vendor))
330 {
331 this->reader->destroy(this->reader);
332 this->reader = NULL;
333 }
334 }
335 }
336 else
337 {
338 return FALSE;
339 }
340 }
341 }
342 METHOD(enumerator_t, vendor_destroy, void,
343 vendor_enumerator_t *this)
344 {
345 DESTROY_IF(this->reader);
346 this->inner->destroy(this->inner);
347 free(this);
348 }
349
350 METHOD(radius_message_t, create_vendor_enumerator, enumerator_t*,
351 private_radius_message_t *this)
352 {
353 vendor_enumerator_t *e;
354
355 INIT(e,
356 .public = {
357 .enumerate = (void*)_vendor_enumerate,
358 .destroy = _vendor_destroy,
359 },
360 .inner = create_enumerator(this),
361 );
362
363 return &e->public;
364 }
365
366 METHOD(radius_message_t, add, void,
367 private_radius_message_t *this, radius_attribute_type_t type, chunk_t data)
368 {
369 rattr_t *attribute;
370
371 if (type == RAT_USER_PASSWORD && !this->password.len)
372 {
373 /* store a null-padded password */
374 this->password = chunk_alloc(round_up(data.len, HASH_SIZE_MD5));
375 memset(this->password.ptr + data.len, 0, this->password.len - data.len);
376 memcpy(this->password.ptr, data.ptr, data.len);
377 return;
378 }
379
380 data.len = min(data.len, MAX_RADIUS_ATTRIBUTE_SIZE);
381 this->msg = realloc(this->msg,
382 ntohs(this->msg->length) + sizeof(rattr_t) + data.len);
383 attribute = ((void*)this->msg) + ntohs(this->msg->length);
384 attribute->type = type;
385 attribute->length = data.len + sizeof(rattr_t);
386 memcpy(attribute->value, data.ptr, data.len);
387 this->msg->length = htons(ntohs(this->msg->length) + attribute->length);
388 }
389
390 METHOD(radius_message_t, crypt, bool,
391 private_radius_message_t *this, chunk_t salt, chunk_t in, chunk_t out,
392 chunk_t secret, hasher_t *hasher)
393 {
394 char b[HASH_SIZE_MD5];
395
396 /**
397 * From RFC2548 (encryption):
398 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
399 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
400 * . . .
401 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
402 *
403 * P/C = Plain/Crypted => in/out
404 * S = secret
405 * R = authenticator
406 * A = salt
407 */
408 if (in.len != out.len)
409 {
410 return FALSE;
411 }
412 if (in.len % HASH_SIZE_MD5 || in.len < HASH_SIZE_MD5)
413 {
414 return FALSE;
415 }
416 if (out.ptr != in.ptr)
417 {
418 memcpy(out.ptr, in.ptr, in.len);
419 }
420 /* Preparse seed for first round:
421 * b(1) = MD5(S + R + A) */
422 if (!hasher->get_hash(hasher, secret, NULL) ||
423 !hasher->get_hash(hasher,
424 chunk_from_thing(this->msg->authenticator), NULL) ||
425 !hasher->get_hash(hasher, salt, b))
426 {
427 return FALSE;
428 }
429 while (in.len)
430 {
431 /* p(i) = b(i) xor c(1) */
432 memxor(out.ptr, b, HASH_SIZE_MD5);
433
434 out = chunk_skip(out, HASH_SIZE_MD5);
435 if (out.len)
436 {
437 /* Prepare seed for next round::
438 * b(i) = MD5(S + c(i-1)) */
439 if (!hasher->get_hash(hasher, secret, NULL) ||
440 !hasher->get_hash(hasher,
441 chunk_create(in.ptr, HASH_SIZE_MD5), b))
442 {
443 return FALSE;
444 }
445 }
446 in = chunk_skip(in, HASH_SIZE_MD5);
447 }
448 return TRUE;
449 }
450
451 METHOD(radius_message_t, sign, bool,
452 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
453 hasher_t *hasher, signer_t *signer, rng_t *rng, bool msg_auth)
454 {
455 if (rng)
456 {
457 /* build Request-Authenticator */
458 if (!rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator))
459 {
460 return FALSE;
461 }
462 }
463 else
464 {
465 /* prepare build of Response-Authenticator */
466 if (req_auth)
467 {
468 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
469 }
470 else
471 {
472 memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
473 }
474 }
475
476 if (this->password.len)
477 {
478 /* encrypt password inline */
479 if (!crypt(this, chunk_empty, this->password, this->password,
480 secret, hasher))
481 {
482 return FALSE;
483 }
484 add(this, RAT_USER_PASSWORD, this->password);
485 chunk_clear(&this->password);
486 }
487
488 if (msg_auth)
489 {
490 char buf[HASH_SIZE_MD5];
491
492 /* build Message-Authenticator attribute, using 16 null bytes */
493 memset(buf, 0, sizeof(buf));
494 add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
495 if (!signer->get_signature(signer,
496 chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
497 ((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5))
498 {
499 return FALSE;
500 }
501 }
502
503 if (!rng)
504 {
505 chunk_t msg;
506
507 /* build Response-Authenticator */
508 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
509 if (!hasher->get_hash(hasher, msg, NULL) ||
510 !hasher->get_hash(hasher, secret, this->msg->authenticator))
511 {
512 return FALSE;
513 }
514 }
515 return TRUE;
516 }
517
518 METHOD(radius_message_t, verify, bool,
519 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
520 hasher_t *hasher, signer_t *signer)
521 {
522 char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5];
523 enumerator_t *enumerator;
524 int type;
525 chunk_t data, msg;
526 bool has_eap = FALSE, has_auth = FALSE;
527
528 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
529
530 if (this->msg->code != RMC_ACCESS_REQUEST)
531 {
532 /* replace Response by Request Authenticator for verification */
533 memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
534 if (req_auth)
535 {
536 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
537 }
538 else
539 {
540 memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
541 }
542
543 /* verify Response-Authenticator */
544 if (!hasher->get_hash(hasher, msg, NULL) ||
545 !hasher->get_hash(hasher, secret, buf) ||
546 !memeq_const(buf, res_auth, HASH_SIZE_MD5))
547 {
548 DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
549 return FALSE;
550 }
551 }
552
553 /* verify Message-Authenticator attribute */
554 enumerator = create_enumerator(this);
555 while (enumerator->enumerate(enumerator, &type, &data))
556 {
557 if (type == RAT_MESSAGE_AUTHENTICATOR)
558 {
559 if (data.len != HASH_SIZE_MD5)
560 {
561 DBG1(DBG_CFG, "RADIUS Message-Authenticator invalid length");
562 enumerator->destroy(enumerator);
563 return FALSE;
564 }
565 memcpy(buf, data.ptr, data.len);
566 memset(data.ptr, 0, data.len);
567 if (signer->verify_signature(signer, msg,
568 chunk_create(buf, sizeof(buf))))
569 {
570 /* restore Message-Authenticator */
571 memcpy(data.ptr, buf, data.len);
572 has_auth = TRUE;
573 break;
574 }
575 else
576 {
577 DBG1(DBG_CFG, "RADIUS Message-Authenticator verification failed");
578 enumerator->destroy(enumerator);
579 return FALSE;
580 }
581 }
582 else if (type == RAT_EAP_MESSAGE)
583 {
584 has_eap = TRUE;
585 }
586 }
587 enumerator->destroy(enumerator);
588
589 if (this->msg->code != RMC_ACCESS_REQUEST)
590 {
591 /* restore Response-Authenticator */
592 memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
593 }
594
595 if (has_eap && !has_auth)
596 { /* Message-Authenticator is required if we have an EAP-Message */
597 DBG1(DBG_CFG, "RADIUS Message-Authenticator attribute missing");
598 return FALSE;
599 }
600 return TRUE;
601 }
602
603 METHOD(radius_message_t, get_code, radius_message_code_t,
604 private_radius_message_t *this)
605 {
606 return this->msg->code;
607 }
608
609 METHOD(radius_message_t, get_identifier, u_int8_t,
610 private_radius_message_t *this)
611 {
612 return this->msg->identifier;
613 }
614
615 METHOD(radius_message_t, set_identifier, void,
616 private_radius_message_t *this, u_int8_t identifier)
617 {
618 this->msg->identifier = identifier;
619 }
620
621 METHOD(radius_message_t, get_authenticator, u_int8_t*,
622 private_radius_message_t *this)
623 {
624 return this->msg->authenticator;
625 }
626
627
628 METHOD(radius_message_t, get_encoding, chunk_t,
629 private_radius_message_t *this)
630 {
631 return chunk_create((u_char*)this->msg, ntohs(this->msg->length));
632 }
633
634 METHOD(radius_message_t, destroy, void,
635 private_radius_message_t *this)
636 {
637 chunk_clear(&this->password);
638 free(this->msg);
639 free(this);
640 }
641
642 /**
643 * Generic constructor
644 */
645 static private_radius_message_t *radius_message_create_empty()
646 {
647 private_radius_message_t *this;
648
649 INIT(this,
650 .public = {
651 .create_enumerator = _create_enumerator,
652 .create_vendor_enumerator = _create_vendor_enumerator,
653 .add = _add,
654 .get_code = _get_code,
655 .get_identifier = _get_identifier,
656 .set_identifier = _set_identifier,
657 .get_authenticator = _get_authenticator,
658 .get_encoding = _get_encoding,
659 .sign = _sign,
660 .verify = _verify,
661 .crypt = _crypt,
662 .destroy = _destroy,
663 },
664 );
665
666 return this;
667 }
668
669 /**
670 * See header
671 */
672 radius_message_t *radius_message_create(radius_message_code_t code)
673 {
674 private_radius_message_t *this = radius_message_create_empty();
675
676 INIT(this->msg,
677 .code = code,
678 .identifier = 0,
679 .length = htons(sizeof(rmsg_t)),
680 );
681
682 return &this->public;
683 }
684
685 /**
686 * See header
687 */
688 radius_message_t *radius_message_parse(chunk_t data)
689 {
690 private_radius_message_t *this = radius_message_create_empty();
691
692 this->msg = malloc(data.len);
693 memcpy(this->msg, data.ptr, data.len);
694 if (data.len < sizeof(rmsg_t) ||
695 ntohs(this->msg->length) != data.len)
696 {
697 DBG1(DBG_IKE, "RADIUS message has invalid length");
698 destroy(this);
699 return NULL;
700 }
701 return &this->public;
702 }