aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/eap/message/EapMessage.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/internal/net/eap/message/EapMessage.java')
-rw-r--r--src/java/com/android/internal/net/eap/message/EapMessage.java252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/eap/message/EapMessage.java b/src/java/com/android/internal/net/eap/message/EapMessage.java
new file mode 100644
index 00000000..bb1c6329
--- /dev/null
+++ b/src/java/com/android/internal/net/eap/message/EapMessage.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net.eap.message;
+
+import static com.android.internal.net.eap.EapAuthenticator.LOG;
+import static com.android.internal.net.eap.message.EapData.EAP_NAK;
+import static com.android.internal.net.eap.message.EapData.NOTIFICATION_DATA;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.net.eap.EapResult;
+import com.android.internal.net.eap.EapResult.EapError;
+import com.android.internal.net.eap.EapResult.EapResponse;
+import com.android.internal.net.eap.exceptions.EapInvalidPacketLengthException;
+import com.android.internal.net.eap.exceptions.EapSilentException;
+import com.android.internal.net.eap.exceptions.InvalidEapCodeException;
+import com.android.internal.net.eap.exceptions.UnsupportedEapTypeException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * EapMessage represents an EAP Message.
+ *
+ * <p>EapMessages will be of type:
+ * <ul>
+ * <li>@{link EAP_CODE_REQUEST}</li>
+ * <li>@{link EAP_CODE_RESPONSE}</li>
+ * <li>@{link EAP_CODE_SUCCESS}</li>
+ * <li>@{link EAP_CODE_FAILURE}</li>
+ * </ul>
+ *
+ * Per RFC 3748 Section 4, EAP-Request and EAP-Response packets should be in the format:
+ *
+ * +-----------------+-----------------+----------------------------------+
+ * | Code (1B) | Identifier (1B) | Length (2B) |
+ * +-----------------+-----------------+----------------------------------+
+ * | Type (1B) | Type-Data ...
+ * +-----------------+-----
+ *
+ * EAP-Success and EAP-Failure packets should be in the format:
+ *
+ * +-----------------+-----------------+----------------------------------+
+ * | Code (1B) | Identifier (1B) | Length (2B) = '0004' |
+ * +-----------------+-----------------+----------------------------------+
+ *
+ * Note that Length includes the EAP Header bytes.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc3748#section-4">RFC 3748, Extensible Authentication
+ * Protocol (EAP)</a>
+ */
+public class EapMessage {
+ private static final String TAG = EapMessage.class.getSimpleName();
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ EAP_CODE_REQUEST,
+ EAP_CODE_RESPONSE,
+ EAP_CODE_SUCCESS,
+ EAP_CODE_FAILURE
+ })
+ public @interface EapCode {}
+
+ public static final int EAP_CODE_REQUEST = 1;
+ public static final int EAP_CODE_RESPONSE = 2;
+ public static final int EAP_CODE_SUCCESS = 3;
+ public static final int EAP_CODE_FAILURE = 4;
+
+ public static final Map<Integer, String> EAP_CODE_STRING = new HashMap<>();
+ static {
+ EAP_CODE_STRING.put(EAP_CODE_REQUEST, "REQUEST");
+ EAP_CODE_STRING.put(EAP_CODE_RESPONSE, "RESPONSE");
+ EAP_CODE_STRING.put(EAP_CODE_SUCCESS, "SUCCESS");
+ EAP_CODE_STRING.put(EAP_CODE_FAILURE, "FAILURE");
+ }
+
+ public static final int EAP_HEADER_LENGTH = 4;
+
+ @EapCode public final int eapCode;
+ public final int eapIdentifier;
+ public final int eapLength;
+ public final EapData eapData;
+
+ public EapMessage(@EapCode int eapCode, int eapIdentifier, @Nullable EapData eapData)
+ throws EapSilentException {
+ this.eapCode = eapCode;
+ this.eapIdentifier = eapIdentifier;
+ this.eapLength = EAP_HEADER_LENGTH + ((eapData == null) ? 0 : eapData.getLength());
+ this.eapData = eapData;
+
+ validate();
+ }
+
+ /**
+ * Decodes and returns an EapMessage from the given byte array.
+ *
+ * @param packet byte array containing a byte-encoded EapMessage
+ * @return the EapMessage instance representing the given {@param packet}
+ * @throws EapSilentException for decoding errors that must be discarded silently
+ */
+ public static EapMessage decode(@NonNull byte[] packet) throws EapSilentException {
+ ByteBuffer buffer = ByteBuffer.wrap(packet);
+ int eapCode;
+ int eapIdentifier;
+ int eapLength;
+ EapData eapData;
+ try {
+ eapCode = Byte.toUnsignedInt(buffer.get());
+ eapIdentifier = Byte.toUnsignedInt(buffer.get());
+ eapLength = Short.toUnsignedInt(buffer.getShort());
+
+ if (eapCode == EAP_CODE_REQUEST || eapCode == EAP_CODE_RESPONSE) {
+ int eapType = Byte.toUnsignedInt(buffer.get());
+ if (!EapData.isSupportedEapType(eapType)) {
+ LOG.e(TAG, "Decoding EAP packet with unsupported EAP-Type: " + eapType);
+ throw new UnsupportedEapTypeException(eapIdentifier,
+ "Unsupported eapType=" + eapType);
+ }
+
+ byte[] eapDataBytes = new byte[buffer.remaining()];
+ buffer.get(eapDataBytes);
+ eapData = new EapData(eapType, eapDataBytes);
+ } else {
+ eapData = null;
+ }
+ } catch (BufferUnderflowException ex) {
+ String msg = "EAP packet is missing required values";
+ LOG.e(TAG, msg, ex);
+ throw new EapInvalidPacketLengthException(msg, ex);
+ }
+
+ int eapDataLength = (eapData == null) ? 0 : eapData.getLength();
+ if (eapLength > EAP_HEADER_LENGTH + eapDataLength) {
+ String msg = "Packet is shorter than specified length";
+ LOG.e(TAG, msg);
+ throw new EapInvalidPacketLengthException(msg);
+ }
+
+ return new EapMessage(eapCode, eapIdentifier, eapData);
+ }
+
+ /**
+ * Converts this EapMessage instance to its byte-encoded representation.
+ *
+ * @return byte[] representing the byte-encoded EapMessage
+ */
+ public byte[] encode() {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(eapLength);
+ byteBuffer.put((byte) eapCode);
+ byteBuffer.put((byte) eapIdentifier);
+ byteBuffer.putShort((short) eapLength);
+
+ if (eapData != null) {
+ eapData.encodeToByteBuffer(byteBuffer);
+ }
+
+ return byteBuffer.array();
+ }
+
+ /**
+ * Creates and returns an EAP-Response/Notification message for the given EAP Identifier wrapped
+ * in an EapResponse object.
+ *
+ * @param eapIdentifier the identifier for the message being responded to
+ * @return an EapResponse object containing an EAP-Response/Notification message with an
+ * identifier matching the given identifier, or an EapError if an exception was thrown
+ */
+ public static EapResult getNotificationResponse(int eapIdentifier) {
+ try {
+ return EapResponse.getEapResponse(
+ new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, NOTIFICATION_DATA));
+ } catch (EapSilentException ex) {
+ // this should never happen - the only variable value is the identifier
+ LOG.wtf(TAG, "Failed to create Notification Response for message with identifier="
+ + eapIdentifier);
+ return new EapError(ex);
+ }
+ }
+
+ /**
+ * Creates and returns an EAP-Response/Nak message for the given EAP Identifier wrapped in an
+ * EapResponse object.
+ *
+ * @param eapIdentifier the identifier for the message being responded to
+ * @param supportedEapTypes Collection of EAP Method types supported by the EAP Session
+ * @return an EapResponse object containing an EAP-Response/Nak message with an identifier
+ * matching the given identifier, or an EapError if an exception was thrown
+ */
+ public static EapResult getNakResponse(
+ int eapIdentifier,
+ Collection<Integer> supportedEapTypes) {
+ try {
+ ByteBuffer buffer = ByteBuffer.allocate(supportedEapTypes.size());
+ for (int eapMethodType : supportedEapTypes) {
+ buffer.put((byte) eapMethodType);
+ }
+ EapData nakData = new EapData(EAP_NAK, buffer.array());
+
+ return EapResponse.getEapResponse(
+ new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, nakData));
+ } catch (EapSilentException ex) {
+ // this should never happen - the only variable value is the identifier
+ LOG.wtf(TAG, "Failed to create Nak for message with identifier="
+ + eapIdentifier);
+ return new EapError(ex);
+ }
+ }
+
+ private void validate() throws EapSilentException {
+ if (eapCode != EAP_CODE_REQUEST
+ && eapCode != EAP_CODE_RESPONSE
+ && eapCode != EAP_CODE_SUCCESS
+ && eapCode != EAP_CODE_FAILURE) {
+ LOG.e(TAG, "Invalid EAP Code: " + eapCode);
+ throw new InvalidEapCodeException(eapCode);
+ }
+
+ if ((eapCode == EAP_CODE_SUCCESS || eapCode == EAP_CODE_FAILURE)
+ && eapLength != EAP_HEADER_LENGTH) {
+ LOG.e(TAG, "Invalid length for EAP-Success/EAP-Failure. Length: " + eapLength);
+ throw new EapInvalidPacketLengthException(
+ "EAP Success/Failure packets must be length 4");
+ }
+
+ if ((eapCode == EAP_CODE_REQUEST || eapCode == EAP_CODE_RESPONSE) && eapData == null) {
+ LOG.e(TAG, "No Type value included for EAP-Request/EAP-Response");
+ throw new EapInvalidPacketLengthException(
+ "EAP Request/Response packets must include a Type value");
+ }
+ }
+}