1 /* 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 17 package com.android.internal.net.eap.message.mschapv2; 18 19 import static com.android.internal.net.eap.EapAuthenticator.LOG; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.internal.net.eap.EapResult.EapError; 23 import com.android.internal.net.eap.exceptions.mschapv2.EapMsChapV2ParsingException; 24 import com.android.internal.net.eap.message.EapMessage; 25 26 import java.nio.BufferUnderflowException; 27 import java.nio.ByteBuffer; 28 import java.nio.charset.Charset; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.Map; 32 import java.util.Set; 33 34 /** 35 * EapMsChapV2TypeData represents the Type Data for an {@link EapMessage} during an EAP MSCHAPv2 36 * session. 37 */ 38 public class EapMsChapV2TypeData { 39 private static final int LABEL_VALUE_LENGTH = 2; 40 private static final String ASCII_CHARSET_NAME = "US-ASCII"; 41 private static final String MESSAGE_PREFIX = "M="; 42 private static final String MESSAGE_LABEL = "M"; 43 44 // EAP MSCHAPv2 OpCode values (EAP MSCHAPv2#2) 45 public static final int EAP_MSCHAP_V2_CHALLENGE = 1; 46 public static final int EAP_MSCHAP_V2_RESPONSE = 2; 47 public static final int EAP_MSCHAP_V2_SUCCESS = 3; 48 public static final int EAP_MSCHAP_V2_FAILURE = 4; 49 public static final int EAP_MSCHAP_V2_CHANGE_PASSWORD = 7; 50 51 public static final Map<Integer, String> EAP_OP_CODE_STRING = new HashMap<>(); 52 static { EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_CHALLENGE, "Challenge")53 EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_CHALLENGE, "Challenge"); EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_RESPONSE, "Response")54 EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_RESPONSE, "Response"); EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_SUCCESS, "Success")55 EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_SUCCESS, "Success"); EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_FAILURE, "Failure")56 EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_FAILURE, "Failure"); EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_CHANGE_PASSWORD, "Change-Password")57 EAP_OP_CODE_STRING.put(EAP_MSCHAP_V2_CHANGE_PASSWORD, "Change-Password"); 58 } 59 60 private static final Set<Integer> SUPPORTED_OP_CODES = new HashSet<>(); 61 static { 62 SUPPORTED_OP_CODES.add(EAP_MSCHAP_V2_CHALLENGE); 63 SUPPORTED_OP_CODES.add(EAP_MSCHAP_V2_RESPONSE); 64 SUPPORTED_OP_CODES.add(EAP_MSCHAP_V2_SUCCESS); 65 SUPPORTED_OP_CODES.add(EAP_MSCHAP_V2_FAILURE); 66 } 67 68 public final int opCode; 69 EapMsChapV2TypeData(int opCode)70 EapMsChapV2TypeData(int opCode) throws EapMsChapV2ParsingException { 71 this.opCode = opCode; 72 73 if (!SUPPORTED_OP_CODES.contains(opCode)) { 74 throw new EapMsChapV2ParsingException("Unsupported opCode provided: " + opCode); 75 } 76 } 77 78 /** 79 * Encodes this EapMsChapV2TypeData instance as a byte[]. 80 * 81 * @return byte[] representing the encoded value of this EapMsChapV2TypeData instance. 82 */ encode()83 public byte[] encode() { 84 throw new UnsupportedOperationException( 85 "encode() not supported by " + this.getClass().getSimpleName()); 86 } 87 88 abstract static class EapMsChapV2VariableTypeData extends EapMsChapV2TypeData { 89 public final int msChapV2Id; 90 public final int msLength; 91 EapMsChapV2VariableTypeData(int opCode, int msChapV2Id, int msLength)92 EapMsChapV2VariableTypeData(int opCode, int msChapV2Id, int msLength) 93 throws EapMsChapV2ParsingException { 94 super(opCode); 95 96 this.msChapV2Id = msChapV2Id; 97 this.msLength = msLength; 98 } 99 } 100 101 /** 102 * EapMsChapV2ChallengeRequest represents the EAP MSCHAPv2 Challenge Packet (EAP MSCHAPv2#2.1). 103 */ 104 public static class EapMsChapV2ChallengeRequest extends EapMsChapV2VariableTypeData { 105 public static final int VALUE_SIZE = 16; 106 public static final int TYPE_DATA_HEADER_SIZE = 5; 107 108 public final byte[] challenge = new byte[VALUE_SIZE]; 109 public final byte[] name; 110 EapMsChapV2ChallengeRequest(ByteBuffer buffer)111 EapMsChapV2ChallengeRequest(ByteBuffer buffer) throws EapMsChapV2ParsingException { 112 super( 113 EAP_MSCHAP_V2_CHALLENGE, 114 Byte.toUnsignedInt(buffer.get()), 115 Short.toUnsignedInt(buffer.getShort())); 116 117 int valueSize = Byte.toUnsignedInt(buffer.get()); 118 if (valueSize != VALUE_SIZE) { 119 throw new EapMsChapV2ParsingException("Challenge Value-Size must be 16"); 120 } 121 buffer.get(challenge); 122 123 int nameLenBytes = msLength - VALUE_SIZE - TYPE_DATA_HEADER_SIZE; 124 if (nameLenBytes < 0) { 125 throw new EapMsChapV2ParsingException("Invalid MS-Length specified"); 126 } 127 128 name = new byte[nameLenBytes]; 129 buffer.get(name); 130 } 131 132 @VisibleForTesting EapMsChapV2ChallengeRequest( int msChapV2Id, int msLength, byte[] challenge, byte[] name)133 public EapMsChapV2ChallengeRequest( 134 int msChapV2Id, int msLength, byte[] challenge, byte[] name) 135 throws EapMsChapV2ParsingException { 136 super(EAP_MSCHAP_V2_CHALLENGE, msChapV2Id, msLength); 137 138 if (challenge.length != VALUE_SIZE) { 139 throw new EapMsChapV2ParsingException("Challenge length must be 16"); 140 } 141 142 System.arraycopy(challenge, 0, this.challenge, 0, VALUE_SIZE); 143 this.name = name; 144 } 145 } 146 147 /** 148 * EapMsChapV2ChallengeResponse represents the EAP MSCHAPv2 Response Packet (EAP MSCHAPv2#2.2). 149 */ 150 public static class EapMsChapV2ChallengeResponse extends EapMsChapV2VariableTypeData { 151 public static final int VALUE_SIZE = 49; 152 public static final int PEER_CHALLENGE_SIZE = 16; 153 public static final int RESERVED_BYTES = 8; 154 public static final int NT_RESPONSE_SIZE = 24; 155 public static final int TYPE_DATA_HEADER_SIZE = 5; 156 157 public final byte[] peerChallenge = new byte[PEER_CHALLENGE_SIZE]; 158 public final byte[] ntResponse = new byte[NT_RESPONSE_SIZE]; 159 public final int flags; 160 public final byte[] name; 161 EapMsChapV2ChallengeResponse( int msChapV2Id, byte[] peerChallenge, byte[] ntResponse, int flags, byte[] name)162 public EapMsChapV2ChallengeResponse( 163 int msChapV2Id, byte[] peerChallenge, byte[] ntResponse, int flags, byte[] name) 164 throws EapMsChapV2ParsingException { 165 super( 166 EAP_MSCHAP_V2_RESPONSE, 167 msChapV2Id, 168 TYPE_DATA_HEADER_SIZE + VALUE_SIZE + name.length); 169 170 if (peerChallenge.length != PEER_CHALLENGE_SIZE) { 171 throw new EapMsChapV2ParsingException("Peer-Challenge must be 16B"); 172 } else if (ntResponse.length != NT_RESPONSE_SIZE) { 173 throw new EapMsChapV2ParsingException("NT-Response must be 24B"); 174 } else if (flags != 0) { 175 throw new EapMsChapV2ParsingException("Flags must be 0x00"); 176 } 177 178 System.arraycopy(peerChallenge, 0, this.peerChallenge, 0, PEER_CHALLENGE_SIZE); 179 System.arraycopy(ntResponse, 0, this.ntResponse, 0, NT_RESPONSE_SIZE); 180 this.flags = flags; 181 this.name = name; 182 } 183 184 @Override encode()185 public byte[] encode() { 186 ByteBuffer buffer = ByteBuffer.allocate(msLength); 187 buffer.put((byte) EAP_MSCHAP_V2_RESPONSE); 188 buffer.put((byte) msChapV2Id); 189 buffer.putShort((short) msLength); 190 buffer.put((byte) VALUE_SIZE); 191 buffer.put(peerChallenge); 192 buffer.put(new byte[RESERVED_BYTES]); 193 buffer.put(ntResponse); 194 buffer.put((byte) flags); 195 buffer.put(name); 196 197 return buffer.array(); 198 } 199 } 200 201 /** 202 * EapMsChapV2SuccessRequest represents the EAP MSCHAPv2 Success Request Packet 203 * (EAP MSCHAPv2#2.3). 204 */ 205 public static class EapMsChapV2SuccessRequest extends EapMsChapV2VariableTypeData { 206 private static final int AUTH_STRING_LEN_HEX = 40; 207 private static final int AUTH_STRING_LEN_BYTES = 20; 208 private static final int NUM_REQUIRED_ATTRIBUTES = 2; 209 private static final String AUTH_STRING_LABEL = "S"; 210 211 public final byte[] authBytes = new byte[AUTH_STRING_LEN_BYTES]; 212 public final String message; 213 EapMsChapV2SuccessRequest(ByteBuffer buffer)214 EapMsChapV2SuccessRequest(ByteBuffer buffer) throws EapMsChapV2ParsingException { 215 super( 216 EAP_MSCHAP_V2_SUCCESS, 217 Byte.toUnsignedInt(buffer.get()), 218 Short.toUnsignedInt(buffer.getShort())); 219 220 byte[] message = new byte[buffer.remaining()]; 221 buffer.get(message); 222 223 // message formatting: "S=<auth_string> M=<message>" 224 Map<String, String> mappings = 225 getMessageMappings(new String(message, Charset.forName(ASCII_CHARSET_NAME))); 226 227 if (!mappings.containsKey(AUTH_STRING_LABEL) 228 || mappings.size() != NUM_REQUIRED_ATTRIBUTES) { 229 throw new EapMsChapV2ParsingException( 230 "Auth message must be in the format: 'S=<auth_string> M=<message>'"); 231 } 232 233 String authStringHex = mappings.get(AUTH_STRING_LABEL); 234 if (authStringHex.length() != AUTH_STRING_LEN_HEX) { 235 throw new EapMsChapV2ParsingException("Auth String must be 40 hex chars (20B)"); 236 } 237 byte[] authBytes = hexStringToByteArray(authStringHex); 238 System.arraycopy(authBytes, 0, this.authBytes, 0, AUTH_STRING_LEN_BYTES); 239 240 this.message = mappings.get(MESSAGE_LABEL); 241 } 242 243 @VisibleForTesting EapMsChapV2SuccessRequest( int msChapV2Id, int msLength, byte[] authBytes, String message)244 public EapMsChapV2SuccessRequest( 245 int msChapV2Id, int msLength, byte[] authBytes, String message) 246 throws EapMsChapV2ParsingException { 247 super(EAP_MSCHAP_V2_SUCCESS, msChapV2Id, msLength); 248 249 if (authBytes.length != AUTH_STRING_LEN_BYTES) { 250 throw new EapMsChapV2ParsingException("Auth String must be 20B"); 251 } 252 253 System.arraycopy(authBytes, 0, this.authBytes, 0, AUTH_STRING_LEN_BYTES); 254 this.message = message; 255 } 256 } 257 258 /** 259 * EapMsChapV2SuccessResponse represents the EAP MSCHAPv2 Success Response Packet 260 * (EAP MSCHAPv2#2.4). 261 */ 262 public static class EapMsChapV2SuccessResponse extends EapMsChapV2TypeData { EapMsChapV2SuccessResponse()263 private EapMsChapV2SuccessResponse() throws EapMsChapV2ParsingException { 264 super(EAP_MSCHAP_V2_SUCCESS); 265 } 266 267 /** 268 * Constructs and returns a new EAP MSCHAPv2 Success Response type data. 269 * 270 * @return a new EapMsChapV2SuccessResponse instance 271 */ getEapMsChapV2SuccessResponse()272 public static EapMsChapV2SuccessResponse getEapMsChapV2SuccessResponse() { 273 try { 274 return new EapMsChapV2SuccessResponse(); 275 } catch (EapMsChapV2ParsingException ex) { 276 // This should never happen 277 LOG.wtf( 278 EapMsChapV2SuccessResponse.class.getSimpleName(), 279 "ParsingException thrown while creating EapMsChapV2SuccessResponse", 280 ex); 281 return null; 282 } 283 } 284 285 @Override encode()286 public byte[] encode() { 287 return new byte[] {(byte) EAP_MSCHAP_V2_SUCCESS}; 288 } 289 } 290 291 /** 292 * EapMsChapV2FailureRequest represents the EAP MSCHAPv2 Failure Request Packet 293 * (EAP MSCHAPv2#2.5). 294 */ 295 public static class EapMsChapV2FailureRequest extends EapMsChapV2VariableTypeData { 296 private static final int NUM_REQUIRED_ATTRIBUTES = 5; 297 private static final int CHALLENGE_LENGTH = 16; 298 private static final String ERROR_LABEL = "E"; 299 private static final String RETRY_LABEL = "R"; 300 private static final String IS_RETRYABLE_FLAG = "1"; 301 private static final String CHALLENGE_LABEL = "C"; 302 private static final String PASSWORD_CHANGE_PROTOCOL_LABEL = "V"; 303 304 // Error codes defined in EAP MSCHAPv2#2.5 305 public static final Map<Integer, String> EAP_ERROR_CODE_STRING = new HashMap<>(); 306 static { 307 EAP_ERROR_CODE_STRING.put(646, "ERROR_RESTRICTED_LOGON_HOURS"); 308 EAP_ERROR_CODE_STRING.put(647, "ERROR_ACCT_DISABLED"); 309 EAP_ERROR_CODE_STRING.put(648, "ERROR_PASSWD_EXPIRED"); 310 EAP_ERROR_CODE_STRING.put(649, "ERROR_NO_DIALIN_PERMISSION"); 311 EAP_ERROR_CODE_STRING.put(691, "ERROR_AUTHENTICATION_FAILURE"); 312 EAP_ERROR_CODE_STRING.put(709, "ERROR_CHANGING_PASSWORD"); 313 } 314 315 public final int errorCode; 316 public final boolean isRetryable; 317 public final byte[] challenge; 318 public final int passwordChangeProtocol; 319 public final String message; 320 EapMsChapV2FailureRequest(ByteBuffer buffer)321 EapMsChapV2FailureRequest(ByteBuffer buffer) 322 throws EapMsChapV2ParsingException, NumberFormatException { 323 super( 324 EAP_MSCHAP_V2_FAILURE, 325 Byte.toUnsignedInt(buffer.get()), 326 Short.toUnsignedInt(buffer.getShort())); 327 328 byte[] message = new byte[buffer.remaining()]; 329 buffer.get(message); 330 331 // message formatting: 332 // "E=<error_code> R=<retry bit> C=<challenge> V=<password_change_protocol> M=<message>" 333 Map<String, String> mappings = 334 getMessageMappings(new String(message, Charset.forName(ASCII_CHARSET_NAME))); 335 if (!mappings.containsKey(ERROR_LABEL) 336 || !mappings.containsKey(RETRY_LABEL) 337 || !mappings.containsKey(CHALLENGE_LABEL) 338 || !mappings.containsKey(PASSWORD_CHANGE_PROTOCOL_LABEL) 339 || mappings.size() != NUM_REQUIRED_ATTRIBUTES) { 340 throw new EapMsChapV2ParsingException( 341 "Message must be formatted as: E=<error_code> R=<retry bit> C=<challenge>" 342 + " V=<password_change_protocol> M=<message>"); 343 } 344 345 this.errorCode = Integer.parseInt(mappings.get(ERROR_LABEL)); 346 this.isRetryable = IS_RETRYABLE_FLAG.equals(mappings.get(RETRY_LABEL)); 347 this.challenge = hexStringToByteArray(mappings.get(CHALLENGE_LABEL)); 348 this.passwordChangeProtocol = Integer.parseInt(mappings.get( 349 PASSWORD_CHANGE_PROTOCOL_LABEL)); 350 this.message = mappings.get("M"); 351 352 if (challenge.length != CHALLENGE_LENGTH) { 353 throw new EapMsChapV2ParsingException("Challenge must be 16B long"); 354 } 355 } 356 357 @VisibleForTesting EapMsChapV2FailureRequest( int msChapV2Id, int msLength, int errorCode, boolean isRetryable, byte[] challenge, int passwordChangeProtocol, String message)358 public EapMsChapV2FailureRequest( 359 int msChapV2Id, 360 int msLength, 361 int errorCode, 362 boolean isRetryable, 363 byte[] challenge, 364 int passwordChangeProtocol, 365 String message) 366 throws EapMsChapV2ParsingException { 367 super(EAP_MSCHAP_V2_FAILURE, msChapV2Id, msLength); 368 369 this.errorCode = errorCode; 370 this.isRetryable = isRetryable; 371 this.challenge = challenge; 372 this.passwordChangeProtocol = passwordChangeProtocol; 373 this.message = message; 374 375 if (challenge.length != CHALLENGE_LENGTH) { 376 throw new EapMsChapV2ParsingException("Challenge length must be 16B"); 377 } 378 } 379 } 380 381 /** 382 * EapMsChapV2FailureResponse represents the EAP MSCHAPv2 Failure Response Packet 383 * (EAP MSCHAPv2#2.6). 384 */ 385 public static class EapMsChapV2FailureResponse extends EapMsChapV2TypeData { EapMsChapV2FailureResponse()386 private EapMsChapV2FailureResponse() throws EapMsChapV2ParsingException { 387 super(EAP_MSCHAP_V2_FAILURE); 388 } 389 390 /** 391 * Constructs and returns a new EAP MSCHAPv2 Failure Response type data. 392 * 393 * @return a new EapMsChapV2FailureResponse instance 394 */ getEapMsChapV2FailureResponse()395 public static EapMsChapV2FailureResponse getEapMsChapV2FailureResponse() { 396 try { 397 return new EapMsChapV2FailureResponse(); 398 } catch (EapMsChapV2ParsingException ex) { 399 // This should never happen 400 LOG.wtf( 401 EapMsChapV2SuccessResponse.class.getSimpleName(), 402 "ParsingException thrown while creating EapMsChapV2FailureResponse", 403 ex); 404 return null; 405 } 406 } 407 408 @Override encode()409 public byte[] encode() { 410 return new byte[] {(byte) EAP_MSCHAP_V2_FAILURE}; 411 } 412 } 413 414 @VisibleForTesting getMessageMappings(String message)415 static Map<String, String> getMessageMappings(String message) 416 throws EapMsChapV2ParsingException { 417 Map<String, String> messageMappings = new HashMap<>(); 418 int mPos = message.indexOf(MESSAGE_PREFIX); 419 420 String preMString; 421 if (mPos == -1) { 422 preMString = message; 423 messageMappings.put(MESSAGE_LABEL, "<omitted by authenticator>"); 424 } else { 425 preMString = message.substring(0, mPos); 426 messageMappings.put(MESSAGE_LABEL, message.substring(mPos + MESSAGE_PREFIX.length())); 427 } 428 429 // preMString: "S=<auth string> " or "E=<error> R=r C=<challenge> V=<version> " 430 for (String value : preMString.split(" ")) { 431 String[] keyValue = value.split("="); 432 if (keyValue.length != LABEL_VALUE_LENGTH) { 433 throw new EapMsChapV2ParsingException( 434 "Message must be formatted <label character>=<value>"); 435 } else if (messageMappings.containsKey(keyValue[0])) { 436 throw new EapMsChapV2ParsingException( 437 "Duplicated key-value pair in message: " + LOG.pii(message)); 438 } 439 messageMappings.put(keyValue[0], keyValue[1]); 440 } 441 442 return messageMappings; 443 } 444 445 @VisibleForTesting hexStringToByteArray(String hexString)446 static byte[] hexStringToByteArray(String hexString) 447 throws EapMsChapV2ParsingException, NumberFormatException { 448 if (hexString.length() % 2 != 0) { 449 throw new EapMsChapV2ParsingException( 450 "Hex string must contain an even number of characters"); 451 } 452 453 byte[] dataBytes = new byte[hexString.length() / 2]; 454 for (int i = 0; i < hexString.length(); i += 2) { 455 dataBytes[i / 2] = (byte) Integer.parseInt(hexString.substring(i, i + 2), 16); 456 } 457 return dataBytes; 458 } 459 460 /** Class for decoding EAP MSCHAPv2 type data. */ 461 public static class EapMsChapV2TypeDataDecoder { 462 /** 463 * Returns the EAP MSCHAPv2 Op Code for the given EAP type data. 464 * 465 * @param eapTypeData byte[] type data to read the Op Code from 466 * @return the EAP MSCHAPv2 Op Code for the current type data 467 * @throws BufferUnderflowException iff eapTypeData.length == 0 468 */ getOpCode(byte[] eapTypeData)469 public int getOpCode(byte[] eapTypeData) throws BufferUnderflowException { 470 return Byte.toUnsignedInt(ByteBuffer.wrap(eapTypeData).get()); 471 } 472 473 /** 474 * Decodes and returns an EapMsChapV2ChallengeRequest for the specified eapTypeData. 475 * 476 * @param tag String for logging tag 477 * @param eapTypeData byte[] to be decoded as an EapMsChapV2ChallengeRequest instance 478 * @return DecodeResult wrapping an EapMsChapV2ChallengeRequest instance for the given 479 * eapTypeData iff the eapTypeData is formatted correctly. Otherwise, the DecodeResult 480 * wraps the appropriate EapError. 481 */ decodeChallengeRequest( String tag, byte[] eapTypeData)482 public DecodeResult<EapMsChapV2ChallengeRequest> decodeChallengeRequest( 483 String tag, byte[] eapTypeData) { 484 try { 485 ByteBuffer buffer = ByteBuffer.wrap(eapTypeData); 486 int opCode = Byte.toUnsignedInt(buffer.get()); 487 488 if (opCode != EAP_MSCHAP_V2_CHALLENGE) { 489 return new DecodeResult<>( 490 new EapError( 491 new EapMsChapV2ParsingException( 492 "Received type data with invalid opCode: " 493 + EAP_OP_CODE_STRING.getOrDefault( 494 opCode, "Unknown (" + opCode + ")")))); 495 } 496 497 return new DecodeResult<>(new EapMsChapV2ChallengeRequest(buffer)); 498 } catch (BufferUnderflowException | EapMsChapV2ParsingException ex) { 499 LOG.e(tag, "Error parsing EAP MSCHAPv2 Challenge Request type data", ex); 500 return new DecodeResult<>(new EapError(ex)); 501 } 502 } 503 504 /** 505 * Decodes and returns an EapMsChapV2SuccessRequest for the specified eapTypeData. 506 * 507 * @param tag String for logging tag 508 * @param eapTypeData byte[] to be decoded as an EapMsChapV2SuccessRequest instance 509 * @return DecodeResult wrapping an EapMsChapV2SuccessRequest instance for the given 510 * eapTypeData iff the eapTypeData is formatted correctly. Otherwise, the DecodeResult 511 * wraps the appropriate EapError. 512 */ decodeSuccessRequest( String tag, byte[] eapTypeData)513 public DecodeResult<EapMsChapV2SuccessRequest> decodeSuccessRequest( 514 String tag, byte[] eapTypeData) { 515 try { 516 ByteBuffer buffer = ByteBuffer.wrap(eapTypeData); 517 int opCode = Byte.toUnsignedInt(buffer.get()); 518 519 if (opCode != EAP_MSCHAP_V2_SUCCESS) { 520 return new DecodeResult<>( 521 new EapError( 522 new EapMsChapV2ParsingException( 523 "Received type data with invalid opCode: " 524 + EAP_OP_CODE_STRING.getOrDefault( 525 opCode, "Unknown (" + opCode + ")")))); 526 } 527 528 return new DecodeResult<>(new EapMsChapV2SuccessRequest(buffer)); 529 } catch (BufferUnderflowException 530 | NumberFormatException 531 | EapMsChapV2ParsingException ex) { 532 LOG.e(tag, "Error parsing EAP MSCHAPv2 Success Request type data", ex); 533 return new DecodeResult<>(new EapError(ex)); 534 } 535 } 536 537 /** 538 * Decodes and returns an EapMsChapV2FailureRequest for the specified eapTypeData. 539 * 540 * @param tag String for logging tag 541 * @param eapTypeData byte[] to be decoded as an EapMsChapV2FailureRequest instance 542 * @return DecodeResult wrapping an EapMsChapV2FailureRequest instance for the given 543 * eapTypeData iff the eapTypeData is formatted correctly. Otherwise, the DecodeResult 544 * wraps the appropriate EapError. 545 */ decodeFailureRequest( String tag, byte[] eapTypeData)546 public DecodeResult<EapMsChapV2FailureRequest> decodeFailureRequest( 547 String tag, byte[] eapTypeData) { 548 try { 549 ByteBuffer buffer = ByteBuffer.wrap(eapTypeData); 550 int opCode = Byte.toUnsignedInt(buffer.get()); 551 552 if (opCode != EAP_MSCHAP_V2_FAILURE) { 553 return new DecodeResult<>( 554 new EapError( 555 new EapMsChapV2ParsingException( 556 "Received type data with invalid opCode: " 557 + EAP_OP_CODE_STRING.getOrDefault( 558 opCode, "Unknown (" + opCode + ")")))); 559 } 560 561 return new DecodeResult<>(new EapMsChapV2FailureRequest(buffer)); 562 } catch (BufferUnderflowException 563 | NumberFormatException 564 | EapMsChapV2ParsingException ex) { 565 LOG.e(tag, "Error parsing EAP MSCHAPv2 Failure Request type data", ex); 566 return new DecodeResult<>(new EapError(ex)); 567 } 568 } 569 570 /** 571 * DecodeResult represents the result from calling a decode method within 572 * EapMsChapV2TypeDataDecoder. It will contain either an EapMsChapV2TypeData or an EapError. 573 * 574 * @param <T> The EapMsChapV2TypeData type that is wrapped in this DecodeResult 575 */ 576 public static class DecodeResult<T extends EapMsChapV2TypeData> { 577 public final T eapTypeData; 578 public final EapError eapError; 579 DecodeResult(T eapTypeData)580 public DecodeResult(T eapTypeData) { 581 this.eapTypeData = eapTypeData; 582 this.eapError = null; 583 } 584 DecodeResult(EapError eapError)585 public DecodeResult(EapError eapError) { 586 this.eapTypeData = null; 587 this.eapError = eapError; 588 } 589 590 /** 591 * Checks whether this instance represents a successful decode operation. 592 * 593 * @return true iff this DecodeResult represents a successfully decoded Type Data 594 */ isSuccessfulDecode()595 public boolean isSuccessfulDecode() { 596 return eapTypeData != null; 597 } 598 } 599 } 600 } 601