1 /* 2 * Copyright (C) 2020 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.ttls; 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.ttls.EapTtlsParsingException; 24 25 import java.nio.BufferUnderflowException; 26 import java.nio.ByteBuffer; 27 28 /** 29 * EapTtlsAvp represents the structure of an AVP during an EAP-TTLS session (RFC5281#10.1) The 30 * structure of the flag byte is as follows: 31 * 32 * <pre> 33 * |---+---+---+---+---+-----------+ 34 * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 35 * | V | M | r | r | r | r | r | r | 36 * |---+---+---+---+---+---+---+---+ 37 * V = Vendor ID present 38 * M = AVP support is mandatory 39 * r = Reserved bits (must be ignored) 40 * </pre> 41 * 42 * @see <a href="https://tools.ietf.org/html/rfc5281#section-10.1">RFC 5281, Extensible 43 * Authentication Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0 44 * (EAP-TTLSv0)</a> 45 */ 46 public class EapTtlsAvp { 47 private static final String TAG = EapTtlsAvp.class.getSimpleName(); 48 49 // AVP code derived from RFC3579#3.1. Note that the EAP-TTLS uses an custom AVP structure (see 50 // RFC 5281, section 10.1), as opposed to the one defined in RFC 3579. 51 private static final int EAP_MESSAGE_AVP_CODE = 79; 52 53 private static final int AVP_CODE_LEN_BYTES = 4; 54 private static final int AVP_FLAGS_LEN_BYTES = 1; 55 private static final int AVP_LENGTH_LEN_BYTES = 3; 56 private static final int AVP_VENDOR_ID_LEN_BYTES = 4; 57 private static final int AVP_HEADER_LEN_BYTES = 58 AVP_CODE_LEN_BYTES + AVP_FLAGS_LEN_BYTES + AVP_LENGTH_LEN_BYTES; 59 private static final int AVP_BYTE_ALIGNMENT = 4; 60 61 private static final int FLAG_VENDOR_ID_INCLUDED = 1 << 7; 62 private static final int FLAG_AVP_MANDATORY = 1 << 6; 63 64 public final int avpCode; 65 public final int avpLength; 66 public final int vendorId; 67 public final byte[] data; 68 69 public final boolean isMandatory; 70 public final boolean isVendorIdPresent; 71 72 @VisibleForTesting EapTtlsAvp(ByteBuffer buffer)73 EapTtlsAvp(ByteBuffer buffer) throws EapTtlsParsingException { 74 avpCode = buffer.getInt(); 75 byte avpFlags = buffer.get(); 76 77 isMandatory = (avpFlags & FLAG_AVP_MANDATORY) != 0; 78 isVendorIdPresent = (avpFlags & FLAG_VENDOR_ID_INCLUDED) != 0; 79 80 avpLength = getAvpLength(buffer); 81 int dataLength = avpLength - AVP_HEADER_LEN_BYTES; 82 83 if (isVendorIdPresent) { 84 dataLength -= AVP_VENDOR_ID_LEN_BYTES; 85 vendorId = buffer.getInt(); 86 } else { 87 // no vendor ID is equivalent to a vendor ID of 0 (RFC5281#10.1) 88 vendorId = 0; 89 } 90 91 if (dataLength < 0) { 92 throw new EapTtlsParsingException( 93 "Received an AVP with an invalid length: " 94 + avpLength 95 + ". Data length was predicted to be " 96 + dataLength); 97 } 98 99 data = new byte[dataLength]; 100 buffer.get(data); 101 102 // the remaining padding is consumed in order to align with the next AVP header 103 int paddingSize = getAvpPadding(avpLength); 104 buffer.get(new byte[paddingSize]); 105 } 106 EapTtlsAvp(int avpCode, int vendorId, boolean isMandatory, byte[] data)107 private EapTtlsAvp(int avpCode, int vendorId, boolean isMandatory, byte[] data) { 108 this.avpCode = avpCode; 109 this.vendorId = vendorId; 110 this.isMandatory = isMandatory; 111 this.data = data; 112 // A vendor ID of 0 is equivalent to not sending the vendor ID at all (RFC5281#10.1) 113 if (vendorId != 0) { 114 avpLength = data.length + AVP_HEADER_LEN_BYTES + AVP_VENDOR_ID_LEN_BYTES; 115 isVendorIdPresent = true; 116 } else { 117 avpLength = data.length + AVP_HEADER_LEN_BYTES; 118 isVendorIdPresent = false; 119 } 120 } 121 122 /** 123 * Assembles each bit from the flag byte into a byte 124 * 125 * @return a byte that compromises the avp flags 126 */ getFlagByte()127 private byte getFlagByte() { 128 int flag = 0; 129 flag |= isVendorIdPresent ? FLAG_VENDOR_ID_INCLUDED : 0; 130 flag |= isMandatory ? FLAG_AVP_MANDATORY : 0; 131 return (byte) flag; 132 } 133 134 /** 135 * Encodes this AVP instance into a byte array. 136 * 137 * @return byte[] representing the encoded value of this EapTtlsAvp instance 138 */ encode()139 public byte[] encode() { 140 // Each AVP must be padded to the next 4 byte boundary (RFC5281#10.2), so 0 to 3 padding 141 // bytes may be added to the original length 142 int paddedAvpLength = avpLength + getAvpPadding(avpLength); 143 144 ByteBuffer encodedBuffer = ByteBuffer.allocate(paddedAvpLength); 145 146 encodedBuffer.putInt(avpCode); 147 encodedBuffer.put(getFlagByte()); 148 encodeAvpLength(encodedBuffer, avpLength); 149 if (isVendorIdPresent) { 150 encodedBuffer.putInt(vendorId); 151 } 152 encodedBuffer.put(data); 153 154 return encodedBuffer.array(); 155 } 156 157 /** 158 * Produces an EAP-MESSAGE AVP (RFC5281#10.1) 159 * 160 * @param data the data to encode in the avp 161 * @param vendorId the vendorId or 0 if not specified 162 * @return an EAP-MESSAGE AVP 163 */ getEapMessageAvp(int vendorId, byte[] data)164 public static EapTtlsAvp getEapMessageAvp(int vendorId, byte[] data) { 165 return new EapTtlsAvp(EAP_MESSAGE_AVP_CODE, vendorId, true /* isMandatory */, data); 166 } 167 168 /** 169 * Retrieves the required padding bytes (4 byte aligned) for a given length 170 * 171 * @param avpLength the length to pad 172 * @return the required padding bytes 173 */ 174 @VisibleForTesting getAvpPadding(int avpLength)175 static int getAvpPadding(int avpLength) { 176 if (avpLength % AVP_BYTE_ALIGNMENT == 0) { 177 return 0; 178 } 179 return AVP_BYTE_ALIGNMENT - (avpLength % AVP_BYTE_ALIGNMENT); 180 } 181 182 /** 183 * Encodes an AVP length into a given bytebuffer 184 * 185 * <p>As per RFC5281#10.2, the avp length field is 3 bytes 186 * 187 * @param buffer the bytebuffer to encode the length into 188 * @param length the length to encode 189 */ 190 @VisibleForTesting encodeAvpLength(ByteBuffer buffer, int length)191 static void encodeAvpLength(ByteBuffer buffer, int length) { 192 buffer.put((byte) (length >> 16)); 193 buffer.put((byte) (length >> 8)); 194 buffer.put((byte) length); 195 } 196 197 /** 198 * Converts a byte array of size 3 to its integer representation 199 * 200 * <p>As per RFC5281#10.2, the AVP length field is 3 bytes 201 * 202 * @param buffer a byte buffer to extract the length from 203 * @return an int representation of the byte array 204 * @throws BufferUnderflowException if the buffer has less than 3 bytes remaining 205 */ 206 @VisibleForTesting getAvpLength(ByteBuffer buffer)207 static int getAvpLength(ByteBuffer buffer) throws BufferUnderflowException { 208 return (Byte.toUnsignedInt(buffer.get()) << 16) 209 | (Byte.toUnsignedInt(buffer.get()) << 8) 210 | Byte.toUnsignedInt(buffer.get()); 211 } 212 213 /** EapTtlsAvpDecoder will be used for decoding {@link EapTtlsAvp} objects. */ 214 public static class EapTtlsAvpDecoder { 215 /** 216 * Decodes and returns an EapTtlsAvp for the specified EAP-TTLS AVP. 217 * 218 * <p>In the case that multiple AVPs are received, all AVPs will be decoded, but only the 219 * EAP-MESSAGE AVP will be stored. All AVP codes and Vendor-IDs will be logged. Furthermore, 220 * if multiple EAP-MESSAGE AVPs are received, this will be treated as an error. 221 * 222 * @param avp a byte array representing the AVP 223 * @return DecodeResult wrapping an EapTtlsAvp instance for the given EapTtlsAvp iff the 224 * eapTtlsAvp is formatted correctly. Otherwise, the DecodeResult wraps the appropriate 225 * EapError. 226 */ decode(byte[] avp)227 public AvpDecodeResult decode(byte[] avp) { 228 try { 229 // AVPs must be 4 byte aligned (RFC5281#10.2) 230 if (avp.length % AVP_BYTE_ALIGNMENT != 0) { 231 return new AvpDecodeResult( 232 new EapError( 233 new EapTtlsParsingException( 234 "Received one or more invalid AVPs: AVPs must be 4" 235 + " byte aligned."))); 236 } 237 ByteBuffer avpBuffer = ByteBuffer.wrap(avp); 238 EapTtlsAvp eapMessageAvp = null; 239 240 while (avpBuffer.hasRemaining()) { 241 EapTtlsAvp decodedAvp = new EapTtlsAvp(avpBuffer); 242 LOG.i( 243 TAG, 244 "Decoded AVP with code " 245 + decodedAvp.avpCode 246 + " and vendor ID " 247 + decodedAvp.vendorId); 248 249 if (decodedAvp.avpCode == EAP_MESSAGE_AVP_CODE) { 250 if (eapMessageAvp != null) { 251 // Only one EAP-MESSAGE AVP is expected at a time 252 return new AvpDecodeResult( 253 new EapError( 254 new EapTtlsParsingException( 255 "Received multiple EAP-MESSAGE AVPs in one" 256 + " message"))); 257 } 258 eapMessageAvp = decodedAvp; 259 } else if (decodedAvp.isMandatory) { 260 // As per RFC5281#10.1, if an AVP tagged as mandatory is unsupported, the 261 // negotiation should fail 262 return new AvpDecodeResult( 263 new EapError( 264 new EapTtlsParsingException( 265 "Received an AVP that requires support for AVP code" 266 + decodedAvp.avpCode))); 267 } 268 } 269 270 if (eapMessageAvp == null) { 271 return new AvpDecodeResult( 272 new EapError( 273 new EapTtlsParsingException( 274 "No EAP-MESSAGE (79) AVP was found"))); 275 } 276 277 return new AvpDecodeResult(eapMessageAvp); 278 } catch (BufferUnderflowException | EapTtlsParsingException e) { 279 return new AvpDecodeResult(new EapError(e)); 280 } 281 } 282 283 /** 284 * DecodeResult represents the result from attempting to decode a sequence of EAP-TTLS 285 * AVPs. It will contain either an EapTtlsAvp or an EapError. 286 * 287 * <p>In the case that multiple AVPs are received, all AVPs will be decoded and their AVP 288 * codes/Vendor-ID will be logged. However, only the EAP-MESSAGE AVP will be stored in the 289 * decode result. Furthermore, if zero, or multiple EAP-MESSAGE AVPs are received, this will 290 * be treated as an error. 291 */ 292 public static class AvpDecodeResult { 293 public final EapTtlsAvp eapTtlsAvp; 294 public final EapError eapError; 295 AvpDecodeResult(EapTtlsAvp eapTtlsAvp)296 public AvpDecodeResult(EapTtlsAvp eapTtlsAvp) { 297 this.eapTtlsAvp = eapTtlsAvp; 298 this.eapError = null; 299 } 300 AvpDecodeResult(EapError eapError)301 public AvpDecodeResult(EapError eapError) { 302 this.eapTtlsAvp = null; 303 this.eapError = eapError; 304 } 305 306 /** 307 * Checks whether this instance represents a successful decode operation. 308 * 309 * @return true iff this DecodeResult represents a successfully decoded Type Data 310 */ isSuccessfulDecode()311 public boolean isSuccessfulDecode() { 312 return eapTtlsAvp != null; 313 } 314 } 315 } 316 } 317