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; 18 19 import static com.android.internal.net.eap.EapAuthenticator.LOG; 20 import static com.android.internal.net.eap.message.EapData.EAP_NAK; 21 import static com.android.internal.net.eap.message.EapData.NOTIFICATION_DATA; 22 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 27 import com.android.internal.net.eap.EapResult; 28 import com.android.internal.net.eap.EapResult.EapError; 29 import com.android.internal.net.eap.EapResult.EapResponse; 30 import com.android.internal.net.eap.exceptions.EapInvalidPacketLengthException; 31 import com.android.internal.net.eap.exceptions.EapSilentException; 32 import com.android.internal.net.eap.exceptions.InvalidEapCodeException; 33 import com.android.internal.net.eap.exceptions.UnsupportedEapTypeException; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.nio.BufferUnderflowException; 38 import java.nio.ByteBuffer; 39 import java.util.Collection; 40 import java.util.HashMap; 41 import java.util.Map; 42 43 /** 44 * EapMessage represents an EAP Message. 45 * 46 * <p>EapMessages will be of type: 47 * <ul> 48 * <li>@{link EAP_CODE_REQUEST}</li> 49 * <li>@{link EAP_CODE_RESPONSE}</li> 50 * <li>@{link EAP_CODE_SUCCESS}</li> 51 * <li>@{link EAP_CODE_FAILURE}</li> 52 * </ul> 53 * 54 * Per RFC 3748 Section 4, EAP-Request and EAP-Response packets should be in the format: 55 * 56 * +-----------------+-----------------+----------------------------------+ 57 * | Code (1B) | Identifier (1B) | Length (2B) | 58 * +-----------------+-----------------+----------------------------------+ 59 * | Type (1B) | Type-Data ... 60 * +-----------------+----- 61 * 62 * EAP-Success and EAP-Failure packets should be in the format: 63 * 64 * +-----------------+-----------------+----------------------------------+ 65 * | Code (1B) | Identifier (1B) | Length (2B) = '0004' | 66 * +-----------------+-----------------+----------------------------------+ 67 * 68 * Note that Length includes the EAP Header bytes. 69 * 70 * @see <a href="https://tools.ietf.org/html/rfc3748#section-4">RFC 3748, Extensible Authentication 71 * Protocol (EAP)</a> 72 */ 73 public class EapMessage { 74 private static final String TAG = EapMessage.class.getSimpleName(); 75 76 @Retention(RetentionPolicy.SOURCE) 77 @IntDef({ 78 EAP_CODE_REQUEST, 79 EAP_CODE_RESPONSE, 80 EAP_CODE_SUCCESS, 81 EAP_CODE_FAILURE 82 }) 83 public @interface EapCode {} 84 85 public static final int EAP_CODE_REQUEST = 1; 86 public static final int EAP_CODE_RESPONSE = 2; 87 public static final int EAP_CODE_SUCCESS = 3; 88 public static final int EAP_CODE_FAILURE = 4; 89 90 public static final Map<Integer, String> EAP_CODE_STRING = new HashMap<>(); 91 static { EAP_CODE_STRING.put(EAP_CODE_REQUEST, "REQUEST")92 EAP_CODE_STRING.put(EAP_CODE_REQUEST, "REQUEST"); EAP_CODE_STRING.put(EAP_CODE_RESPONSE, "RESPONSE")93 EAP_CODE_STRING.put(EAP_CODE_RESPONSE, "RESPONSE"); EAP_CODE_STRING.put(EAP_CODE_SUCCESS, "SUCCESS")94 EAP_CODE_STRING.put(EAP_CODE_SUCCESS, "SUCCESS"); EAP_CODE_STRING.put(EAP_CODE_FAILURE, "FAILURE")95 EAP_CODE_STRING.put(EAP_CODE_FAILURE, "FAILURE"); 96 } 97 98 public static final int EAP_HEADER_LENGTH = 4; 99 100 @EapCode public final int eapCode; 101 public final int eapIdentifier; 102 public final int eapLength; 103 public final EapData eapData; 104 EapMessage(@apCode int eapCode, int eapIdentifier, @Nullable EapData eapData)105 public EapMessage(@EapCode int eapCode, int eapIdentifier, @Nullable EapData eapData) 106 throws EapSilentException { 107 this.eapCode = eapCode; 108 this.eapIdentifier = eapIdentifier; 109 this.eapLength = EAP_HEADER_LENGTH + ((eapData == null) ? 0 : eapData.getLength()); 110 this.eapData = eapData; 111 112 validate(); 113 } 114 115 /** 116 * Decodes and returns an EapMessage from the given byte array. 117 * 118 * @param packet byte array containing a byte-encoded EapMessage 119 * @return the EapMessage instance representing the given {@param packet} 120 * @throws EapSilentException for decoding errors that must be discarded silently 121 */ decode(@onNull byte[] packet)122 public static EapMessage decode(@NonNull byte[] packet) throws EapSilentException { 123 ByteBuffer buffer = ByteBuffer.wrap(packet); 124 int eapCode; 125 int eapIdentifier; 126 int eapLength; 127 EapData eapData; 128 try { 129 eapCode = Byte.toUnsignedInt(buffer.get()); 130 eapIdentifier = Byte.toUnsignedInt(buffer.get()); 131 eapLength = Short.toUnsignedInt(buffer.getShort()); 132 133 if (eapCode == EAP_CODE_REQUEST || eapCode == EAP_CODE_RESPONSE) { 134 int eapType = Byte.toUnsignedInt(buffer.get()); 135 if (!EapData.isSupportedEapType(eapType)) { 136 LOG.e(TAG, "Decoding EAP packet with unsupported EAP-Type: " + eapType); 137 throw new UnsupportedEapTypeException(eapIdentifier, 138 "Unsupported eapType=" + eapType); 139 } 140 141 // Length of data to go into EapData.eapTypeData = 142 // eapLength - EAP_HEADER_LENGTH - 1B (eapType) 143 int eapDataLengthRemaining = Math.max(0, eapLength - EAP_HEADER_LENGTH - 1); 144 byte[] eapDataBytes = 145 new byte[Math.min(eapDataLengthRemaining, buffer.remaining())]; 146 147 buffer.get(eapDataBytes); 148 eapData = new EapData(eapType, eapDataBytes); 149 } else { 150 eapData = null; 151 } 152 } catch (BufferUnderflowException ex) { 153 String msg = "EAP packet is missing required values"; 154 LOG.e(TAG, msg, ex); 155 throw new EapInvalidPacketLengthException(msg, ex); 156 } 157 158 int eapDataLength = (eapData == null) ? 0 : eapData.getLength(); 159 if (eapLength > EAP_HEADER_LENGTH + eapDataLength) { 160 String msg = "Packet is shorter than specified length"; 161 LOG.e(TAG, msg); 162 throw new EapInvalidPacketLengthException(msg); 163 } 164 165 return new EapMessage(eapCode, eapIdentifier, eapData); 166 } 167 168 /** 169 * Converts this EapMessage instance to its byte-encoded representation. 170 * 171 * @return byte[] representing the byte-encoded EapMessage 172 */ encode()173 public byte[] encode() { 174 ByteBuffer byteBuffer = ByteBuffer.allocate(eapLength); 175 byteBuffer.put((byte) eapCode); 176 byteBuffer.put((byte) eapIdentifier); 177 byteBuffer.putShort((short) eapLength); 178 179 if (eapData != null) { 180 eapData.encodeToByteBuffer(byteBuffer); 181 } 182 183 return byteBuffer.array(); 184 } 185 186 /** 187 * Creates and returns an EAP-Response/Notification message for the given EAP Identifier wrapped 188 * in an EapResponse object. 189 * 190 * @param eapIdentifier the identifier for the message being responded to 191 * @return an EapResponse object containing an EAP-Response/Notification message with an 192 * identifier matching the given identifier, or an EapError if an exception was thrown 193 */ getNotificationResponse(int eapIdentifier)194 public static EapResult getNotificationResponse(int eapIdentifier) { 195 try { 196 return EapResponse.getEapResponse( 197 new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, NOTIFICATION_DATA)); 198 } catch (EapSilentException ex) { 199 // this should never happen - the only variable value is the identifier 200 LOG.wtf(TAG, "Failed to create Notification Response for message with identifier=" 201 + eapIdentifier); 202 return new EapError(ex); 203 } 204 } 205 206 /** 207 * Creates and returns an EAP-Response/Nak message for the given EAP Identifier wrapped in an 208 * EapResponse object. 209 * 210 * @param eapIdentifier the identifier for the message being responded to 211 * @param supportedEapTypes Collection of EAP Method types supported by the EAP Session 212 * @return an EapResponse object containing an EAP-Response/Nak message with an identifier 213 * matching the given identifier, or an EapError if an exception was thrown 214 */ getNakResponse( int eapIdentifier, Collection<Integer> supportedEapTypes)215 public static EapResult getNakResponse( 216 int eapIdentifier, 217 Collection<Integer> supportedEapTypes) { 218 try { 219 ByteBuffer buffer = ByteBuffer.allocate(supportedEapTypes.size()); 220 for (int eapMethodType : supportedEapTypes) { 221 buffer.put((byte) eapMethodType); 222 } 223 EapData nakData = new EapData(EAP_NAK, buffer.array()); 224 225 return EapResponse.getEapResponse( 226 new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, nakData)); 227 } catch (EapSilentException ex) { 228 // this should never happen - the only variable value is the identifier 229 LOG.wtf(TAG, "Failed to create Nak for message with identifier=" 230 + eapIdentifier); 231 return new EapError(ex); 232 } 233 } 234 validate()235 private void validate() throws EapSilentException { 236 if (eapCode != EAP_CODE_REQUEST 237 && eapCode != EAP_CODE_RESPONSE 238 && eapCode != EAP_CODE_SUCCESS 239 && eapCode != EAP_CODE_FAILURE) { 240 LOG.e(TAG, "Invalid EAP Code: " + eapCode); 241 throw new InvalidEapCodeException(eapCode); 242 } 243 244 if ((eapCode == EAP_CODE_SUCCESS || eapCode == EAP_CODE_FAILURE) 245 && eapLength != EAP_HEADER_LENGTH) { 246 LOG.e(TAG, "Invalid length for EAP-Success/EAP-Failure. Length: " + eapLength); 247 throw new EapInvalidPacketLengthException( 248 "EAP Success/Failure packets must be length 4"); 249 } 250 251 if ((eapCode == EAP_CODE_REQUEST || eapCode == EAP_CODE_RESPONSE) && eapData == null) { 252 LOG.e(TAG, "No Type value included for EAP-Request/EAP-Response"); 253 throw new EapInvalidPacketLengthException( 254 "EAP Request/Response packets must include a Type value"); 255 } 256 } 257 } 258