blob: db1655d1c8a6f67005d0fdaaa1ec1a6c9fa4e2ec [file] [log] [blame]
/*
* 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);
}
// Length of data to go into EapData.eapTypeData =
// eapLength - EAP_HEADER_LENGTH - 1B (eapType)
int eapDataLengthRemaining = Math.max(0, eapLength - EAP_HEADER_LENGTH - 1);
byte[] eapDataBytes =
new byte[Math.min(eapDataLengthRemaining, 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");
}
}
}