/src/hostap/src/eap_peer/eap_mschapv2.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * EAP peer method: EAP-MSCHAPV2 (draft-kamath-pppext-eap-mschapv2-00.txt) |
3 | | * Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi> |
4 | | * |
5 | | * This software may be distributed under the terms of the BSD license. |
6 | | * See README for more details. |
7 | | * |
8 | | * This file implements EAP peer part of EAP-MSCHAPV2 method (EAP type 26). |
9 | | * draft-kamath-pppext-eap-mschapv2-00.txt defines the Microsoft EAP CHAP |
10 | | * Extensions Protocol, Version 2, for mutual authentication and key |
11 | | * derivation. This encapsulates MS-CHAP-v2 protocol which is defined in |
12 | | * RFC 2759. Use of EAP-MSCHAPV2 derived keys with MPPE cipher is described in |
13 | | * RFC 3079. |
14 | | */ |
15 | | |
16 | | #include "includes.h" |
17 | | |
18 | | #include "common.h" |
19 | | #include "crypto/ms_funcs.h" |
20 | | #include "crypto/random.h" |
21 | | #include "common/wpa_ctrl.h" |
22 | | #include "mschapv2.h" |
23 | | #include "eap_i.h" |
24 | | #include "eap_config.h" |
25 | | |
26 | | |
27 | | #ifdef _MSC_VER |
28 | | #pragma pack(push, 1) |
29 | | #endif /* _MSC_VER */ |
30 | | |
31 | | struct eap_mschapv2_hdr { |
32 | | u8 op_code; /* MSCHAPV2_OP_* */ |
33 | | u8 mschapv2_id; /* usually same as EAP identifier; must be changed |
34 | | * for challenges, but not for success/failure */ |
35 | | u8 ms_length[2]; /* Note: misaligned; length - 5 */ |
36 | | /* followed by data */ |
37 | | } STRUCT_PACKED; |
38 | | |
39 | | /* Response Data field */ |
40 | | struct ms_response { |
41 | | u8 peer_challenge[MSCHAPV2_CHAL_LEN]; |
42 | | u8 reserved[8]; |
43 | | u8 nt_response[MSCHAPV2_NT_RESPONSE_LEN]; |
44 | | u8 flags; |
45 | | } STRUCT_PACKED; |
46 | | |
47 | | /* Change-Password Data field */ |
48 | | struct ms_change_password { |
49 | | u8 encr_password[516]; |
50 | | u8 encr_hash[16]; |
51 | | u8 peer_challenge[MSCHAPV2_CHAL_LEN]; |
52 | | u8 reserved[8]; |
53 | | u8 nt_response[MSCHAPV2_NT_RESPONSE_LEN]; |
54 | | u8 flags[2]; |
55 | | } STRUCT_PACKED; |
56 | | |
57 | | #ifdef _MSC_VER |
58 | | #pragma pack(pop) |
59 | | #endif /* _MSC_VER */ |
60 | | |
61 | 9.61k | #define MSCHAPV2_OP_CHALLENGE 1 |
62 | 8.61k | #define MSCHAPV2_OP_RESPONSE 2 |
63 | 6.72k | #define MSCHAPV2_OP_SUCCESS 3 |
64 | 47.6k | #define MSCHAPV2_OP_FAILURE 4 |
65 | 0 | #define MSCHAPV2_OP_CHANGE_PASSWORD 7 |
66 | | |
67 | | #define ERROR_RESTRICTED_LOGON_HOURS 646 |
68 | | #define ERROR_ACCT_DISABLED 647 |
69 | 110k | #define ERROR_PASSWD_EXPIRED 648 |
70 | | #define ERROR_NO_DIALIN_PERMISSION 649 |
71 | 625k | #define ERROR_AUTHENTICATION_FAILURE 691 |
72 | | #define ERROR_CHANGING_PASSWORD 709 |
73 | | |
74 | 17.2k | #define PASSWD_CHANGE_CHAL_LEN 16 |
75 | 0 | #define MSCHAPV2_KEY_LEN 16 |
76 | | |
77 | | |
78 | | struct eap_mschapv2_data { |
79 | | u8 auth_response[MSCHAPV2_AUTH_RESPONSE_LEN]; |
80 | | int auth_response_valid; |
81 | | |
82 | | int prev_error; |
83 | | u8 passwd_change_challenge[PASSWD_CHANGE_CHAL_LEN]; |
84 | | int passwd_change_challenge_valid; |
85 | | int passwd_change_version; |
86 | | |
87 | | /* Optional challenge values generated in EAP-FAST Phase 1 negotiation |
88 | | */ |
89 | | u8 *peer_challenge; |
90 | | u8 *auth_challenge; |
91 | | |
92 | | int phase2; |
93 | | u8 master_key[MSCHAPV2_MASTER_KEY_LEN]; |
94 | | int master_key_valid; |
95 | | int success; |
96 | | |
97 | | struct wpabuf *prev_challenge; |
98 | | }; |
99 | | |
100 | | |
101 | | static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv); |
102 | | |
103 | | |
104 | | static void * eap_mschapv2_init(struct eap_sm *sm) |
105 | 1.19k | { |
106 | 1.19k | struct eap_mschapv2_data *data; |
107 | 1.19k | data = os_zalloc(sizeof(*data)); |
108 | 1.19k | if (data == NULL) |
109 | 0 | return NULL; |
110 | | |
111 | 1.19k | wpa_printf(MSG_DEBUG, "EAP-%sMSCHAPv2 init%s%s", |
112 | 1.19k | sm->eap_fast_mschapv2 ? "FAST-" : "", |
113 | 1.19k | sm->peer_challenge && sm->auth_challenge ? |
114 | 1.19k | " with preset challenges" : "", |
115 | 1.19k | sm->init_phase2 ? " for Phase 2" : ""); |
116 | | |
117 | 1.19k | if (sm->peer_challenge) { |
118 | 0 | data->peer_challenge = os_memdup(sm->peer_challenge, |
119 | 0 | MSCHAPV2_CHAL_LEN); |
120 | 0 | if (data->peer_challenge == NULL) { |
121 | 0 | eap_mschapv2_deinit(sm, data); |
122 | 0 | return NULL; |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | 1.19k | if (sm->auth_challenge) { |
127 | 0 | data->auth_challenge = os_memdup(sm->auth_challenge, |
128 | 0 | MSCHAPV2_CHAL_LEN); |
129 | 0 | if (data->auth_challenge == NULL) { |
130 | 0 | eap_mschapv2_deinit(sm, data); |
131 | 0 | return NULL; |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | 1.19k | data->phase2 = sm->init_phase2; |
136 | | |
137 | 1.19k | return data; |
138 | 1.19k | } |
139 | | |
140 | | |
141 | | static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv) |
142 | 1.19k | { |
143 | 1.19k | struct eap_mschapv2_data *data = priv; |
144 | 1.19k | os_free(data->peer_challenge); |
145 | 1.19k | os_free(data->auth_challenge); |
146 | 1.19k | wpabuf_free(data->prev_challenge); |
147 | 1.19k | bin_clear_free(data, sizeof(*data)); |
148 | 1.19k | } |
149 | | |
150 | | |
151 | | static struct wpabuf * eap_mschapv2_challenge_reply( |
152 | | struct eap_sm *sm, struct eap_mschapv2_data *data, u8 id, |
153 | | u8 mschapv2_id, const u8 *auth_challenge) |
154 | 8.61k | { |
155 | 8.61k | struct wpabuf *resp; |
156 | 8.61k | struct eap_mschapv2_hdr *ms; |
157 | 8.61k | u8 *peer_challenge; |
158 | 8.61k | int ms_len; |
159 | 8.61k | struct ms_response *r; |
160 | 8.61k | size_t identity_len, password_len; |
161 | 8.61k | const u8 *identity, *password; |
162 | 8.61k | int pwhash; |
163 | | |
164 | 8.61k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Generating Challenge Response"); |
165 | | |
166 | 8.61k | identity = eap_get_config_identity(sm, &identity_len); |
167 | 8.61k | password = eap_get_config_password2(sm, &password_len, &pwhash); |
168 | 8.61k | if (identity == NULL || password == NULL) |
169 | 0 | return NULL; |
170 | | |
171 | 8.61k | ms_len = sizeof(*ms) + 1 + sizeof(*r) + identity_len; |
172 | 8.61k | resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, ms_len, |
173 | 8.61k | EAP_CODE_RESPONSE, id); |
174 | 8.61k | if (resp == NULL) |
175 | 0 | return NULL; |
176 | | |
177 | 8.61k | ms = wpabuf_put(resp, sizeof(*ms)); |
178 | 8.61k | ms->op_code = MSCHAPV2_OP_RESPONSE; |
179 | 8.61k | ms->mschapv2_id = mschapv2_id; |
180 | 8.61k | if (data->prev_error) { |
181 | | /* |
182 | | * TODO: this does not seem to be enough when processing two |
183 | | * or more failure messages. IAS did not increment mschapv2_id |
184 | | * in its own packets, but it seemed to expect the peer to |
185 | | * increment this for all packets(?). |
186 | | */ |
187 | 8.29k | ms->mschapv2_id++; |
188 | 8.29k | } |
189 | 8.61k | WPA_PUT_BE16(ms->ms_length, ms_len); |
190 | | |
191 | 8.61k | wpabuf_put_u8(resp, sizeof(*r)); /* Value-Size */ |
192 | | |
193 | | /* Response */ |
194 | 8.61k | r = wpabuf_put(resp, sizeof(*r)); |
195 | 8.61k | peer_challenge = r->peer_challenge; |
196 | 8.61k | if (data->peer_challenge) { |
197 | 0 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge generated " |
198 | 0 | "in Phase 1"); |
199 | 0 | peer_challenge = data->peer_challenge; |
200 | 0 | os_memset(r->peer_challenge, 0, MSCHAPV2_CHAL_LEN); |
201 | 8.61k | } else if (random_get_bytes(peer_challenge, MSCHAPV2_CHAL_LEN)) { |
202 | 0 | wpabuf_free(resp); |
203 | 0 | return NULL; |
204 | 0 | } |
205 | 8.61k | os_memset(r->reserved, 0, 8); |
206 | 8.61k | if (data->auth_challenge) { |
207 | 0 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge generated " |
208 | 0 | "in Phase 1"); |
209 | 0 | auth_challenge = data->auth_challenge; |
210 | 0 | } |
211 | 8.61k | if (mschapv2_derive_response(identity, identity_len, password, |
212 | 8.61k | password_len, pwhash, auth_challenge, |
213 | 8.61k | peer_challenge, r->nt_response, |
214 | 8.61k | data->auth_response, data->master_key)) { |
215 | 0 | wpa_printf(MSG_ERROR, "EAP-MSCHAPV2: Failed to derive " |
216 | 0 | "response"); |
217 | 0 | wpabuf_free(resp); |
218 | 0 | return NULL; |
219 | 0 | } |
220 | 8.61k | data->auth_response_valid = 1; |
221 | 8.61k | data->master_key_valid = 1; |
222 | | |
223 | 8.61k | r->flags = 0; /* reserved, must be zero */ |
224 | | |
225 | 8.61k | wpabuf_put_data(resp, identity, identity_len); |
226 | 8.61k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d " |
227 | 8.61k | "(response)", id, ms->mschapv2_id); |
228 | 8.61k | return resp; |
229 | 8.61k | } |
230 | | |
231 | | |
232 | | /** |
233 | | * eap_mschapv2_process - Process an EAP-MSCHAPv2 challenge message |
234 | | * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() |
235 | | * @data: Pointer to private EAP method data from eap_mschapv2_init() |
236 | | * @ret: Return values from EAP request validation and processing |
237 | | * @req: Pointer to EAP-MSCHAPv2 header from the request |
238 | | * @req_len: Length of the EAP-MSCHAPv2 data |
239 | | * @id: EAP identifier used in the request |
240 | | * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if |
241 | | * no reply available |
242 | | */ |
243 | | static struct wpabuf * eap_mschapv2_challenge( |
244 | | struct eap_sm *sm, struct eap_mschapv2_data *data, |
245 | | struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, |
246 | | size_t req_len, u8 id) |
247 | 9.61k | { |
248 | 9.61k | size_t len, challenge_len; |
249 | 9.61k | const u8 *pos, *challenge; |
250 | | |
251 | 9.61k | if (eap_get_config_identity(sm, &len) == NULL || |
252 | 9.61k | eap_get_config_password(sm, &len) == NULL) |
253 | 0 | return NULL; |
254 | | |
255 | 9.61k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received challenge"); |
256 | 9.61k | if (req_len < sizeof(*req) + 1) { |
257 | 0 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge data " |
258 | 0 | "(len %lu)", (unsigned long) req_len); |
259 | 0 | ret->ignore = true; |
260 | 0 | return NULL; |
261 | 0 | } |
262 | 9.61k | pos = (const u8 *) (req + 1); |
263 | 9.61k | challenge_len = *pos++; |
264 | 9.61k | len = req_len - sizeof(*req) - 1; |
265 | 9.61k | if (challenge_len != MSCHAPV2_CHAL_LEN) { |
266 | 743 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid challenge length " |
267 | 743 | "%lu", (unsigned long) challenge_len); |
268 | 743 | ret->ignore = true; |
269 | 743 | return NULL; |
270 | 743 | } |
271 | | |
272 | 8.87k | if (len < challenge_len) { |
273 | 262 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge" |
274 | 262 | " packet: len=%lu challenge_len=%lu", |
275 | 262 | (unsigned long) len, (unsigned long) challenge_len); |
276 | 262 | ret->ignore = true; |
277 | 262 | return NULL; |
278 | 262 | } |
279 | | |
280 | 8.61k | if (data->passwd_change_challenge_valid) { |
281 | 335 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Using challenge from the " |
282 | 335 | "failure message"); |
283 | 335 | challenge = data->passwd_change_challenge; |
284 | 335 | } else |
285 | 8.27k | challenge = pos; |
286 | 8.61k | pos += challenge_len; |
287 | 8.61k | len -= challenge_len; |
288 | 8.61k | wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Authentication Servername", |
289 | 8.61k | pos, len); |
290 | | |
291 | 8.61k | ret->ignore = false; |
292 | 8.61k | ret->methodState = METHOD_MAY_CONT; |
293 | 8.61k | ret->decision = DECISION_FAIL; |
294 | 8.61k | ret->allowNotifications = true; |
295 | | |
296 | 8.61k | return eap_mschapv2_challenge_reply(sm, data, id, req->mschapv2_id, |
297 | 8.61k | challenge); |
298 | 8.87k | } |
299 | | |
300 | | |
301 | | static void eap_mschapv2_password_changed(struct eap_sm *sm, |
302 | | struct eap_mschapv2_data *data) |
303 | 0 | { |
304 | 0 | struct eap_peer_config *config = eap_get_config(sm); |
305 | 0 | if (config && config->new_password) { |
306 | 0 | wpa_msg(sm->msg_ctx, MSG_INFO, |
307 | 0 | WPA_EVENT_PASSWORD_CHANGED |
308 | 0 | "EAP-MSCHAPV2: Password changed successfully"); |
309 | 0 | data->prev_error = 0; |
310 | 0 | bin_clear_free(config->password, config->password_len); |
311 | 0 | if (config->flags & EAP_CONFIG_FLAGS_EXT_PASSWORD) { |
312 | | /* TODO: update external storage */ |
313 | 0 | } else if (config->flags & EAP_CONFIG_FLAGS_PASSWORD_NTHASH) { |
314 | 0 | config->password = os_malloc(16); |
315 | 0 | config->password_len = 16; |
316 | 0 | if (config->password && |
317 | 0 | nt_password_hash(config->new_password, |
318 | 0 | config->new_password_len, |
319 | 0 | config->password)) { |
320 | 0 | bin_clear_free(config->password, |
321 | 0 | config->password_len); |
322 | 0 | config->password = NULL; |
323 | 0 | config->password_len = 0; |
324 | 0 | } |
325 | 0 | bin_clear_free(config->new_password, |
326 | 0 | config->new_password_len); |
327 | 0 | } else { |
328 | 0 | config->password = config->new_password; |
329 | 0 | config->password_len = config->new_password_len; |
330 | 0 | } |
331 | 0 | config->new_password = NULL; |
332 | 0 | config->new_password_len = 0; |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | |
337 | | /** |
338 | | * eap_mschapv2_process - Process an EAP-MSCHAPv2 success message |
339 | | * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() |
340 | | * @data: Pointer to private EAP method data from eap_mschapv2_init() |
341 | | * @ret: Return values from EAP request validation and processing |
342 | | * @req: Pointer to EAP-MSCHAPv2 header from the request |
343 | | * @req_len: Length of the EAP-MSCHAPv2 data |
344 | | * @id: EAP identifier used in th erequest |
345 | | * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if |
346 | | * no reply available |
347 | | */ |
348 | | static struct wpabuf * eap_mschapv2_success(struct eap_sm *sm, |
349 | | struct eap_mschapv2_data *data, |
350 | | struct eap_method_ret *ret, |
351 | | const struct eap_mschapv2_hdr *req, |
352 | | size_t req_len, u8 id) |
353 | 6.72k | { |
354 | 6.72k | struct wpabuf *resp; |
355 | 6.72k | const u8 *pos; |
356 | 6.72k | size_t len; |
357 | | |
358 | 6.72k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received success"); |
359 | 6.72k | len = req_len - sizeof(*req); |
360 | 6.72k | pos = (const u8 *) (req + 1); |
361 | 6.72k | if (!data->auth_response_valid || |
362 | 6.72k | mschapv2_verify_auth_response(data->auth_response, pos, len)) { |
363 | 6.72k | wpa_printf(MSG_WARNING, "EAP-MSCHAPV2: Invalid authenticator " |
364 | 6.72k | "response in success request"); |
365 | 6.72k | ret->methodState = METHOD_DONE; |
366 | 6.72k | ret->decision = DECISION_FAIL; |
367 | 6.72k | return NULL; |
368 | 6.72k | } |
369 | 0 | pos += 2 + 2 * MSCHAPV2_AUTH_RESPONSE_LEN; |
370 | 0 | len -= 2 + 2 * MSCHAPV2_AUTH_RESPONSE_LEN; |
371 | 0 | while (len > 0 && *pos == ' ') { |
372 | 0 | pos++; |
373 | 0 | len--; |
374 | 0 | } |
375 | 0 | wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Success message", |
376 | 0 | pos, len); |
377 | 0 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Authentication succeeded"); |
378 | | |
379 | | /* Note: Only op_code of the EAP-MSCHAPV2 header is included in success |
380 | | * message. */ |
381 | 0 | resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, 1, |
382 | 0 | EAP_CODE_RESPONSE, id); |
383 | 0 | if (resp == NULL) { |
384 | 0 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Failed to allocate " |
385 | 0 | "buffer for success response"); |
386 | 0 | ret->ignore = true; |
387 | 0 | return NULL; |
388 | 0 | } |
389 | | |
390 | 0 | wpabuf_put_u8(resp, MSCHAPV2_OP_SUCCESS); /* op_code */ |
391 | |
|
392 | 0 | ret->methodState = METHOD_DONE; |
393 | 0 | ret->decision = DECISION_UNCOND_SUCC; |
394 | 0 | ret->allowNotifications = false; |
395 | 0 | data->success = 1; |
396 | |
|
397 | 0 | if (data->prev_error == ERROR_PASSWD_EXPIRED) |
398 | 0 | eap_mschapv2_password_changed(sm, data); |
399 | |
|
400 | 0 | return resp; |
401 | 0 | } |
402 | | |
403 | | |
404 | | static int eap_mschapv2_failure_txt(struct eap_sm *sm, |
405 | | struct eap_mschapv2_data *data, char *txt) |
406 | 27.7k | { |
407 | 27.7k | char *pos, *msg = ""; |
408 | 27.7k | int retry = 1; |
409 | 27.7k | struct eap_peer_config *config = eap_get_config(sm); |
410 | | |
411 | | /* For example: |
412 | | * E=691 R=1 C=<32 octets hex challenge> V=3 M=Authentication Failure |
413 | | */ |
414 | | |
415 | 27.7k | pos = txt; |
416 | | |
417 | 27.7k | if (pos && os_strncmp(pos, "E=", 2) == 0) { |
418 | 6.36k | pos += 2; |
419 | 6.36k | data->prev_error = atoi(pos); |
420 | 6.36k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: error %d", |
421 | 6.36k | data->prev_error); |
422 | 6.36k | pos = os_strchr(pos, ' '); |
423 | 6.36k | if (pos) |
424 | 5.52k | pos++; |
425 | 6.36k | } |
426 | | |
427 | 27.7k | if (pos && os_strncmp(pos, "R=", 2) == 0) { |
428 | 1.46k | pos += 2; |
429 | 1.46k | retry = atoi(pos); |
430 | 1.46k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: retry is %sallowed", |
431 | 1.46k | retry == 1 ? "" : "not "); |
432 | 1.46k | pos = os_strchr(pos, ' '); |
433 | 1.46k | if (pos) |
434 | 802 | pos++; |
435 | 1.46k | } |
436 | | |
437 | 27.7k | if (pos && os_strncmp(pos, "C=", 2) == 0) { |
438 | 15.8k | int hex_len; |
439 | 15.8k | pos += 2; |
440 | 15.8k | hex_len = os_strchr(pos, ' ') - (char *) pos; |
441 | 15.8k | if (hex_len == PASSWD_CHANGE_CHAL_LEN * 2) { |
442 | 1.14k | if (hexstr2bin(pos, data->passwd_change_challenge, |
443 | 1.14k | PASSWD_CHANGE_CHAL_LEN)) { |
444 | 884 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid " |
445 | 884 | "failure challenge"); |
446 | 884 | } else { |
447 | 263 | wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: failure " |
448 | 263 | "challenge", |
449 | 263 | data->passwd_change_challenge, |
450 | 263 | PASSWD_CHANGE_CHAL_LEN); |
451 | 263 | data->passwd_change_challenge_valid = 1; |
452 | 263 | } |
453 | 14.7k | } else { |
454 | 14.7k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid failure " |
455 | 14.7k | "challenge len %d", hex_len); |
456 | 14.7k | } |
457 | 15.8k | pos = os_strchr(pos, ' '); |
458 | 15.8k | if (pos) |
459 | 1.41k | pos++; |
460 | 15.8k | } else { |
461 | 11.8k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: required challenge field " |
462 | 11.8k | "was not present in failure message"); |
463 | 11.8k | } |
464 | | |
465 | 27.7k | if (pos && os_strncmp(pos, "V=", 2) == 0) { |
466 | 883 | pos += 2; |
467 | 883 | data->passwd_change_version = atoi(pos); |
468 | 883 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: password changing " |
469 | 883 | "protocol version %d", data->passwd_change_version); |
470 | 883 | pos = os_strchr(pos, ' '); |
471 | 883 | if (pos) |
472 | 226 | pos++; |
473 | 883 | } |
474 | | |
475 | 27.7k | if (pos && os_strncmp(pos, "M=", 2) == 0) { |
476 | 655 | pos += 2; |
477 | 655 | msg = pos; |
478 | 655 | } |
479 | 27.7k | if (data->prev_error == ERROR_AUTHENTICATION_FAILURE && retry && |
480 | 27.7k | config && config->phase2 && |
481 | 27.7k | os_strstr(config->phase2, "mschapv2_retry=0")) { |
482 | 0 | wpa_printf(MSG_DEBUG, |
483 | 0 | "EAP-MSCHAPV2: mark password retry disabled based on local configuration"); |
484 | 0 | retry = 0; |
485 | 0 | } |
486 | 27.7k | wpa_msg(sm->msg_ctx, MSG_WARNING, |
487 | 27.7k | "EAP-MSCHAPV2: failure message: '%s' (retry %sallowed, error " |
488 | 27.7k | "%d)", |
489 | 27.7k | msg, retry == 1 ? "" : "not ", data->prev_error); |
490 | 27.7k | if (data->prev_error == ERROR_PASSWD_EXPIRED && |
491 | 27.7k | data->passwd_change_version == 3 && config) { |
492 | 433 | if (config->new_password == NULL) { |
493 | 433 | wpa_msg(sm->msg_ctx, MSG_INFO, |
494 | 433 | "EAP-MSCHAPV2: Password expired - password " |
495 | 433 | "change required"); |
496 | 433 | eap_sm_request_new_password(sm); |
497 | 433 | } |
498 | 27.2k | } else if (retry == 1 && config) { |
499 | | /* TODO: could prevent the current password from being used |
500 | | * again at least for some period of time */ |
501 | 26.0k | if (!config->mschapv2_retry) |
502 | 7.87k | eap_sm_request_identity(sm); |
503 | 26.0k | eap_sm_request_password(sm); |
504 | 26.0k | config->mschapv2_retry = 1; |
505 | 26.0k | } else if (config) { |
506 | | /* TODO: prevent retries using same username/password */ |
507 | 1.22k | config->mschapv2_retry = 0; |
508 | 1.22k | } |
509 | | |
510 | 27.7k | return retry == 1; |
511 | 27.7k | } |
512 | | |
513 | | |
514 | | static struct wpabuf * eap_mschapv2_change_password( |
515 | | struct eap_sm *sm, struct eap_mschapv2_data *data, |
516 | | struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, u8 id) |
517 | 0 | { |
518 | | #ifdef CONFIG_NO_RC4 |
519 | | wpa_printf(MSG_ERROR, |
520 | | "EAP-MSCHAPV2: RC4 not support in the build - cannot change password"); |
521 | | return NULL; |
522 | | #else /* CONFIG_NO_RC4 */ |
523 | 0 | struct wpabuf *resp; |
524 | 0 | int ms_len; |
525 | 0 | const u8 *username, *password, *new_password; |
526 | 0 | size_t username_len, password_len, new_password_len; |
527 | 0 | struct eap_mschapv2_hdr *ms; |
528 | 0 | struct ms_change_password *cp; |
529 | 0 | u8 password_hash[16], password_hash_hash[16]; |
530 | 0 | int pwhash; |
531 | |
|
532 | 0 | username = eap_get_config_identity(sm, &username_len); |
533 | 0 | password = eap_get_config_password2(sm, &password_len, &pwhash); |
534 | 0 | new_password = eap_get_config_new_password(sm, &new_password_len); |
535 | 0 | if (username == NULL || password == NULL || new_password == NULL) |
536 | 0 | return NULL; |
537 | | |
538 | 0 | username = mschapv2_remove_domain(username, &username_len); |
539 | |
|
540 | 0 | ret->ignore = false; |
541 | 0 | ret->methodState = METHOD_MAY_CONT; |
542 | 0 | ret->decision = DECISION_COND_SUCC; |
543 | 0 | ret->allowNotifications = true; |
544 | |
|
545 | 0 | ms_len = sizeof(*ms) + sizeof(*cp); |
546 | 0 | resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, ms_len, |
547 | 0 | EAP_CODE_RESPONSE, id); |
548 | 0 | if (resp == NULL) |
549 | 0 | return NULL; |
550 | | |
551 | 0 | ms = wpabuf_put(resp, sizeof(*ms)); |
552 | 0 | ms->op_code = MSCHAPV2_OP_CHANGE_PASSWORD; |
553 | 0 | ms->mschapv2_id = req->mschapv2_id + 1; |
554 | 0 | WPA_PUT_BE16(ms->ms_length, ms_len); |
555 | 0 | cp = wpabuf_put(resp, sizeof(*cp)); |
556 | | |
557 | | /* Encrypted-Password */ |
558 | 0 | if (pwhash) { |
559 | 0 | if (encrypt_pw_block_with_password_hash( |
560 | 0 | new_password, new_password_len, |
561 | 0 | password, cp->encr_password)) |
562 | 0 | goto fail; |
563 | 0 | } else { |
564 | 0 | if (new_password_encrypted_with_old_nt_password_hash( |
565 | 0 | new_password, new_password_len, |
566 | 0 | password, password_len, cp->encr_password)) |
567 | 0 | goto fail; |
568 | 0 | } |
569 | | |
570 | | /* Encrypted-Hash */ |
571 | 0 | if (pwhash) { |
572 | 0 | u8 new_password_hash[16]; |
573 | 0 | if (nt_password_hash(new_password, new_password_len, |
574 | 0 | new_password_hash) || |
575 | 0 | nt_password_hash_encrypted_with_block(password, |
576 | 0 | new_password_hash, |
577 | 0 | cp->encr_hash)) |
578 | 0 | goto fail; |
579 | 0 | } else { |
580 | 0 | if (old_nt_password_hash_encrypted_with_new_nt_password_hash( |
581 | 0 | new_password, new_password_len, |
582 | 0 | password, password_len, cp->encr_hash)) |
583 | 0 | goto fail; |
584 | 0 | } |
585 | | |
586 | | /* Peer-Challenge */ |
587 | 0 | if (random_get_bytes(cp->peer_challenge, MSCHAPV2_CHAL_LEN)) |
588 | 0 | goto fail; |
589 | | |
590 | | /* Reserved, must be zero */ |
591 | 0 | os_memset(cp->reserved, 0, 8); |
592 | | |
593 | | /* NT-Response */ |
594 | 0 | wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", |
595 | 0 | data->passwd_change_challenge, PASSWD_CHANGE_CHAL_LEN); |
596 | 0 | wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge", |
597 | 0 | cp->peer_challenge, MSCHAPV2_CHAL_LEN); |
598 | 0 | wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username", |
599 | 0 | username, username_len); |
600 | 0 | wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: new password", |
601 | 0 | new_password, new_password_len); |
602 | 0 | generate_nt_response(data->passwd_change_challenge, cp->peer_challenge, |
603 | 0 | username, username_len, |
604 | 0 | new_password, new_password_len, |
605 | 0 | cp->nt_response); |
606 | 0 | wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: NT-Response", |
607 | 0 | cp->nt_response, MSCHAPV2_NT_RESPONSE_LEN); |
608 | | |
609 | | /* Authenticator response is not really needed yet, but calculate it |
610 | | * here so that challenges need not be saved. */ |
611 | 0 | generate_authenticator_response(new_password, new_password_len, |
612 | 0 | cp->peer_challenge, |
613 | 0 | data->passwd_change_challenge, |
614 | 0 | username, username_len, |
615 | 0 | cp->nt_response, data->auth_response); |
616 | 0 | data->auth_response_valid = 1; |
617 | | |
618 | | /* Likewise, generate master_key here since we have the needed data |
619 | | * available. */ |
620 | 0 | if (nt_password_hash(new_password, new_password_len, password_hash) || |
621 | 0 | hash_nt_password_hash(password_hash, password_hash_hash) || |
622 | 0 | get_master_key(password_hash_hash, cp->nt_response, |
623 | 0 | data->master_key)) { |
624 | 0 | data->auth_response_valid = 0; |
625 | 0 | goto fail; |
626 | 0 | } |
627 | 0 | data->master_key_valid = 1; |
628 | | |
629 | | /* Flags */ |
630 | 0 | os_memset(cp->flags, 0, 2); |
631 | |
|
632 | 0 | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d " |
633 | 0 | "(change pw)", id, ms->mschapv2_id); |
634 | |
|
635 | 0 | return resp; |
636 | | |
637 | 0 | fail: |
638 | 0 | wpabuf_free(resp); |
639 | 0 | return NULL; |
640 | 0 | #endif /* CONFIG_NO_RC4 */ |
641 | 0 | } |
642 | | |
643 | | |
644 | | /** |
645 | | * eap_mschapv2_process - Process an EAP-MSCHAPv2 failure message |
646 | | * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() |
647 | | * @data: Pointer to private EAP method data from eap_mschapv2_init() |
648 | | * @ret: Return values from EAP request validation and processing |
649 | | * @req: Pointer to EAP-MSCHAPv2 header from the request |
650 | | * @req_len: Length of the EAP-MSCHAPv2 data |
651 | | * @id: EAP identifier used in th erequest |
652 | | * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if |
653 | | * no reply available |
654 | | */ |
655 | | static struct wpabuf * eap_mschapv2_failure(struct eap_sm *sm, |
656 | | struct eap_mschapv2_data *data, |
657 | | struct eap_method_ret *ret, |
658 | | const struct eap_mschapv2_hdr *req, |
659 | | size_t req_len, u8 id) |
660 | 27.7k | { |
661 | 27.7k | struct wpabuf *resp; |
662 | 27.7k | const u8 *msdata = (const u8 *) (req + 1); |
663 | 27.7k | char *buf; |
664 | 27.7k | size_t len = req_len - sizeof(*req); |
665 | 27.7k | int retry = 0; |
666 | | |
667 | 27.7k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received failure"); |
668 | 27.7k | wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Failure data", |
669 | 27.7k | msdata, len); |
670 | | /* |
671 | | * eap_mschapv2_failure_txt() expects a nul terminated string, so we |
672 | | * must allocate a large enough temporary buffer to create that since |
673 | | * the received message does not include nul termination. |
674 | | */ |
675 | 27.7k | buf = dup_binstr(msdata, len); |
676 | 27.7k | if (buf) { |
677 | 27.7k | retry = eap_mschapv2_failure_txt(sm, data, buf); |
678 | 27.7k | os_free(buf); |
679 | 27.7k | } |
680 | | |
681 | 27.7k | ret->ignore = false; |
682 | 27.7k | ret->methodState = METHOD_DONE; |
683 | 27.7k | ret->decision = DECISION_FAIL; |
684 | 27.7k | ret->allowNotifications = false; |
685 | | |
686 | 27.7k | if (data->prev_error == ERROR_PASSWD_EXPIRED && |
687 | 27.7k | data->passwd_change_version == 3) { |
688 | 433 | struct eap_peer_config *config = eap_get_config(sm); |
689 | 433 | if (config && config->new_password) |
690 | 0 | return eap_mschapv2_change_password(sm, data, ret, req, |
691 | 0 | id); |
692 | 433 | if (config && config->pending_req_new_password) |
693 | 0 | return NULL; |
694 | 27.2k | } else if (retry && data->prev_error == ERROR_AUTHENTICATION_FAILURE) { |
695 | | /* TODO: could try to retry authentication, e.g, after having |
696 | | * changed the username/password. In this case, EAP MS-CHAP-v2 |
697 | | * Failure Response would not be sent here. */ |
698 | 7.71k | return NULL; |
699 | 7.71k | } |
700 | | |
701 | | /* Note: Only op_code of the EAP-MSCHAPV2 header is included in failure |
702 | | * message. */ |
703 | 19.9k | resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, 1, |
704 | 19.9k | EAP_CODE_RESPONSE, id); |
705 | 19.9k | if (resp == NULL) |
706 | 0 | return NULL; |
707 | | |
708 | 19.9k | wpabuf_put_u8(resp, MSCHAPV2_OP_FAILURE); /* op_code */ |
709 | | |
710 | 19.9k | return resp; |
711 | 19.9k | } |
712 | | |
713 | | |
714 | | static int eap_mschapv2_check_config(struct eap_sm *sm) |
715 | 1.63M | { |
716 | 1.63M | size_t len; |
717 | | |
718 | 1.63M | if (eap_get_config_identity(sm, &len) == NULL) { |
719 | 0 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Identity not configured"); |
720 | 0 | eap_sm_request_identity(sm); |
721 | 0 | return -1; |
722 | 0 | } |
723 | | |
724 | 1.63M | if (eap_get_config_password(sm, &len) == NULL) { |
725 | 0 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Password not configured"); |
726 | 0 | eap_sm_request_password(sm); |
727 | 0 | return -1; |
728 | 0 | } |
729 | | |
730 | 1.63M | return 0; |
731 | 1.63M | } |
732 | | |
733 | | |
734 | | static int eap_mschapv2_check_mslen(struct eap_sm *sm, size_t len, |
735 | | const struct eap_mschapv2_hdr *ms) |
736 | 48.0k | { |
737 | 48.0k | size_t ms_len = WPA_GET_BE16(ms->ms_length); |
738 | | |
739 | 48.0k | if (ms_len == len) |
740 | 46.4k | return 0; |
741 | | |
742 | 1.64k | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid header: len=%lu " |
743 | 1.64k | "ms_len=%lu", (unsigned long) len, (unsigned long) ms_len); |
744 | 1.64k | if (sm->workaround) { |
745 | | /* Some authentication servers use invalid ms_len, |
746 | | * ignore it for interoperability. */ |
747 | 0 | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: workaround, ignore" |
748 | 0 | " invalid ms_len %lu (len %lu)", |
749 | 0 | (unsigned long) ms_len, |
750 | 0 | (unsigned long) len); |
751 | 0 | return 0; |
752 | 0 | } |
753 | | |
754 | 1.64k | return -1; |
755 | 1.64k | } |
756 | | |
757 | | |
758 | | static void eap_mschapv2_copy_challenge(struct eap_mschapv2_data *data, |
759 | | const struct wpabuf *reqData) |
760 | 2.12k | { |
761 | | /* |
762 | | * Store a copy of the challenge message, so that it can be processed |
763 | | * again in case retry is allowed after a possible failure. |
764 | | */ |
765 | 2.12k | wpabuf_free(data->prev_challenge); |
766 | 2.12k | data->prev_challenge = wpabuf_dup(reqData); |
767 | 2.12k | } |
768 | | |
769 | | |
770 | | /** |
771 | | * eap_mschapv2_process - Process an EAP-MSCHAPv2 request |
772 | | * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() |
773 | | * @priv: Pointer to private EAP method data from eap_mschapv2_init() |
774 | | * @ret: Return values from EAP request validation and processing |
775 | | * @reqData: EAP request to be processed (eapReqData) |
776 | | * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if |
777 | | * no reply available |
778 | | */ |
779 | | static struct wpabuf * eap_mschapv2_process(struct eap_sm *sm, void *priv, |
780 | | struct eap_method_ret *ret, |
781 | | const struct wpabuf *reqData) |
782 | 1.63M | { |
783 | 1.63M | struct eap_mschapv2_data *data = priv; |
784 | 1.63M | struct eap_peer_config *config = eap_get_config(sm); |
785 | 1.63M | const struct eap_mschapv2_hdr *ms; |
786 | 1.63M | int using_prev_challenge = 0; |
787 | 1.63M | const u8 *pos; |
788 | 1.63M | size_t len; |
789 | 1.63M | u8 id; |
790 | | |
791 | 1.63M | if (eap_mschapv2_check_config(sm)) { |
792 | 0 | ret->ignore = true; |
793 | 0 | return NULL; |
794 | 0 | } |
795 | | |
796 | 1.63M | if (config->mschapv2_retry && data->prev_challenge && |
797 | 1.63M | data->prev_error == ERROR_AUTHENTICATION_FAILURE) { |
798 | 7.48k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Replacing pending packet " |
799 | 7.48k | "with the previous challenge"); |
800 | | |
801 | 7.48k | reqData = data->prev_challenge; |
802 | 7.48k | using_prev_challenge = 1; |
803 | 7.48k | config->mschapv2_retry = 0; |
804 | 7.48k | } |
805 | | |
806 | 1.63M | pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, reqData, |
807 | 1.63M | &len); |
808 | 1.63M | if (pos == NULL || len < sizeof(*ms) + 1) { |
809 | 1.59M | ret->ignore = true; |
810 | 1.59M | return NULL; |
811 | 1.59M | } |
812 | | |
813 | 48.0k | ms = (const struct eap_mschapv2_hdr *) pos; |
814 | 48.0k | if (eap_mschapv2_check_mslen(sm, len, ms)) { |
815 | 1.64k | ret->ignore = true; |
816 | 1.64k | return NULL; |
817 | 1.64k | } |
818 | | |
819 | 46.4k | id = eap_get_id(reqData); |
820 | 46.4k | wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: RX identifier %d mschapv2_id %d", |
821 | 46.4k | id, ms->mschapv2_id); |
822 | | |
823 | 46.4k | switch (ms->op_code) { |
824 | 9.61k | case MSCHAPV2_OP_CHALLENGE: |
825 | 9.61k | if (!using_prev_challenge) |
826 | 2.12k | eap_mschapv2_copy_challenge(data, reqData); |
827 | 9.61k | return eap_mschapv2_challenge(sm, data, ret, ms, len, id); |
828 | 6.72k | case MSCHAPV2_OP_SUCCESS: |
829 | 6.72k | return eap_mschapv2_success(sm, data, ret, ms, len, id); |
830 | 27.7k | case MSCHAPV2_OP_FAILURE: |
831 | 27.7k | return eap_mschapv2_failure(sm, data, ret, ms, len, id); |
832 | 2.39k | default: |
833 | 2.39k | wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Unknown op %d - ignored", |
834 | 2.39k | ms->op_code); |
835 | 2.39k | ret->ignore = true; |
836 | 2.39k | return NULL; |
837 | 46.4k | } |
838 | 46.4k | } |
839 | | |
840 | | |
841 | | static bool eap_mschapv2_isKeyAvailable(struct eap_sm *sm, void *priv) |
842 | 0 | { |
843 | 0 | struct eap_mschapv2_data *data = priv; |
844 | 0 | return data->success && data->master_key_valid; |
845 | 0 | } |
846 | | |
847 | | |
848 | | static u8 * eap_mschapv2_getKey(struct eap_sm *sm, void *priv, size_t *len) |
849 | 0 | { |
850 | 0 | struct eap_mschapv2_data *data = priv; |
851 | 0 | u8 *key; |
852 | 0 | int key_len; |
853 | 0 | bool first_is_send; |
854 | |
|
855 | 0 | if (!data->master_key_valid || !data->success) |
856 | 0 | return NULL; |
857 | | |
858 | 0 | key_len = 2 * MSCHAPV2_KEY_LEN; |
859 | |
|
860 | 0 | key = os_malloc(key_len); |
861 | 0 | if (key == NULL) |
862 | 0 | return NULL; |
863 | | |
864 | | /* |
865 | | * [MS-CHAP], 3.1.5.1 (Master Session Key (MSK) Derivation |
866 | | * MSK = MasterReceiveKey + MasterSendKey + 32 bytes zeros (padding) |
867 | | * On a Peer: |
868 | | * MS-MPPE-Recv-Key = MasterSendKey |
869 | | * MS-MPPE-Send-Key = MasterReceiveKey |
870 | | * |
871 | | * RFC 5422, 3.2.3 (Authenticating Using EAP-FAST-MSCHAPv2) |
872 | | * MSK = MasterSendKey + MasterReceiveKey |
873 | | * (i.e., reverse order and no padding) |
874 | | * |
875 | | * On Peer, EAP-MSCHAPv2 starts with Send key and EAP-FAST-MSCHAPv2 |
876 | | * starts with Receive key. |
877 | | */ |
878 | 0 | first_is_send = !sm->eap_fast_mschapv2; |
879 | 0 | if (get_asymetric_start_key(data->master_key, key, MSCHAPV2_KEY_LEN, |
880 | 0 | first_is_send, 0) < 0 || |
881 | 0 | get_asymetric_start_key(data->master_key, key + MSCHAPV2_KEY_LEN, |
882 | 0 | MSCHAPV2_KEY_LEN, !first_is_send, 0) < 0) { |
883 | 0 | os_free(key); |
884 | 0 | return NULL; |
885 | 0 | } |
886 | | |
887 | 0 | wpa_hexdump_key(MSG_DEBUG, "EAP-MSCHAPV2: Derived key", |
888 | 0 | key, key_len); |
889 | |
|
890 | 0 | *len = key_len; |
891 | 0 | return key; |
892 | 0 | } |
893 | | |
894 | | |
895 | | /** |
896 | | * eap_peer_mschapv2_register - Register EAP-MSCHAPv2 peer method |
897 | | * Returns: 0 on success, -1 on failure |
898 | | * |
899 | | * This function is used to register EAP-MSCHAPv2 peer method into the EAP |
900 | | * method list. |
901 | | */ |
902 | | int eap_peer_mschapv2_register(void) |
903 | 1.19k | { |
904 | 1.19k | struct eap_method *eap; |
905 | | |
906 | 1.19k | eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, |
907 | 1.19k | EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, |
908 | 1.19k | "MSCHAPV2"); |
909 | 1.19k | if (eap == NULL) |
910 | 0 | return -1; |
911 | | |
912 | 1.19k | eap->init = eap_mschapv2_init; |
913 | 1.19k | eap->deinit = eap_mschapv2_deinit; |
914 | 1.19k | eap->process = eap_mschapv2_process; |
915 | 1.19k | eap->isKeyAvailable = eap_mschapv2_isKeyAvailable; |
916 | 1.19k | eap->getKey = eap_mschapv2_getKey; |
917 | | |
918 | 1.19k | return eap_peer_method_register(eap); |
919 | 1.19k | } |