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