b3229c28872cd3ce33e946ee47326f3c9c59bf21
[strongswan.git] / src / libcharon / plugins / eap_radius / radius_socket.c
1 /*
2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
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_socket.h"
17
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include <debug.h>
22
23 /**
24 * Vendor-Id of Microsoft specific attributes
25 */
26 #define VENDOR_ID_MICROSOFT 311
27
28 /**
29 * Microsoft specific vendor attributes
30 */
31 #define MS_MPPE_SEND_KEY 16
32 #define MS_MPPE_RECV_KEY 17
33
34 typedef struct private_radius_socket_t private_radius_socket_t;
35
36 /**
37 * Private data of an radius_socket_t object.
38 */
39 struct private_radius_socket_t {
40
41 /**
42 * Public radius_socket_t interface.
43 */
44 radius_socket_t public;
45
46 /**
47 * socket file descriptor
48 */
49 int fd;
50
51 /**
52 * Server address
53 */
54 char *address;
55
56 /**
57 * Server port
58 */
59 u_int16_t port;
60
61 /**
62 * current RADIUS identifier
63 */
64 u_int8_t identifier;
65
66 /**
67 * hasher to use for response verification
68 */
69 hasher_t *hasher;
70
71 /**
72 * HMAC-MD5 signer to build Message-Authenticator attribute
73 */
74 signer_t *signer;
75
76 /**
77 * random number generator for RADIUS request authenticator
78 */
79 rng_t *rng;
80
81 /**
82 * RADIUS secret
83 */
84 chunk_t secret;
85 };
86
87 /**
88 * Check or establish RADIUS connection
89 */
90 static bool check_connection(private_radius_socket_t *this)
91 {
92 if (this->fd == -1)
93 {
94 host_t *server;
95
96 server = host_create_from_dns(this->address, AF_UNSPEC, this->port);
97 if (!server)
98 {
99 DBG1(DBG_CFG, "resolving RADIUS server address '%s' failed",
100 this->address);
101 return FALSE;
102 }
103 this->fd = socket(server->get_family(server), SOCK_DGRAM, IPPROTO_UDP);
104 if (this->fd == -1)
105 {
106 DBG1(DBG_CFG, "opening RADIUS socket for %#H failed: %s",
107 server, strerror(errno));
108 server->destroy(server);
109 return FALSE;
110 }
111 if (connect(this->fd, server->get_sockaddr(server),
112 *server->get_sockaddr_len(server)) < 0)
113 {
114 DBG1(DBG_CFG, "connecting RADIUS socket to %#H failed: %s",
115 server, strerror(errno));
116 server->destroy(server);
117 close(this->fd);
118 this->fd = -1;
119 return FALSE;
120 }
121 server->destroy(server);
122 }
123 return TRUE;
124 }
125
126 METHOD(radius_socket_t, request, radius_message_t*,
127 private_radius_socket_t *this, radius_message_t *request)
128 {
129 chunk_t data;
130 int i;
131
132 /* set Message Identifier */
133 request->set_identifier(request, this->identifier++);
134 /* sign the request */
135 request->sign(request, this->rng, this->signer);
136
137 if (!check_connection(this))
138 {
139 return NULL;
140 }
141
142 data = request->get_encoding(request);
143 /* timeout after 2, 3, 4, 5 seconds */
144 for (i = 2; i <= 5; i++)
145 {
146 radius_message_t *response;
147 bool retransmit = FALSE;
148 struct timeval tv;
149 char buf[4096];
150 fd_set fds;
151 int res;
152
153 if (send(this->fd, data.ptr, data.len, 0) != data.len)
154 {
155 DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
156 return NULL;
157 }
158 tv.tv_sec = i;
159 tv.tv_usec = 0;
160
161 while (TRUE)
162 {
163 FD_ZERO(&fds);
164 FD_SET(this->fd, &fds);
165 res = select(this->fd + 1, &fds, NULL, NULL, &tv);
166 /* TODO: updated tv to time not waited. Linux does this for us. */
167 if (res < 0)
168 { /* failed */
169 DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
170 strerror(errno));
171 break;
172 }
173 if (res == 0)
174 { /* timeout */
175 DBG1(DBG_CFG, "retransmitting RADIUS message");
176 retransmit = TRUE;
177 break;
178 }
179 res = recv(this->fd, buf, sizeof(buf), MSG_DONTWAIT);
180 if (res <= 0)
181 {
182 DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
183 strerror(errno));
184 break;
185 }
186 response = radius_message_parse_response(chunk_create(buf, res));
187 if (response)
188 {
189 if (response->verify(response,
190 request->get_authenticator(request), this->secret,
191 this->hasher, this->signer))
192 {
193 return response;
194 }
195 response->destroy(response);
196 }
197 DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
198 }
199 if (!retransmit)
200 {
201 break;
202 }
203 }
204 DBG1(DBG_CFG, "RADIUS server is not responding");
205 return NULL;
206 }
207
208 /**
209 * Decrypt a MS-MPPE-Send/Recv-Key
210 */
211 static chunk_t decrypt_mppe_key(private_radius_socket_t *this, u_int16_t salt,
212 chunk_t C, radius_message_t *request)
213 {
214 chunk_t A, R, P, seed;
215 u_char *c, *p;
216
217 /**
218 * From RFC2548 (encryption):
219 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
220 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
221 * . . .
222 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
223 */
224
225 if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
226 {
227 return chunk_empty;
228 }
229
230 A = chunk_create((u_char*)&salt, sizeof(salt));
231 R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
232 P = chunk_alloca(C.len);
233 p = P.ptr;
234 c = C.ptr;
235
236 seed = chunk_cata("cc", R, A);
237
238 while (c < C.ptr + C.len)
239 {
240 /* b(i) = MD5(S + c(i-1)) */
241 this->hasher->get_hash(this->hasher, this->secret, NULL);
242 this->hasher->get_hash(this->hasher, seed, p);
243
244 /* p(i) = b(i) xor c(1) */
245 memxor(p, c, HASH_SIZE_MD5);
246
247 /* prepare next round */
248 seed = chunk_create(c, HASH_SIZE_MD5);
249 c += HASH_SIZE_MD5;
250 p += HASH_SIZE_MD5;
251 }
252
253 /* remove truncation, first byte is key length */
254 if (*P.ptr >= P.len)
255 { /* decryption failed? */
256 return chunk_empty;
257 }
258 return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
259 }
260
261 METHOD(radius_socket_t, decrypt_msk, chunk_t,
262 private_radius_socket_t *this, radius_message_t *request,
263 radius_message_t *response)
264 {
265 struct {
266 u_int32_t id;
267 u_int8_t type;
268 u_int8_t length;
269 u_int16_t salt;
270 u_int8_t key[];
271 } __attribute__((packed)) *mppe_key;
272 enumerator_t *enumerator;
273 chunk_t data, send = chunk_empty, recv = chunk_empty;
274 int type;
275
276 enumerator = response->create_enumerator(response);
277 while (enumerator->enumerate(enumerator, &type, &data))
278 {
279 if (type == RAT_VENDOR_SPECIFIC &&
280 data.len > sizeof(*mppe_key))
281 {
282 mppe_key = (void*)data.ptr;
283 if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT &&
284 mppe_key->length == data.len - sizeof(mppe_key->id))
285 {
286 data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key));
287 if (mppe_key->type == MS_MPPE_SEND_KEY)
288 {
289 send = decrypt_mppe_key(this, mppe_key->salt, data, request);
290 }
291 if (mppe_key->type == MS_MPPE_RECV_KEY)
292 {
293 recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
294 }
295 }
296 }
297 }
298 enumerator->destroy(enumerator);
299 if (send.ptr && recv.ptr)
300 {
301 return chunk_cat("mm", recv, send);
302 }
303 chunk_clear(&send);
304 chunk_clear(&recv);
305 return chunk_empty;
306 }
307
308 METHOD(radius_socket_t, destroy, void,
309 private_radius_socket_t *this)
310 {
311 DESTROY_IF(this->hasher);
312 DESTROY_IF(this->signer);
313 DESTROY_IF(this->rng);
314 if (this->fd != -1)
315 {
316 close(this->fd);
317 }
318 free(this);
319 }
320
321 /**
322 * See header
323 */
324 radius_socket_t *radius_socket_create(char *address, u_int16_t port,
325 chunk_t secret)
326 {
327 private_radius_socket_t *this;
328
329 INIT(this,
330 .public = {
331 .request = _request,
332 .decrypt_msk = _decrypt_msk,
333 .destroy = _destroy,
334 },
335 .address = address,
336 .port = port,
337 .fd = -1,
338 );
339
340 this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
341 this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);
342 this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
343 if (!this->hasher || !this->signer || !this->rng)
344 {
345 DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
346 destroy(this);
347 return NULL;
348 }
349 this->secret = secret;
350 this->signer->set_key(this->signer, secret);
351 /* we use a random identifier, helps if we restart often */
352 this->identifier = random();
353
354 return &this->public;
355 }