Removed libcharon dependencies from generic RADIUS protocol support
[strongswan.git] / src / libcharon / plugins / eap_radius / 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 <debug.h>
19 #include <crypto/hashers/hasher.h>
20
21 typedef struct private_radius_message_t private_radius_message_t;
22 typedef struct rmsg_t rmsg_t;
23 typedef struct rattr_t rattr_t;
24
25 /**
26 * RADIUS message header
27 */
28 struct rmsg_t {
29 /** message code, radius_message_code_t */
30 u_int8_t code;
31 /** message identifier */
32 u_int8_t identifier;
33 /** length of Code, Identifier, Length, Authenticator and Attributes */
34 u_int16_t length;
35 /** message authenticator, MD5 hash */
36 u_int8_t authenticator[HASH_SIZE_MD5];
37 /** variable list of packed attributes */
38 u_int8_t attributes[];
39 } __attribute__((packed));
40
41 /**
42 * RADIUS message attribute.
43 */
44 struct rattr_t {
45 /** attribute type, radius_attribute_type_t */
46 u_int8_t type;
47 /** length of the attriubte, including the Type, Length and Value fields */
48 u_int8_t length;
49 /** variable length attribute value */
50 u_int8_t value[];
51 } __attribute__((packed));
52
53 /**
54 * Private data of an radius_message_t object.
55 */
56 struct private_radius_message_t {
57
58 /**
59 * Public radius_message_t interface.
60 */
61 radius_message_t public;
62
63 /**
64 * message data, allocated
65 */
66 rmsg_t *msg;
67 };
68
69 ENUM_BEGIN(radius_message_code_names, RMC_ACCESS_REQUEST, RMC_ACCOUNTING_RESPONSE,
70 "Access-Request",
71 "Access-Accept",
72 "Access-Reject",
73 "Accounting-Request",
74 "Accounting-Response");
75 ENUM_NEXT(radius_message_code_names, RMC_ACCESS_CHALLENGE, RMC_ACCESS_CHALLENGE, RMC_ACCOUNTING_RESPONSE,
76 "Access-Challenge");
77 ENUM_NEXT(radius_message_code_names, RMC_DISCONNECT_REQUEST, RMC_COA_NAK, RMC_ACCESS_CHALLENGE,
78 "Disconnect-Request",
79 "Disconnect-ACK",
80 "Disconnect-NAK",
81 "CoA-Request",
82 "CoA-ACK",
83 "CoA-NAK");
84 ENUM_END(radius_message_code_names, RMC_COA_NAK);
85
86 ENUM(radius_attribute_type_names, RAT_USER_NAME, RAT_MIP6_HOME_LINK_PREFIX,
87 "User-Name",
88 "User-Password",
89 "CHAP-Password",
90 "NAS-IP-Address",
91 "NAS-Port",
92 "Service-Type",
93 "Framed-Protocol",
94 "Framed-IP-Address",
95 "Framed-IP-Netmask",
96 "Framed-Routing",
97 "Filter-Id",
98 "Framed-MTU",
99 "Framed-Compression",
100 "Login-IP-Host",
101 "Login-Service",
102 "Login-TCP-Port",
103 "Unassigned",
104 "Reply-Message",
105 "Callback-Number",
106 "Callback-Id",
107 "Unassigned",
108 "Framed-Route",
109 "Framed-IPX-Network",
110 "State",
111 "Class",
112 "Vendor-Specific",
113 "Session-Timeout",
114 "Idle-Timeout",
115 "Termination-Action",
116 "Called-Station-Id",
117 "Calling-Station-Id",
118 "NAS-Identifier",
119 "Proxy-State",
120 "Login-LAT-Service",
121 "Login-LAT-Node",
122 "Login-LAT-Group",
123 "Framed-AppleTalk-Link",
124 "Framed-AppleTalk-Network",
125 "Framed-AppleTalk-Zone",
126 "Acct-Status-Type",
127 "Acct-Delay-Time",
128 "Acct-Input-Octets",
129 "Acct-Output-Octets",
130 "Acct-Session-Id",
131 "Acct-Authentic",
132 "Acct-Session-Time",
133 "Acct-Input-Packets",
134 "Acct-Output-Packets",
135 "Acct-Terminate-Cause",
136 "Acct-Multi-Session-Id",
137 "Acct-Link-Count",
138 "Acct-Input-Gigawords",
139 "Acct-Output-Gigawords",
140 "Unassigned",
141 "Event-Timestamp",
142 "Egress-VLANID",
143 "Ingress-Filters",
144 "Egress-VLAN-Name",
145 "User-Priority-Table",
146 "CHAP-Challenge",
147 "NAS-Port-Type",
148 "Port-Limit",
149 "Login-LAT-Port",
150 "Tunnel-Type",
151 "Tunnel-Medium-Type",
152 "Tunnel-Client-Endpoint",
153 "Tunnel-Server-Endpoint",
154 "Acct-Tunnel-Connection",
155 "Tunnel-Password",
156 "ARAP-Password",
157 "ARAP-Features",
158 "ARAP-Zone-Access",
159 "ARAP-Security",
160 "ARAP-Security-Data",
161 "Password-Retry",
162 "Prompt",
163 "Connect-Info",
164 "Configuration-Token",
165 "EAP-Message",
166 "Message-Authenticator",
167 "Tunnel-Private-Group-ID",
168 "Tunnel-Assignment-ID",
169 "Tunnel-Preference",
170 "ARAP-Challenge-Response",
171 "Acct-Interim-Interval",
172 "Acct-Tunnel-Packets-Lost",
173 "NAS-Port-Id",
174 "Framed-Pool",
175 "CUI",
176 "Tunnel-Client-Auth-ID",
177 "Tunnel-Server-Auth-ID",
178 "NAS-Filter-Rule",
179 "Unassigned",
180 "Originating-Line-Info",
181 "NAS-IPv6-Address",
182 "Framed-Interface-Id",
183 "Framed-IPv6-Prefix",
184 "Login-IPv6-Host",
185 "Framed-IPv6-Route",
186 "Framed-IPv6-Pool",
187 "Error-Cause",
188 "EAP-Key-Name",
189 "Digest-Response",
190 "Digest-Realm",
191 "Digest-Nonce",
192 "Digest-Response-Auth",
193 "Digest-Nextnonce",
194 "Digest-Method",
195 "Digest-URI",
196 "Digest-Qop",
197 "Digest-Algorithm",
198 "Digest-Entity-Body-Hash",
199 "Digest-CNonce",
200 "Digest-Nonce-Count",
201 "Digest-Username",
202 "Digest-Opaque",
203 "Digest-Auth-Param",
204 "Digest-AKA-Auts",
205 "Digest-Domain",
206 "Digest-Stale",
207 "Digest-HA1",
208 "SIP-AOR",
209 "Delegated-IPv6-Prefix",
210 "MIP6-Feature-Vector",
211 "MIP6-Home-Link-Prefix");
212
213 /**
214 * Attribute enumerator implementation
215 */
216 typedef struct {
217 /** implements enumerator interface */
218 enumerator_t public;
219 /** currently pointing attribute */
220 rattr_t *next;
221 /** bytes left */
222 int left;
223 } attribute_enumerator_t;
224
225 METHOD(enumerator_t, attribute_enumerate, bool,
226 attribute_enumerator_t *this, int *type, chunk_t *data)
227 {
228 if (this->left == 0)
229 {
230 return FALSE;
231 }
232 if (this->left < sizeof(rattr_t) ||
233 this->left < this->next->length)
234 {
235 DBG1(DBG_IKE, "RADIUS message truncated");
236 return FALSE;
237 }
238 *type = this->next->type;
239 data->ptr = this->next->value;
240 data->len = this->next->length - sizeof(rattr_t);
241 this->left -= this->next->length;
242 this->next = ((void*)this->next) + this->next->length;
243 return TRUE;
244 }
245
246 METHOD(radius_message_t, create_enumerator, enumerator_t*,
247 private_radius_message_t *this)
248 {
249 attribute_enumerator_t *e;
250
251 if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t))
252 {
253 return enumerator_create_empty();
254 }
255 INIT(e,
256 .public = {
257 .enumerate = (void*)_attribute_enumerate,
258 .destroy = (void*)free,
259 },
260 .next = (rattr_t*)this->msg->attributes,
261 .left = ntohs(this->msg->length) - sizeof(rmsg_t),
262 );
263 return &e->public;
264 }
265
266 METHOD(radius_message_t, add, void,
267 private_radius_message_t *this, radius_attribute_type_t type, chunk_t data)
268 {
269 rattr_t *attribute;
270
271 data.len = min(data.len, 253);
272 this->msg = realloc(this->msg,
273 ntohs(this->msg->length) + sizeof(rattr_t) + data.len);
274 attribute = ((void*)this->msg) + ntohs(this->msg->length);
275 attribute->type = type;
276 attribute->length = data.len + sizeof(rattr_t);
277 memcpy(attribute->value, data.ptr, data.len);
278 this->msg->length = htons(ntohs(this->msg->length) + attribute->length);
279 }
280
281 METHOD(radius_message_t, sign, void,
282 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
283 hasher_t *hasher, signer_t *signer, rng_t *rng)
284 {
285 if (rng == NULL)
286 {
287 chunk_t msg;
288
289 if (req_auth)
290 {
291 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
292 }
293 else
294 {
295 memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
296 }
297 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
298 hasher->get_hash(hasher, msg, NULL);
299 hasher->get_hash(hasher, secret, this->msg->authenticator);
300 }
301 else
302 {
303 char buf[HASH_SIZE_MD5];
304
305 /* build Request-Authenticator */
306 rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator);
307
308 /* build Message-Authenticator attribute, using 16 null bytes */
309 memset(buf, 0, sizeof(buf));
310 add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
311 signer->get_signature(signer,
312 chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
313 ((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5);
314 }
315 }
316
317 METHOD(radius_message_t, verify, bool,
318 private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
319 hasher_t *hasher, signer_t *signer)
320 {
321 char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5];
322 enumerator_t *enumerator;
323 int type;
324 chunk_t data, msg;
325 bool has_eap = FALSE, has_auth = FALSE;
326
327 /* replace Response by Request Authenticator for verification */
328 memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
329 if (req_auth)
330 {
331 memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
332 }
333 else
334 {
335 memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
336 }
337 msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
338
339 /* verify Response-Authenticator */
340 hasher->get_hash(hasher, msg, NULL);
341 hasher->get_hash(hasher, secret, buf);
342 if (!memeq(buf, res_auth, HASH_SIZE_MD5))
343 {
344 DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
345 return FALSE;
346 }
347
348 /* verify Message-Authenticator attribute */
349 enumerator = create_enumerator(this);
350 while (enumerator->enumerate(enumerator, &type, &data))
351 {
352 if (type == RAT_MESSAGE_AUTHENTICATOR)
353 {
354 if (data.len != HASH_SIZE_MD5)
355 {
356 DBG1(DBG_CFG, "RADIUS Message-Authenticator invalid length");
357 enumerator->destroy(enumerator);
358 return FALSE;
359 }
360 memcpy(buf, data.ptr, data.len);
361 memset(data.ptr, 0, data.len);
362 if (signer->verify_signature(signer, msg,
363 chunk_create(buf, sizeof(buf))))
364 {
365 /* restore Message-Authenticator */
366 memcpy(data.ptr, buf, data.len);
367 has_auth = TRUE;
368 break;
369 }
370 else
371 {
372 DBG1(DBG_CFG, "RADIUS Message-Authenticator verification failed");
373 enumerator->destroy(enumerator);
374 return FALSE;
375 }
376 }
377 else if (type == RAT_EAP_MESSAGE)
378 {
379 has_eap = TRUE;
380 }
381 }
382 enumerator->destroy(enumerator);
383 /* restore Response-Authenticator */
384 memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
385
386 if (has_eap && !has_auth)
387 { /* Message-Authenticator is required if we have an EAP-Message */
388 DBG1(DBG_CFG, "RADIUS Message-Authenticator attribute missing");
389 return FALSE;
390 }
391 return TRUE;
392 }
393
394 METHOD(radius_message_t, get_code, radius_message_code_t,
395 private_radius_message_t *this)
396 {
397 return this->msg->code;
398 }
399
400 METHOD(radius_message_t, get_identifier, u_int8_t,
401 private_radius_message_t *this)
402 {
403 return this->msg->identifier;
404 }
405
406 METHOD(radius_message_t, set_identifier, void,
407 private_radius_message_t *this, u_int8_t identifier)
408 {
409 this->msg->identifier = identifier;
410 }
411
412 METHOD(radius_message_t, get_authenticator, u_int8_t*,
413 private_radius_message_t *this)
414 {
415 return this->msg->authenticator;
416 }
417
418
419 METHOD(radius_message_t, get_encoding, chunk_t,
420 private_radius_message_t *this)
421 {
422 return chunk_create((u_char*)this->msg, ntohs(this->msg->length));
423 }
424
425 METHOD(radius_message_t, destroy, void,
426 private_radius_message_t *this)
427 {
428 free(this->msg);
429 free(this);
430 }
431
432 /**
433 * Generic constructor
434 */
435 static private_radius_message_t *radius_message_create_empty()
436 {
437 private_radius_message_t *this;
438
439 INIT(this,
440 .public = {
441 .create_enumerator = _create_enumerator,
442 .add = _add,
443 .get_code = _get_code,
444 .get_identifier = _get_identifier,
445 .set_identifier = _set_identifier,
446 .get_authenticator = _get_authenticator,
447 .get_encoding = _get_encoding,
448 .sign = _sign,
449 .verify = _verify,
450 .destroy = _destroy,
451 },
452 );
453
454 return this;
455 }
456
457 /**
458 * See header
459 */
460 radius_message_t *radius_message_create(radius_message_code_t code)
461 {
462 private_radius_message_t *this = radius_message_create_empty();
463
464 INIT(this->msg,
465 .code = code,
466 .identifier = 0,
467 .length = htons(sizeof(rmsg_t)),
468 );
469
470 return &this->public;
471 }
472
473 /**
474 * See header
475 */
476 radius_message_t *radius_message_parse(chunk_t data)
477 {
478 private_radius_message_t *this = radius_message_create_empty();
479
480 this->msg = malloc(data.len);
481 memcpy(this->msg, data.ptr, data.len);
482 if (data.len < sizeof(rmsg_t) ||
483 ntohs(this->msg->length) != data.len)
484 {
485 DBG1(DBG_IKE, "RADIUS message has invalid length");
486 destroy(this);
487 return NULL;
488 }
489 return &this->public;
490 }
491