blob: 2a5d763dcfbfb21fa1a73b4afc6f659f61609e92 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.net.eap.statemachine;
18
19import static com.android.internal.net.eap.EapAuthenticator.LOG;
20import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION;
21import static com.android.internal.net.eap.message.EapData.EAP_TYPE_MSCHAP_V2;
22import static com.android.internal.net.eap.message.EapData.EAP_TYPE_STRING;
23import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE;
24import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_RESPONSE;
25import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS;
26import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_FAILURE;
27import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_MSCHAP_V2_SUCCESS;
28import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EAP_OP_CODE_STRING;
29import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureRequest.EAP_ERROR_CODE_STRING;
30import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureResponse.getEapMsChapV2FailureResponse;
31import static com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2SuccessResponse.getEapMsChapV2SuccessResponse;
32
33import android.net.eap.EapSessionConfig.EapMsChapV2Config;
34
35import com.android.internal.annotations.VisibleForTesting;
36import com.android.internal.net.eap.EapResult;
37import com.android.internal.net.eap.EapResult.EapError;
38import com.android.internal.net.eap.EapResult.EapFailure;
39import com.android.internal.net.eap.EapResult.EapResponse;
40import com.android.internal.net.eap.EapResult.EapSuccess;
41import com.android.internal.net.eap.crypto.ParityBitUtil;
42import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
43import com.android.internal.net.eap.exceptions.EapSilentException;
44import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException;
45import com.android.internal.net.eap.message.EapData;
46import com.android.internal.net.eap.message.EapData.EapMethod;
47import com.android.internal.net.eap.message.EapMessage;
48import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData;
49import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeRequest;
50import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2ChallengeResponse;
51import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2FailureRequest;
52import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2SuccessRequest;
53import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder;
54import com.android.internal.net.eap.message.mschapv2.EapMsChapV2TypeData.EapMsChapV2TypeDataDecoder.DecodeResult;
55import com.android.internal.net.utils.Log;
56
57import org.bouncycastle.crypto.digests.MD4Digest;
58
59import java.io.UnsupportedEncodingException;
60import java.nio.BufferUnderflowException;
61import java.nio.ByteBuffer;
62import java.nio.charset.StandardCharsets;
63import java.security.GeneralSecurityException;
64import java.security.MessageDigest;
65import java.security.SecureRandom;
66import java.util.Arrays;
67
68import javax.crypto.Cipher;
69import javax.crypto.SecretKey;
70import javax.crypto.SecretKeyFactory;
71import javax.crypto.spec.DESKeySpec;
72
73/**
74 * EapMsChapV2MethodStateMachine represents the valid paths possible for the EAP MSCHAPv2 protocol.
75 *
76 * <p>EAP MSCHAPv2 sessions will always follow the path:
77 *
78 * <p>CreatedState
79 * |
80 * +--> ChallengeState
81 * |
82 * +--> ValidateAuthenticatorState --+--> AwaitingEapSuccessState --> FinalState
83 * |
84 * +--> AwaitingEapFailureState --> FinalState
85 *
86 * <p>Note: All Failure-Request messages received in the PostChallenge state will be responded to
87 * with Failure-Response messages. That is, retryable failures <i>will not</i> be retried.
88 *
89 * <p>Note: The EAP standard states that EAP methods may disallow EAP Notification messages for the
90 * duration of the method (RFC 3748#5.2). EAP MSCHAPv2 does not explicitly ban these packets, so
91 * they are allowed at any time (except once a terminal state is reached).
92 *
93 * @see <a href="https://tools.ietf.org/html/draft-kamath-pppext-eap-mschapv2-02">Microsoft EAP CHAP
94 * Extensions Draft (EAP MSCHAPv2)</a>
95 * @see <a href="https://tools.ietf.org/html/rfc2759">RFC 2759, Microsoft PPP CHAP Extensions,
96 * Version 2 (MSCHAPv2)</a>
97 * @see <a href="https://tools.ietf.org/html/rfc3079">RFC 3079, Deriving Keys for use with Microsoft
98 * Point-to-Point Encryption (MPPE)</a>
99 * @hide
100 */
101public class EapMsChapV2MethodStateMachine extends EapMethodStateMachine {
102 private static final String SHA_ALG = "SHA-1";
103 private static final String DES_ALG = "DES/ECB/NoPadding";
104 private static final String DES_KEY_FACTORY = "DES";
105 private static final int PEER_CHALLENGE_SIZE = 16;
106 private static final int CHALLENGE_HASH_LEN = 8;
107 private static final int PASSWORD_HASH_LEN = 16;
108 private static final int PASSWORD_HASH_HASH_LEN = 16;
109 private static final int RESPONSE_LEN = 24;
110 private static final int Z_PASSWORD_HASH_LEN = 21;
111 private static final int Z_PASSWORD_SECTION_LEN = 7;
112 private static final int RESPONSE_SECTION_LEN = 8;
113 private static final int SHS_PAD_LEN = 40;
114 private static final int MASTER_KEY_LEN = 16;
115 private static final int SESSION_KEY_LEN = 16;
116 private static final int MASTER_SESSION_KEY_LEN = 2 * SESSION_KEY_LEN;
117
118 // Reserved for future use and must be 0 (EAP MSCHAPv2#2.2)
119 private static final int FLAGS = 0;
120
121 // we all need a little magic in our lives
122 // Defined in RFC 2759#8.7. Constants used for Success response generation.
123 private static final byte[] CHALLENGE_MAGIC_1 = {
124 (byte) 0x4D, (byte) 0x61, (byte) 0x67, (byte) 0x69, (byte) 0x63, (byte) 0x20, (byte) 0x73,
125 (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x74,
126 (byte) 0x6F, (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
127 (byte) 0x74, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x67, (byte) 0x6E, (byte) 0x69,
128 (byte) 0x6E, (byte) 0x67, (byte) 0x20, (byte) 0x63, (byte) 0x6F, (byte) 0x6E, (byte) 0x73,
129 (byte) 0x74, (byte) 0x61, (byte) 0x6E, (byte) 0x74
130 };
131 private static final byte[] CHALLENGE_MAGIC_2 = {
132 (byte) 0x50, (byte) 0x61, (byte) 0x64, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20,
133 (byte) 0x6D, (byte) 0x61, (byte) 0x6B, (byte) 0x65, (byte) 0x20, (byte) 0x69, (byte) 0x74,
134 (byte) 0x20, (byte) 0x64, (byte) 0x6F, (byte) 0x20, (byte) 0x6D, (byte) 0x6F, (byte) 0x72,
135 (byte) 0x65, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x61, (byte) 0x6E, (byte) 0x20,
136 (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x20, (byte) 0x69, (byte) 0x74, (byte) 0x65,
137 (byte) 0x72, (byte) 0x61, (byte) 0x74, (byte) 0x69, (byte) 0x6F, (byte) 0x6E
138 };
139
140 // Defined in RFC 3079#3.4. Constants used for Master Session Key (MSK) generation
141 private static final byte[] SHS_PAD_1 = new byte[SHS_PAD_LEN];
142 private static final byte[] SHS_PAD_2 = new byte[SHS_PAD_LEN];
143
144 static {
145 Arrays.fill(SHS_PAD_2, (byte) 0xF2);
146 }
147
148 private static final byte[] MSK_MAGIC_1 = {
149 (byte) 0x54, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x69,
150 (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20,
151 (byte) 0x4D, (byte) 0x50, (byte) 0x50, (byte) 0x45, (byte) 0x20, (byte) 0x4D,
152 (byte) 0x61, (byte) 0x73, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x20,
153 (byte) 0x4B, (byte) 0x65, (byte) 0x79
154 };
155 private static final byte[] MSK_MAGIC_2 = {
156 (byte) 0x4F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
157 (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
158 (byte) 0x74, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
159 (byte) 0x2C, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73,
160 (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68,
161 (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x6E, (byte) 0x64,
162 (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x3B, (byte) 0x20,
163 (byte) 0x6F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
164 (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65,
165 (byte) 0x72, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
166 (byte) 0x2C, (byte) 0x20, (byte) 0x69, (byte) 0x74, (byte) 0x20, (byte) 0x69,
167 (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20,
168 (byte) 0x72, (byte) 0x65, (byte) 0x63, (byte) 0x65, (byte) 0x69, (byte) 0x76,
169 (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x2E
170 };
171 private static final byte[] MSK_MAGIC_3 = {
172 (byte) 0x4F, (byte) 0x6E, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x65,
173 (byte) 0x20, (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
174 (byte) 0x74, (byte) 0x20, (byte) 0x73, (byte) 0x69, (byte) 0x64, (byte) 0x65,
175 (byte) 0x2C, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x69, (byte) 0x73,
176 (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74, (byte) 0x68,
177 (byte) 0x65, (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x63, (byte) 0x65,
178 (byte) 0x69, (byte) 0x76, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65,
179 (byte) 0x79, (byte) 0x3B, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x20,
180 (byte) 0x74, (byte) 0x68, (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65,
181 (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x73,
182 (byte) 0x69, (byte) 0x64, (byte) 0x65, (byte) 0x2C, (byte) 0x20, (byte) 0x69,
183 (byte) 0x74, (byte) 0x20, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x74,
184 (byte) 0x68, (byte) 0x65, (byte) 0x20, (byte) 0x73, (byte) 0x65, (byte) 0x6E,
185 (byte) 0x64, (byte) 0x20, (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x2E
186 };
187
188 private final EapMsChapV2Config mEapMsChapV2Config;
189 private final SecureRandom mSecureRandom;
190 private final EapMsChapV2TypeDataDecoder mTypeDataDecoder;
191
192 public EapMsChapV2MethodStateMachine(
193 EapMsChapV2Config eapMsChapV2Config, SecureRandom secureRandom) {
194 this(eapMsChapV2Config, secureRandom, new EapMsChapV2TypeDataDecoder());
195 }
196
197 @VisibleForTesting
198 EapMsChapV2MethodStateMachine(
199 EapMsChapV2Config eapMsChapV2Config,
200 SecureRandom secureRandom,
201 EapMsChapV2TypeDataDecoder eapMsChapV2TypeDataDecoder) {
202 this.mEapMsChapV2Config = eapMsChapV2Config;
203 this.mSecureRandom = secureRandom;
204 this.mTypeDataDecoder = eapMsChapV2TypeDataDecoder;
205
206 transitionTo(new CreatedState());
207 }
208
209 @Override
210 @EapMethod
211 int getEapMethod() {
212 return EAP_TYPE_MSCHAP_V2;
213 }
214
215 @Override
216 EapResult handleEapNotification(String tag, EapMessage message) {
217 return EapStateMachine.handleNotification(tag, message);
218 }
219
220 protected class CreatedState extends EapMethodState {
221 private final String mTAG = this.getClass().getSimpleName();
222
223 @Override
224 public EapResult process(EapMessage message) {
225 EapResult result = handleEapSuccessFailureNotification(mTAG, message);
226 if (result != null) {
227 return result;
228 }
229
230 DecodeResult<EapMsChapV2ChallengeRequest> decodeResult =
231 mTypeDataDecoder.decodeChallengeRequest(mTAG, message.eapData.eapTypeData);
232 if (!decodeResult.isSuccessfulDecode()) {
233 return decodeResult.eapError;
234 }
235
236 return transitionAndProcess(new ChallengeState(), message);
237 }
238 }
239
240 protected class ChallengeState extends EapMethodState {
241 private final String mTAG = this.getClass().getSimpleName();
242
243 @Override
244 public EapResult process(EapMessage message) {
245 EapResult result = handleEapSuccessFailureNotification(mTAG, message);
246 if (result != null) {
247 return result;
248 }
249
250 DecodeResult<EapMsChapV2ChallengeRequest> decodeResult =
251 mTypeDataDecoder.decodeChallengeRequest(mTAG, message.eapData.eapTypeData);
252 if (!decodeResult.isSuccessfulDecode()) {
253 return decodeResult.eapError;
254 }
255
256 EapMsChapV2ChallengeRequest challengeRequest = decodeResult.eapTypeData;
257 LOG.d(
258 mTAG,
259 "Received Challenge Request:"
260 + " Challenge=" + LOG.pii(challengeRequest.challenge)
261 + " Server-Name=" + Log.byteArrayToHexString(challengeRequest.name));
262
263 byte[] peerChallenge = new byte[PEER_CHALLENGE_SIZE];
264 mSecureRandom.nextBytes(peerChallenge);
265
266 byte[] ntResponse;
267 try {
268 ntResponse =
269 generateNtResponse(
270 challengeRequest.challenge,
271 peerChallenge,
272 mEapMsChapV2Config.username,
273 mEapMsChapV2Config.password);
274 } catch (GeneralSecurityException ex) {
275 LOG.e(mTAG, "Error generating EAP MSCHAPv2 Challenge response", ex);
276 return new EapError(ex);
277 }
278
279 LOG.d(
280 mTAG,
281 "Generating Challenge Response:"
282 + " Username=" + LOG.pii(mEapMsChapV2Config.username)
283 + " Peer-Challenge=" + LOG.pii(peerChallenge)
284 + " NT-Response=" + LOG.pii(ntResponse));
285
286 try {
287 EapMsChapV2ChallengeResponse challengeResponse =
288 new EapMsChapV2ChallengeResponse(
289 challengeRequest.msChapV2Id,
290 peerChallenge,
291 ntResponse,
292 FLAGS,
293 usernameToBytes(mEapMsChapV2Config.username));
294 transitionTo(
295 new ValidateAuthenticatorState(
296 challengeRequest.challenge, peerChallenge, ntResponse));
297
298 return buildEapMessageResponse(mTAG, message.eapIdentifier, challengeResponse);
299 } catch (EapMsChapV2ParsingException ex) {
300 LOG.e(mTAG, "Error building response type data", ex);
301 return new EapError(ex);
302 }
303 }
304 }
305
306 protected class ValidateAuthenticatorState extends EapMethodState {
307 private final String mTAG = this.getClass().getSimpleName();
308
309 private final byte[] mAuthenticatorChallenge;
310 private final byte[] mPeerChallenge;
311 private final byte[] mNtResponse;
312
313 @VisibleForTesting
314 ValidateAuthenticatorState(
315 byte[] authenticatorChallenge, byte[] peerChallenge, byte[] ntResponse) {
316 this.mAuthenticatorChallenge = authenticatorChallenge;
317 this.mPeerChallenge = peerChallenge;
318 this.mNtResponse = ntResponse;
319 }
320
321 @Override
322 public EapResult process(EapMessage message) {
323 EapResult result = handleEapSuccessFailureNotification(mTAG, message);
324 if (result != null) {
325 return result;
326 }
327
328 int opCode;
329 try {
330 opCode = mTypeDataDecoder.getOpCode(message.eapData.eapTypeData);
331 } catch (BufferUnderflowException ex) {
332 LOG.e(mTAG, "Empty type data received in ValidateAuthenticatorState", ex);
333 return new EapError(ex);
334 }
335
336 LOG.d(
337 mTAG,
338 "Received Op Code: "
339 + EAP_OP_CODE_STRING.getOrDefault(opCode, "Unknown")
340 + " (" + opCode + ")");
341
342 switch (opCode) {
343 case EAP_MSCHAP_V2_SUCCESS:
344 DecodeResult<EapMsChapV2SuccessRequest> successDecodeResult =
345 mTypeDataDecoder.decodeSuccessRequest(
346 mTAG, message.eapData.eapTypeData);
347 if (!successDecodeResult.isSuccessfulDecode()) {
348 return successDecodeResult.eapError;
349 }
350
351 EapMsChapV2SuccessRequest successRequest = successDecodeResult.eapTypeData;
352 LOG.d(
353 mTAG,
354 "Received SuccessRequest:"
355 + " Auth-String=" + LOG.pii(successRequest.authBytes)
356 + " Message=" + successRequest.message);
357
358 boolean isSuccessfulAuth;
359 try {
360 isSuccessfulAuth =
361 checkAuthenticatorResponse(
362 mEapMsChapV2Config.password,
363 mNtResponse,
364 mPeerChallenge,
365 mAuthenticatorChallenge,
366 mEapMsChapV2Config.username,
367 successRequest.authBytes);
368 } catch (GeneralSecurityException | UnsupportedEncodingException ex) {
369 LOG.e(mTAG, "Error validating MSCHAPv2 Authenticator Response", ex);
370 return new EapError(ex);
371 }
372
373 if (!isSuccessfulAuth) {
374 LOG.e(
375 mTAG,
376 "Authenticator Response does not match expected response value");
377 transitionTo(new FinalState());
378 return new EapFailure();
379 }
380
381 transitionTo(new AwaitingEapSuccessState(mNtResponse));
382 return buildEapMessageResponse(
383 mTAG, message.eapIdentifier, getEapMsChapV2SuccessResponse());
384
385 case EAP_MSCHAP_V2_FAILURE:
386 DecodeResult<EapMsChapV2FailureRequest> failureDecodeResult =
387 mTypeDataDecoder.decodeFailureRequest(
388 mTAG, message.eapData.eapTypeData);
389 if (!failureDecodeResult.isSuccessfulDecode()) {
390 return failureDecodeResult.eapError;
391 }
392
393 EapMsChapV2FailureRequest failureRequest = failureDecodeResult.eapTypeData;
394 int errorCode = failureRequest.errorCode;
395 LOG.e(
396 mTAG,
397 String.format(
398 "Received MSCHAPv2 Failure-Request: E=%s (%d) R=%b V=%d M=%s",
399 EAP_ERROR_CODE_STRING.getOrDefault(errorCode, "UNKNOWN"),
400 errorCode,
401 failureRequest.isRetryable,
402 failureRequest.passwordChangeProtocol,
403 failureRequest.message));
404 transitionTo(new AwaitingEapFailureState());
405 return buildEapMessageResponse(
406 mTAG, message.eapIdentifier, getEapMsChapV2FailureResponse());
407
408 default:
409 LOG.e(mTAG, "Invalid OpCode: " + opCode);
410 return new EapError(
411 new EapInvalidRequestException(
412 "Unexpected request received in EAP MSCHAPv2"));
413 }
414 }
415 }
416
417 protected class AwaitingEapSuccessState extends EapMethodState {
418 private final String mTAG = this.getClass().getSimpleName();
419
420 private final byte[] mNtResponse;
421
422 AwaitingEapSuccessState(byte[] ntResponse) {
423 this.mNtResponse = ntResponse;
424 }
425
426 @Override
427 public EapResult process(EapMessage message) {
428 if (message.eapCode == EAP_CODE_FAILURE) {
429 LOG.e(mTAG, "Received EAP-Failure in PreSuccessState");
430 transitionTo(new FinalState());
431 return new EapFailure();
432 } else if (message.eapCode != EAP_CODE_SUCCESS) {
433 int eapType = message.eapData.eapType;
434 if (eapType == EAP_NOTIFICATION) {
435 return handleEapNotification(mTAG, message);
436 } else {
437 LOG.e(
438 mTAG,
439 "Received unexpected EAP message. Type="
440 + EAP_TYPE_STRING.getOrDefault(
441 eapType, "UNKNOWN (" + eapType + ")"));
442 return new EapError(
443 new EapInvalidRequestException(
444 "Expected EAP Type "
445 + getEapMethod()
446 + ", received "
447 + eapType));
448 }
449 }
450
451 try {
452 byte[] msk = generateMsk(mEapMsChapV2Config.password, mNtResponse);
453 transitionTo(new FinalState());
454 return new EapSuccess(msk, new byte[0]);
455 } catch (GeneralSecurityException | UnsupportedEncodingException ex) {
456 LOG.e(mTAG, "Error generating MSK for EAP MSCHAPv2", ex);
457 return new EapError(ex);
458 }
459 }
460 }
461
462 protected class AwaitingEapFailureState extends EapMethodState {
463 private final String mTAG = this.getClass().getSimpleName();
464
465 @Override
466 public EapResult process(EapMessage message) {
467 EapResult result = handleEapSuccessFailureNotification(mTAG, message);
468 if (result != null) {
469 return result;
470 }
471 int eapType = message.eapData.eapType;
472 LOG.e(
473 mTAG,
474 "Received unexpected EAP message. Type="
475 + EAP_TYPE_STRING.getOrDefault(eapType, "UNKNOWN (" + eapType + ")"));
476 return new EapError(
477 new EapInvalidRequestException(
478 "Expected EAP Type " + getEapMethod() + ", received " + eapType));
479 }
480 }
481
482 private EapResult buildEapMessageResponse(
483 String tag, int eapIdentifier, EapMsChapV2TypeData typeData) {
484 try {
485 EapData eapData = new EapData(getEapMethod(), typeData.encode());
486 EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, eapData);
487 return EapResponse.getEapResponse(eapMessage);
488 } catch (EapSilentException ex) {
489 LOG.e(tag, "Error building response EapMessage", ex);
490 return new EapError(ex);
491 }
492 }
493
494 /** Util for converting String username to "0-to-256 char username", as used in RFC 2759#8. */
495 @VisibleForTesting
496 static byte[] usernameToBytes(String username) {
497 return username.getBytes(StandardCharsets.US_ASCII);
498 }
499
500 /**
501 * Util for converting String password to "0-to-256-unicode-char password", as used in
502 * RFC 2759#8.
503 */
504 @VisibleForTesting
505 static byte[] passwordToBytes(String password) {
506 return password.getBytes(StandardCharsets.UTF_16LE);
507 }
508
509 /* Implementation of RFC 2759#8.1: GenerateNTResponse() */
510 @VisibleForTesting
511 static byte[] generateNtResponse(
512 byte[] authenticatorChallenge, byte[] peerChallenge, String username, String password)
513 throws GeneralSecurityException {
514 byte[] challenge = challengeHash(peerChallenge, authenticatorChallenge, username);
515 byte[] passwordHash = ntPasswordHash(password);
516 return challengeResponse(challenge, passwordHash);
517 }
518
519 /* Implementation of RFC 2759#8.2: ChallengeHash() */
520 @VisibleForTesting
521 static byte[] challengeHash(
522 byte[] peerChallenge, byte[] authenticatorChallenge, String username)
523 throws GeneralSecurityException {
524 MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
525 sha1.update(peerChallenge);
526 sha1.update(authenticatorChallenge);
527 sha1.update(usernameToBytes(username));
528 return Arrays.copyOf(sha1.digest(), CHALLENGE_HASH_LEN);
529 }
530
531 /* Implementation of RFC 2759#8.3: NtPasswordHash() */
532 @VisibleForTesting
533 static byte[] ntPasswordHash(String password) {
534 MD4Digest md4 = new MD4Digest();
535 byte[] passwordBytes = passwordToBytes(password);
536 md4.update(passwordBytes, 0, passwordBytes.length);
537
538 byte[] passwordHash = new byte[PASSWORD_HASH_LEN];
539 md4.doFinal(passwordHash, 0);
540 return passwordHash;
541 }
542
543 /* Implementation of RFC 2759#8.4: HashNtPasswordHash() */
544 @VisibleForTesting
545 static byte[] hashNtPasswordHash(byte[] passwordHash) {
546 MD4Digest md4 = new MD4Digest();
547 md4.update(passwordHash, 0, passwordHash.length);
548
549 byte[] passwordHashHash = new byte[PASSWORD_HASH_HASH_LEN];
550 md4.doFinal(passwordHashHash, 0);
551 return passwordHashHash;
552 }
553
554 /* Implementation of RFC 2759#8.5: ChallengeResponse() */
555 @VisibleForTesting
556 static byte[] challengeResponse(byte[] challenge, byte[] passwordHash)
557 throws GeneralSecurityException {
558 byte[] zPasswordHash = Arrays.copyOf(passwordHash, Z_PASSWORD_HASH_LEN);
559
560 ByteBuffer response = ByteBuffer.allocate(RESPONSE_LEN);
561 for (int i = 0; i < 3; i++) {
562 int from = i * Z_PASSWORD_SECTION_LEN;
563 int to = from + Z_PASSWORD_SECTION_LEN;
564 byte[] zPasswordSection = Arrays.copyOfRange(zPasswordHash, from, to);
565 response.put(desEncrypt(challenge, zPasswordSection));
566 }
567 return response.array();
568 }
569
570 /* Implementation of RFC 2759#8.6: DesEncrypt() */
571 @VisibleForTesting
572 static byte[] desEncrypt(byte[] clear, byte[] key) throws GeneralSecurityException {
573 if (key.length != Z_PASSWORD_SECTION_LEN) {
574 throw new IllegalArgumentException("DES Key must be 7B before parity-bits are added");
575 }
576
577 key = ParityBitUtil.addParityBits(key);
578 SecretKey secretKey =
579 SecretKeyFactory.getInstance(DES_KEY_FACTORY).generateSecret(new DESKeySpec(key));
580
581 Cipher des = Cipher.getInstance(DES_ALG);
582 des.init(Cipher.ENCRYPT_MODE, secretKey);
583 byte[] output = des.doFinal(clear);
584
585 // RFC 2759#8.6 specifies 8B outputs for DesEncrypt()
586 return Arrays.copyOf(output, RESPONSE_SECTION_LEN);
587 }
588
589 /**
590 * Implementation of RFC 2759#8.7: GenerateAuthenticatorResponse()
591 *
592 * <p>Keep response as byte[] so checkAuthenticatorResponse() can easily compare byte[]'s
593 */
594 @VisibleForTesting
595 static byte[] generateAuthenticatorResponse(
596 String password,
597 byte[] ntResponse,
598 byte[] peerChallenge,
599 byte[] authenticatorChallenge,
600 String username)
601 throws GeneralSecurityException, UnsupportedEncodingException {
602 byte[] passwordHash = ntPasswordHash(password);
603 byte[] passwordHashHash = hashNtPasswordHash(passwordHash);
604
605 MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
606 sha1.update(passwordHashHash);
607 sha1.update(ntResponse);
608 sha1.update(CHALLENGE_MAGIC_1); // add just a dash of magic
609 byte[] digest = sha1.digest();
610
611 byte[] challenge = challengeHash(peerChallenge, authenticatorChallenge, username);
612
613 sha1.update(digest);
614 sha1.update(challenge);
615 sha1.update(CHALLENGE_MAGIC_2);
616
617 return sha1.digest();
618 }
619
620 /* Implementation of RFC 2759#8.8: CheckAuthenticatorResponse() */
621 @VisibleForTesting
622 static boolean checkAuthenticatorResponse(
623 String password,
624 byte[] ntResponse,
625 byte[] peerChallenge,
626 byte[] authenticatorChallenge,
627 String userName,
628 byte[] receivedResponse)
629 throws GeneralSecurityException, UnsupportedEncodingException {
630 byte[] myResponse =
631 generateAuthenticatorResponse(
632 password, ntResponse, peerChallenge, authenticatorChallenge, userName);
633 return Arrays.equals(myResponse, receivedResponse);
634 }
635
636 /* Implementation of RFC 3079#3.4: GetMasterKey() */
637 @VisibleForTesting
638 static byte[] getMasterKey(byte[] passwordHashHash, byte[] ntResponse)
639 throws GeneralSecurityException {
640 MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
641 sha1.update(passwordHashHash);
642 sha1.update(ntResponse);
643 sha1.update(MSK_MAGIC_1);
644 return Arrays.copyOf(sha1.digest(), MASTER_KEY_LEN);
645 }
646
647 /* Implementation of RFC 3079#3.4: GetAsymmetricStartKey() */
648 @VisibleForTesting
649 static byte[] getAsymmetricStartKey(byte[] masterKey, boolean isSend)
650 throws GeneralSecurityException {
651 // salt: referred to as 's' in RFC 3079#3.4 GetAsymmetricStartKey()
652 byte[] salt = isSend ? MSK_MAGIC_2 : MSK_MAGIC_3;
653 MessageDigest sha1 = MessageDigest.getInstance(SHA_ALG);
654 sha1.update(masterKey);
655 sha1.update(SHS_PAD_1);
656 sha1.update(salt);
657 sha1.update(SHS_PAD_2);
658 return Arrays.copyOf(sha1.digest(), SESSION_KEY_LEN);
659 }
660
661 @VisibleForTesting
662 static byte[] generateMsk(String password, byte[] ntResponse)
663 throws GeneralSecurityException, UnsupportedEncodingException {
664 byte[] passwordHash = ntPasswordHash(password);
665 byte[] passwordHashHash = hashNtPasswordHash(passwordHash);
666 byte[] masterKey = getMasterKey(passwordHashHash, ntResponse);
667
668 // MSK: SendKey + ReceiveKey
669 ByteBuffer msk = ByteBuffer.allocate(MASTER_SESSION_KEY_LEN);
670 msk.put(getAsymmetricStartKey(masterKey, true /* isSend */));
671 msk.put(getAsymmetricStartKey(masterKey, false /* isSend */));
672
673 return msk.array();
674 }
675}