Coverage Report

Created: 2025-08-29 06:25

/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
}