]>
Commit | Line | Data |
---|---|---|
ce7967c5 MW |
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 | * current RADIUS identifier | |
53 | */ | |
54 | u_int8_t identifier; | |
55 | ||
56 | /** | |
57 | * hasher to use for response verification | |
58 | */ | |
59 | hasher_t *hasher; | |
60 | ||
61 | /** | |
62 | * HMAC-MD5 signer to build Message-Authenticator attribute | |
63 | */ | |
64 | signer_t *signer; | |
65 | ||
66 | /** | |
67 | * random number generator for RADIUS request authenticator | |
68 | */ | |
69 | rng_t *rng; | |
70 | ||
71 | /** | |
72 | * RADIUS secret | |
73 | */ | |
74 | chunk_t secret; | |
75 | }; | |
76 | ||
77 | METHOD(radius_socket_t, request, radius_message_t*, | |
78 | private_radius_socket_t *this, radius_message_t *request) | |
79 | { | |
80 | chunk_t data; | |
81 | int i; | |
82 | ||
83 | /* set Message Identifier */ | |
84 | request->set_identifier(request, this->identifier++); | |
85 | /* sign the request */ | |
86 | request->sign(request, this->rng, this->signer); | |
87 | ||
88 | data = request->get_encoding(request); | |
89 | /* timeout after 2, 3, 4, 5 seconds */ | |
90 | for (i = 2; i <= 5; i++) | |
91 | { | |
92 | radius_message_t *response; | |
93 | bool retransmit = FALSE; | |
94 | struct timeval tv; | |
95 | char buf[4096]; | |
96 | fd_set fds; | |
97 | int res; | |
98 | ||
99 | if (send(this->fd, data.ptr, data.len, 0) != data.len) | |
100 | { | |
101 | DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno)); | |
102 | return NULL; | |
103 | } | |
104 | tv.tv_sec = i; | |
105 | tv.tv_usec = 0; | |
106 | ||
107 | while (TRUE) | |
108 | { | |
109 | FD_ZERO(&fds); | |
110 | FD_SET(this->fd, &fds); | |
111 | res = select(this->fd + 1, &fds, NULL, NULL, &tv); | |
112 | /* TODO: updated tv to time not waited. Linux does this for us. */ | |
113 | if (res < 0) | |
114 | { /* failed */ | |
115 | DBG1(DBG_CFG, "waiting for RADIUS message failed: %s", | |
116 | strerror(errno)); | |
117 | break; | |
118 | } | |
119 | if (res == 0) | |
120 | { /* timeout */ | |
121 | DBG1(DBG_CFG, "retransmitting RADIUS message"); | |
122 | retransmit = TRUE; | |
123 | break; | |
124 | } | |
125 | res = recv(this->fd, buf, sizeof(buf), MSG_DONTWAIT); | |
126 | if (res <= 0) | |
127 | { | |
128 | DBG1(DBG_CFG, "receiving RADIUS message failed: %s", | |
129 | strerror(errno)); | |
130 | break; | |
131 | } | |
132 | response = radius_message_parse_response(chunk_create(buf, res)); | |
133 | if (response) | |
134 | { | |
135 | if (response->verify(response, | |
136 | request->get_authenticator(request), this->secret, | |
137 | this->hasher, this->signer)) | |
138 | { | |
139 | return response; | |
140 | } | |
141 | response->destroy(response); | |
142 | } | |
143 | DBG1(DBG_CFG, "received invalid RADIUS message, ignored"); | |
144 | } | |
145 | if (!retransmit) | |
146 | { | |
147 | break; | |
148 | } | |
149 | } | |
150 | DBG1(DBG_CFG, "RADIUS server is not responding"); | |
151 | return NULL; | |
152 | } | |
153 | ||
154 | /** | |
155 | * Decrypt a MS-MPPE-Send/Recv-Key | |
156 | */ | |
157 | static chunk_t decrypt_mppe_key(private_radius_socket_t *this, u_int16_t salt, | |
158 | chunk_t C, radius_message_t *request) | |
159 | { | |
160 | chunk_t A, R, P, seed; | |
161 | u_char *c, *p; | |
162 | ||
163 | /** | |
164 | * From RFC2548 (encryption): | |
165 | * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1) | |
166 | * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2) | |
167 | * . . . | |
168 | * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i) | |
169 | */ | |
170 | ||
171 | if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5) | |
172 | { | |
173 | return chunk_empty; | |
174 | } | |
175 | ||
176 | A = chunk_create((u_char*)&salt, sizeof(salt)); | |
177 | R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5); | |
178 | P = chunk_alloca(C.len); | |
179 | p = P.ptr; | |
180 | c = C.ptr; | |
181 | ||
182 | seed = chunk_cata("cc", R, A); | |
183 | ||
184 | while (c < C.ptr + C.len) | |
185 | { | |
186 | /* b(i) = MD5(S + c(i-1)) */ | |
187 | this->hasher->get_hash(this->hasher, this->secret, NULL); | |
188 | this->hasher->get_hash(this->hasher, seed, p); | |
189 | ||
190 | /* p(i) = b(i) xor c(1) */ | |
191 | memxor(p, c, HASH_SIZE_MD5); | |
192 | ||
193 | /* prepare next round */ | |
194 | seed = chunk_create(c, HASH_SIZE_MD5); | |
195 | c += HASH_SIZE_MD5; | |
196 | p += HASH_SIZE_MD5; | |
197 | } | |
198 | ||
199 | /* remove truncation, first byte is key length */ | |
200 | if (*P.ptr >= P.len) | |
201 | { /* decryption failed? */ | |
202 | return chunk_empty; | |
203 | } | |
204 | return chunk_clone(chunk_create(P.ptr + 1, *P.ptr)); | |
205 | } | |
206 | ||
207 | METHOD(radius_socket_t, decrypt_msk, chunk_t, | |
208 | private_radius_socket_t *this, radius_message_t *request, | |
209 | radius_message_t *response) | |
210 | { | |
211 | struct { | |
212 | u_int32_t id; | |
213 | u_int8_t type; | |
214 | u_int8_t length; | |
215 | u_int16_t salt; | |
216 | u_int8_t key[]; | |
217 | } __attribute__((packed)) *mppe_key; | |
218 | enumerator_t *enumerator; | |
219 | chunk_t data, send = chunk_empty, recv = chunk_empty; | |
220 | int type; | |
221 | ||
222 | enumerator = response->create_enumerator(response); | |
223 | while (enumerator->enumerate(enumerator, &type, &data)) | |
224 | { | |
225 | if (type == RAT_VENDOR_SPECIFIC && | |
226 | data.len > sizeof(*mppe_key)) | |
227 | { | |
228 | mppe_key = (void*)data.ptr; | |
229 | if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT && | |
230 | mppe_key->length == data.len - sizeof(mppe_key->id)) | |
231 | { | |
232 | data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key)); | |
233 | if (mppe_key->type == MS_MPPE_SEND_KEY) | |
234 | { | |
235 | send = decrypt_mppe_key(this, mppe_key->salt, data, request); | |
236 | } | |
237 | if (mppe_key->type == MS_MPPE_RECV_KEY) | |
238 | { | |
239 | recv = decrypt_mppe_key(this, mppe_key->salt, data, request); | |
240 | } | |
241 | } | |
242 | } | |
243 | } | |
244 | enumerator->destroy(enumerator); | |
245 | if (send.ptr && recv.ptr) | |
246 | { | |
247 | return chunk_cat("mm", recv, send); | |
248 | } | |
249 | chunk_clear(&send); | |
250 | chunk_clear(&recv); | |
251 | return chunk_empty; | |
252 | } | |
253 | ||
254 | METHOD(radius_socket_t, destroy, void, | |
255 | private_radius_socket_t *this) | |
256 | { | |
257 | DESTROY_IF(this->hasher); | |
258 | DESTROY_IF(this->signer); | |
259 | DESTROY_IF(this->rng); | |
3e2419eb | 260 | chunk_clear(&this->secret); |
ce7967c5 MW |
261 | close(this->fd); |
262 | free(this); | |
263 | } | |
264 | ||
265 | /** | |
266 | * See header | |
267 | */ | |
268 | radius_socket_t *radius_socket_create(host_t *host, chunk_t secret) | |
269 | { | |
270 | private_radius_socket_t *this; | |
271 | ||
272 | INIT(this, | |
273 | .public = { | |
274 | .request = _request, | |
275 | .decrypt_msk = _decrypt_msk, | |
276 | .destroy = _destroy, | |
277 | }, | |
278 | ); | |
279 | ||
280 | this->fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP); | |
281 | if (this->fd < 0) | |
282 | { | |
283 | DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno)); | |
284 | free(this); | |
285 | return NULL; | |
286 | } | |
287 | if (connect(this->fd, host->get_sockaddr(host), | |
288 | *host->get_sockaddr_len(host)) < 0) | |
289 | { | |
290 | DBG1(DBG_CFG, "connecting RADIUS socket failed"); | |
291 | close(this->fd); | |
292 | free(this); | |
293 | return NULL; | |
294 | } | |
295 | this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); | |
296 | this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128); | |
297 | this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); | |
298 | if (!this->hasher || !this->signer || !this->rng) | |
299 | { | |
300 | DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required"); | |
301 | destroy(this); | |
302 | return NULL; | |
303 | } | |
3e2419eb | 304 | this->secret = chunk_clone(secret); |
ce7967c5 MW |
305 | this->signer->set_key(this->signer, secret); |
306 | /* we use a random identifier, helps if we restart often */ | |
307 | this->identifier = random(); | |
308 | ||
309 | return &this->public; | |
310 | } |