Add a radius message method to enumerate vendor specific 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 /**
71 * Described in header.
72 */
73 void libradius_init(void)
74 {
75 /* empty */
76 }
77
78 ENUM_BEGIN(radius_message_code_names, RMC_ACCESS_REQUEST, RMC_ACCOUNTING_RESPONSE,
79 "Access-Request",
80 "Access-Accept",
81 "Access-Reject",
82 "Accounting-Request",
83 "Accounting-Response");
84 ENUM_NEXT(radius_message_code_names, RMC_ACCESS_CHALLENGE, RMC_ACCESS_CHALLENGE, RMC_ACCOUNTING_RESPONSE,
85 "Access-Challenge");
86 ENUM_NEXT(radius_message_code_names, RMC_DISCONNECT_REQUEST, RMC_COA_NAK, RMC_ACCESS_CHALLENGE,
87 "Disconnect-Request",
88 "Disconnect-ACK",
89 "Disconnect-NAK",
90 "CoA-Request",
91 "CoA-ACK",
92 "CoA-NAK");
93 ENUM_END(radius_message_code_names, RMC_COA_NAK);
94
95 ENUM(radius_attribute_type_names, RAT_USER_NAME, RAT_MIP6_HOME_LINK_PREFIX,
96 "User-Name",
97 "User-Password",
98 "CHAP-Password",
99 "NAS-IP-Address",
100 "NAS-Port",
101 "Service-Type",
102 "Framed-Protocol",
103 "Framed-IP-Address",
104 "Framed-IP-Netmask",
105 "Framed-Routing",
106 "Filter-Id",
107 "Framed-MTU",
108 "Framed-Compression",
109 "Login-IP-Host",
110 "Login-Service",
111 "Login-TCP-Port",
112 "Unassigned",
113 "Reply-Message",
114 "Callback-Number",
115 "Callback-Id",
116 "Unassigned",
117 "Framed-Route",
118 "Framed-IPX-Network",
119 "State",
120 "Class",
121 "Vendor-Specific",
122 "Session-Timeout",
123 "Idle-Timeout",
124 "Termination-Action",
125 "Called-Station-Id",
126 "Calling-Station-Id",
127 "NAS-Identifier",
128 "Proxy-State",
129 "Login-LAT-Service",
130 "Login-LAT-Node",
131 "Login-LAT-Group",
132 "Framed-AppleTalk-Link",
133 "Framed-AppleTalk-Network",
134 "Framed-AppleTalk-Zone",
135 "Acct-Status-Type",
136 "Acct-Delay-Time",
137 "Acct-Input-Octets",
138 "Acct-Output-Octets",
139 "Acct-Session-Id",
140 "Acct-Authentic",
141 "Acct-Session-Time",
142 "Acct-Input-Packets",
143 "Acct-Output-Packets",
144 "Acct-Terminate-Cause",
145 "Acct-Multi-Session-Id",
146 "Acct-Link-Count",
147 "Acct-Input-Gigawords",
148 "Acct-Output-Gigawords",
149 "Unassigned",
150 "Event-Timestamp",
151 "Egress-VLANID",
152 "Ingress-Filters",
153 "Egress-VLAN-Name",
154 "User-Priority-Table",
155 "CHAP-Challenge",
156 "NAS-Port-Type",
157 "Port-Limit",
158 "Login-LAT-Port",
159 "Tunnel-Type",
160 "Tunnel-Medium-Type",
161 "Tunnel-Client-Endpoint",
162 "Tunnel-Server-Endpoint",
163 "Acct-Tunnel-Connection",
164 "Tunnel-Password",
165 "ARAP-Password",
166 "ARAP-Features",
167 "ARAP-Zone-Access",
168 "ARAP-Security",
169 "ARAP-Security-Data",
170 "Password-Retry",
171 "Prompt",
172 "Connect-Info",
173 "Configuration-Token",
174 "EAP-Message",
175 "Message-Authenticator",
176 "Tunnel-Private-Group-ID",
177 "Tunnel-Assignment-ID",
178 "Tunnel-Preference",
179 "ARAP-Challenge-Response",
180 "Acct-Interim-Interval",
181 "Acct-Tunnel-Packets-Lost",
182 "NAS-Port-Id",
183 "Framed-Pool",
184 "CUI",
185 "Tunnel-Client-Auth-ID",
186 "Tunnel-Server-Auth-ID",
187 "NAS-Filter-Rule",
188 "Unassigned",
189 "Originating-Line-Info",
190 "NAS-IPv6-Address",
191 "Framed-Interface-Id",
192 "Framed-IPv6-Prefix",
193 "Login-IPv6-Host",
194 "Framed-IPv6-Route",
195 "Framed-IPv6-Pool",
196 "Error-Cause",
197 "EAP-Key-Name",
198 "Digest-Response",
199 "Digest-Realm",
200 "Digest-Nonce",
201 "Digest-Response-Auth",
202 "Digest-Nextnonce",
203 "Digest-Method",
204 "Digest-URI",
205 "Digest-Qop",
206 "Digest-Algorithm",
207 "Digest-Entity-Body-Hash",
208 "Digest-CNonce",
209 "Digest-Nonce-Count",
210 "Digest-Username",
211 "Digest-Opaque",
212 "Digest-Auth-Param",
213 "Digest-AKA-Auts",
214 "Digest-Domain",
215 "Digest-Stale",
216 "Digest-HA1",
217 "SIP-AOR",
218 "Delegated-IPv6-Prefix",
219 "MIP6-Feature-Vector",
220 "MIP6-Home-Link-Prefix");
221
222 /**
223 * Attribute enumerator implementation
224 */
225 typedef struct {
226 /** implements enumerator interface */
227 enumerator_t public;
228 /** currently pointing attribute */
229 rattr_t *next;
230 /** bytes left */
231 int left;
232 } attribute_enumerator_t;
233
234 METHOD(enumerator_t, attribute_enumerate, bool,
235 attribute_enumerator_t *this, int *type, chunk_t *data)
236 {
237 if (this->left == 0)
238 {
239 return FALSE;
240 }
241 if (this->left < sizeof(rattr_t) ||
242 this->left < this->next->length)
243 {
244 DBG1(DBG_IKE, "RADIUS message truncated");
245 return FALSE;
246 }
247 *type = this->next->type;
248 data->ptr = this->next->value;
249 data->len = this->next->length - sizeof(rattr_t);
250 this->left -= this->next->length;
251 this->next = ((void*)this->next) + this->next->length;
252 return TRUE;
253 }
254
255 METHOD(radius_message_t, create_enumerator, enumerator_t*,
256 private_radius_message_t *this)
257 {
258 attribute_enumerator_t *e;
259
260 if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t))
261 {
262 return enumerator_create_empty();
263 }
264 INIT(e,
265 .public = {
266 .enumerate = (void*)_attribute_enumerate,
267 .destroy = (void*)free,
268 },
269 .next = (rattr_t*)this->msg->attributes,
270 .left = ntohs(this->msg->length) - sizeof(rmsg_t),
271 );
272 return &e->public;
273 }
274
275 /**
276 * Vendor attribute enumerator implementation
277 */
278 typedef struct {
279 /** implements enumerator interface */
280 enumerator_t public;
281 /** inner attribute enumerator */
282 enumerator_t *inner;
283 /** current vendor ID */
284 u_int32_t vendor;
285 /** reader for current vendor ID */
286 bio_reader_t *reader;
287 } vendor_enumerator_t;
288
289 METHOD(enumerator_t, vendor_enumerate, bool,
290 vendor_enumerator_t *this, int *vendor, int *type, chunk_t *data)
291 {
292 chunk_t inner_data;
293 int inner_type;
294 u_int8_t type8, len;
295
296 while (TRUE)
297 {
298 if (this->reader)
299 {
300 if (this->reader->remaining(this->reader) >= 2 &&
301 this->reader->read_uint8(this->reader, &type8) &&
302 this->reader->read_uint8(this->reader, &len) && len >= 2 &&
303 this->reader->read_data(this->reader, len - 2, data))
304 {
305 *vendor = this->vendor;
306 *type = type8;
307 return TRUE;
308 }
309 this->reader->destroy(this->reader);
310 this->reader = NULL;
311 }
312 if (this->inner->enumerate(this->inner, &inner_type, &inner_data))
313 {
314 if (inner_type == RAT_VENDOR_SPECIFIC)
315 {
316 this->reader = bio_reader_create(inner_data);
317 if (!this->reader->read_uint32(this->reader, &this->vendor))
318 {
319 this->reader->destroy(this->reader);
320 this->reader = NULL;
321 }
322 }
323 }
324 else
325 {
326 return FALSE;
327 }
328 }
329 }
330 METHOD(enumerator_t, vendor_destroy, void,
331 vendor_enumerator_t *this)
332 {
333 DESTROY_IF(this->reader);
334 this->inner->destroy(this->inner);
335 free(this);
336 }
337
338 METHOD(radius_message_t, create_vendor_enumerator, enumerator_t*,
339 private_radius_message_t *this)
340 {
341 vendor_enumerator_t *e;
342
343 INIT(e,
344 .public = {
345 .enumerate = (void*)_vendor_enumerate,
346 .destroy = _vendor_destroy,
347 },
348 .inner = create_enumerator(this),
349 );
350
351 return &e->public;
352 }
353
354 METHOD(radius_message_t, add, void,
355 private_radius_message_t *this, radius_attribute_type_t type, chunk_t data)
356 {
357 rattr_t *attribute;
358
359 data.len = min(data.len, MAX_RADIUS_ATTRIBUTE_SIZE);
360 this->msg = realloc(this->msg,
361 ntohs(this->msg->length) + sizeof(rattr_t) + data.len);
362 attribute = ((void*)this->msg) + ntohs(this->msg->length);
363 attribute->type = type;
364 attribute->length = data.len + sizeof(rattr_t);
365 memcpy(attribute->value, data.ptr, data.len);
366 this->msg->length = htons(ntohs(this->msg->length) + attribute->length);
367 }
368
369 METHOD(radius_message_t, sign, bool,
370 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
371 hasher_t *hasher, signer_t *signer, rng_t *rng, bool msg_auth)
372 {
373 if (rng)
374 {
375 /* build Request-Authenticator */
376 if (!rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator))
377 {
378 return FALSE;
379 }
380 }
381 else
382 {
383 /* prepare build of Response-Authenticator */
384 if (req_auth)
385 {
386 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
387 }
388 else
389 {
390 memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
391 }
392 }
393
394 if (msg_auth)
395 {
396 char buf[HASH_SIZE_MD5];
397
398 /* build Message-Authenticator attribute, using 16 null bytes */
399 memset(buf, 0, sizeof(buf));
400 add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
401 if (!signer->get_signature(signer,
402 chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
403 ((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5))
404 {
405 return FALSE;
406 }
407 }
408
409 if (!rng)
410 {
411 chunk_t msg;
412
413 /* build Response-Authenticator */
414 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
415 if (!hasher->get_hash(hasher, msg, NULL) ||
416 !hasher->get_hash(hasher, secret, this->msg->authenticator))
417 {
418 return FALSE;
419 }
420 }
421 return TRUE;
422 }
423
424 METHOD(radius_message_t, verify, bool,
425 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
426 hasher_t *hasher, signer_t *signer)
427 {
428 char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5];
429 enumerator_t *enumerator;
430 int type;
431 chunk_t data, msg;
432 bool has_eap = FALSE, has_auth = FALSE;
433
434 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
435
436 if (this->msg->code != RMC_ACCESS_REQUEST)
437 {
438 /* replace Response by Request Authenticator for verification */
439 memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
440 if (req_auth)
441 {
442 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
443 }
444 else
445 {
446 memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
447 }
448
449 /* verify Response-Authenticator */
450 if (!hasher->get_hash(hasher, msg, NULL) ||
451 !hasher->get_hash(hasher, secret, buf) ||
452 !memeq(buf, res_auth, HASH_SIZE_MD5))
453 {
454 DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
455 return FALSE;
456 }
457 }
458
459 /* verify Message-Authenticator attribute */
460 enumerator = create_enumerator(this);
461 while (enumerator->enumerate(enumerator, &type, &data))
462 {
463 if (type == RAT_MESSAGE_AUTHENTICATOR)
464 {
465 if (data.len != HASH_SIZE_MD5)
466 {
467 DBG1(DBG_CFG, "RADIUS Message-Authenticator invalid length");
468 enumerator->destroy(enumerator);
469 return FALSE;
470 }
471 memcpy(buf, data.ptr, data.len);
472 memset(data.ptr, 0, data.len);
473 if (signer->verify_signature(signer, msg,
474 chunk_create(buf, sizeof(buf))))
475 {
476 /* restore Message-Authenticator */
477 memcpy(data.ptr, buf, data.len);
478 has_auth = TRUE;
479 break;
480 }
481 else
482 {
483 DBG1(DBG_CFG, "RADIUS Message-Authenticator verification failed");
484 enumerator->destroy(enumerator);
485 return FALSE;
486 }
487 }
488 else if (type == RAT_EAP_MESSAGE)
489 {
490 has_eap = TRUE;
491 }
492 }
493 enumerator->destroy(enumerator);
494
495 if (this->msg->code != RMC_ACCESS_REQUEST)
496 {
497 /* restore Response-Authenticator */
498 memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
499 }
500
501 if (has_eap && !has_auth)
502 { /* Message-Authenticator is required if we have an EAP-Message */
503 DBG1(DBG_CFG, "RADIUS Message-Authenticator attribute missing");
504 return FALSE;
505 }
506 return TRUE;
507 }
508
509 METHOD(radius_message_t, get_code, radius_message_code_t,
510 private_radius_message_t *this)
511 {
512 return this->msg->code;
513 }
514
515 METHOD(radius_message_t, get_identifier, u_int8_t,
516 private_radius_message_t *this)
517 {
518 return this->msg->identifier;
519 }
520
521 METHOD(radius_message_t, set_identifier, void,
522 private_radius_message_t *this, u_int8_t identifier)
523 {
524 this->msg->identifier = identifier;
525 }
526
527 METHOD(radius_message_t, get_authenticator, u_int8_t*,
528 private_radius_message_t *this)
529 {
530 return this->msg->authenticator;
531 }
532
533
534 METHOD(radius_message_t, get_encoding, chunk_t,
535 private_radius_message_t *this)
536 {
537 return chunk_create((u_char*)this->msg, ntohs(this->msg->length));
538 }
539
540 METHOD(radius_message_t, destroy, void,
541 private_radius_message_t *this)
542 {
543 free(this->msg);
544 free(this);
545 }
546
547 /**
548 * Generic constructor
549 */
550 static private_radius_message_t *radius_message_create_empty()
551 {
552 private_radius_message_t *this;
553
554 INIT(this,
555 .public = {
556 .create_enumerator = _create_enumerator,
557 .create_vendor_enumerator = _create_vendor_enumerator,
558 .add = _add,
559 .get_code = _get_code,
560 .get_identifier = _get_identifier,
561 .set_identifier = _set_identifier,
562 .get_authenticator = _get_authenticator,
563 .get_encoding = _get_encoding,
564 .sign = _sign,
565 .verify = _verify,
566 .destroy = _destroy,
567 },
568 );
569
570 return this;
571 }
572
573 /**
574 * See header
575 */
576 radius_message_t *radius_message_create(radius_message_code_t code)
577 {
578 private_radius_message_t *this = radius_message_create_empty();
579
580 INIT(this->msg,
581 .code = code,
582 .identifier = 0,
583 .length = htons(sizeof(rmsg_t)),
584 );
585
586 return &this->public;
587 }
588
589 /**
590 * See header
591 */
592 radius_message_t *radius_message_parse(chunk_t data)
593 {
594 private_radius_message_t *this = radius_message_create_empty();
595
596 this->msg = malloc(data.len);
597 memcpy(this->msg, data.ptr, data.len);
598 if (data.len < sizeof(rmsg_t) ||
599 ntohs(this->msg->length) != data.len)
600 {
601 DBG1(DBG_IKE, "RADIUS message has invalid length");
602 destroy(this);
603 return NULL;
604 }
605 return &this->public;
606 }