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 import com.android.internal.net.eap.message.EapMessage; 25 26 import java.nio.BufferUnderflowException; 27 import java.nio.ByteBuffer; 28 29 /** 30 * EapTtlsTypeData represents the type data for an {@link EapMessage} during an EAP-TTLS session. 31 * The structure of the flag byte is as follows: 32 * 33 * <pre> 34 * |---+---+---+---+---+-------+ 35 * | 0 | 1 | 2 | 3 | 4 | 5 6 7 | 36 * | L | M | S | R | R | V | 37 * |---+---+---+---+---+-------+ 38 * L = Message length is included 39 * M = More fragments incoming 40 * S = Start 41 * R = Reserved 42 * V = Version 43 * </pre> 44 * 45 * @see <a href="https://tools.ietf.org/html/rfc5281">RFC 5281, Extensible Authentication Protocol 46 * Tunneled Transport Layer Security Authenticated Protocol Version 0 (EAP-TTLSv0)</a> 47 */ 48 public class EapTtlsTypeData { 49 private static final String TAG = EapTtlsTypeData.class.getSimpleName(); 50 51 /* 52 * Used to extract bits from the flag byte as well as set them. Flag defined via: 53 * https://tools.ietf.org/html/rfc5281#section-9.1 54 * Note that unlike the flag diagram, the length included field is treated as the 55 * most significant bit 56 */ 57 private static final int FLAG_LENGTH_INCLUDED = 1 << 7; 58 private static final int FLAG_PACKET_FRAGMENTED = 1 << 6; 59 private static final int FLAG_START = 1 << 5; 60 // used to extract the lower 3 bits from the flag byte 61 private static final int FLAG_VERSION_MASK = 0x07; 62 63 private static final int FLAGS_LEN_BYTES = 1; 64 private static final int MESSAGE_LENGTH_LEN_BYTES = 4; 65 66 private static final int SUPPORTED_EAP_TTLS_VERSION = 0; 67 private static final int LEN_NOT_INCLUDED = 0; 68 69 public final boolean isLengthIncluded; 70 public final boolean isStart; 71 public final boolean isDataFragmented; 72 public final int version; 73 public final int messageLength; 74 public byte[] data; 75 76 // Package-private EapTtlsTypeData(ByteBuffer buffer)77 EapTtlsTypeData(ByteBuffer buffer) throws EapTtlsParsingException { 78 byte flags = buffer.get(); 79 isLengthIncluded = (flags & FLAG_LENGTH_INCLUDED) != 0; 80 isDataFragmented = (flags & FLAG_PACKET_FRAGMENTED) != 0; 81 isStart = (flags & FLAG_START) != 0; 82 version = (flags & FLAG_VERSION_MASK); 83 84 messageLength = isLengthIncluded ? buffer.getInt() : 0; 85 data = new byte[buffer.remaining()]; 86 buffer.get(data); 87 88 if (!isDataFragmented && isLengthIncluded && data.length != messageLength) { 89 throw new EapTtlsParsingException( 90 "Received an unfragmented packet with message length not equal to payload"); 91 } 92 } 93 EapTtlsTypeData( boolean isDataFragmented, boolean isStart, int version, int messageLength, byte[] data)94 private EapTtlsTypeData( 95 boolean isDataFragmented, boolean isStart, int version, int messageLength, byte[] data) 96 throws EapTtlsParsingException { 97 this.isLengthIncluded = messageLength != LEN_NOT_INCLUDED; 98 this.isDataFragmented = isDataFragmented; 99 this.isStart = isStart; 100 if (version != SUPPORTED_EAP_TTLS_VERSION) { 101 throw new EapTtlsParsingException("Unsupported version number: " + version); 102 } 103 this.version = version; 104 this.messageLength = messageLength; 105 this.data = data; 106 107 if (!isDataFragmented && isLengthIncluded && data.length != messageLength) { 108 throw new EapTtlsParsingException( 109 "Received an unfragmented packet with message length not equal to payload"); 110 } 111 } 112 /** 113 * Assembles each bit from the flag byte into a byte 114 * 115 * @return a byte that compromises the EAP-TTLS flags 116 */ getFlagByte()117 private byte getFlagByte() { 118 return (byte) 119 ((isLengthIncluded ? FLAG_LENGTH_INCLUDED : 0) 120 | (isDataFragmented ? FLAG_PACKET_FRAGMENTED : 0) 121 | (isStart ? FLAG_START : 0) 122 | (version)); 123 } 124 125 /** 126 * Determines if the type data represents an acknowledgment packet (RFC5281#9.2.3) 127 * 128 * @return true if it is an ack 129 */ isAcknowledgmentPacket()130 public boolean isAcknowledgmentPacket() { 131 return data.length == 0 && !isStart && !isLengthIncluded && !isDataFragmented; 132 } 133 134 /** 135 * Constructs and returns new EAP-TTLS response type data. 136 * 137 * @param packetFragmented a boolean that indicates whether this is a fragmented message 138 * @param start indicates if the start bit should be set 139 * @param version the EAP-TTLS version number 140 * @param messageLength an optional field to indicate the raw length of the data field prior to 141 * fragmentation 142 * @param data the raw tls message sequence 143 * @return an EapTtlsTypeData or null if the packet configuration is invalid 144 */ getEapTtlsTypeData( boolean packetFragmented, boolean start, int version, int messageLength, byte[] data)145 public static EapTtlsTypeData getEapTtlsTypeData( 146 boolean packetFragmented, boolean start, int version, int messageLength, byte[] data) { 147 try { 148 return new EapTtlsTypeData(packetFragmented, start, version, messageLength, data); 149 } catch (EapTtlsParsingException e) { 150 LOG.e(TAG, "Parsing exception thrown while attempting to create an EapTtlsTypeData"); 151 return null; 152 } 153 } 154 155 /** 156 * Encodes this EapTtlsTypeData instance as a byte[]. 157 * 158 * @return byte[] representing the encoded value of this EapTtlsTypeData instance 159 */ encode()160 public byte[] encode() { 161 int msgLen = isLengthIncluded ? MESSAGE_LENGTH_LEN_BYTES : 0; 162 int bufferSize = data.length + FLAGS_LEN_BYTES + msgLen; 163 ByteBuffer buffer = ByteBuffer.allocate(bufferSize); 164 buffer.put(getFlagByte()); 165 if (isLengthIncluded) { 166 buffer.putInt(messageLength); 167 } 168 buffer.put(data); 169 return buffer.array(); 170 } 171 172 /** EapTtlsAcknowledgement represents an EapTtls ack response (EAP-TTLS#9.2.3) */ 173 public static class EapTtlsAcknowledgement extends EapTtlsTypeData { 174 private static final String TAG = EapTtlsAcknowledgement.class.getSimpleName(); 175 176 @VisibleForTesting EapTtlsAcknowledgement()177 public EapTtlsAcknowledgement() throws EapTtlsParsingException { 178 super( 179 false /* no fragmentation */, 180 false /* not start */, 181 0 /* version */, 182 0 /* length */, 183 new byte[0] /* no data */); 184 } 185 186 /** 187 * Constructs and returns a new EAP-TTLS acknowledgement type data. 188 * 189 * @return a new EapTtlsAcknowledgement instance 190 */ getEapTtlsAcknowledgement()191 public static EapTtlsAcknowledgement getEapTtlsAcknowledgement() { 192 try { 193 return new EapTtlsAcknowledgement(); 194 } catch (EapTtlsParsingException e) { 195 // This should never happen 196 LOG.e( 197 TAG, 198 "Parsing exception thrown while attempting" 199 + "to create an acknowledgement packet"); 200 return null; 201 } 202 } 203 } 204 205 /** EapTtlsTypeDataDecoder will be used for decoding {@link EapTtlsTypeData} objects. */ 206 public static class EapTtlsTypeDataDecoder { 207 208 /** 209 * Decodes and returns an EapTtlsTypeData for the specified eapTypeData. 210 * 211 * @param eapTypeData byte[] to be decoded as an EapTtlsTypeData instance 212 * @return DecodeResult wrapping an EapTtlsTypeData instance for the given eapTypeData iff 213 * the eapTypeData is formatted correctly. Otherwise, the DecodeResult wraps the 214 * appropriate EapError. 215 */ decodeEapTtlsRequestPacket(byte[] eapTypeData)216 public DecodeResult decodeEapTtlsRequestPacket(byte[] eapTypeData) { 217 try { 218 ByteBuffer buffer = ByteBuffer.wrap(eapTypeData); 219 return new DecodeResult(new EapTtlsTypeData(buffer)); 220 } catch (BufferUnderflowException | EapTtlsParsingException e) { 221 return new DecodeResult(new EapError(e)); 222 } 223 } 224 225 /** 226 * DecodeResult represents the result from calling a decode method within 227 * EapTtlsTypeDataDecoder. It will contain either an EapTtlsTypeData or an EapError. 228 */ 229 public static class DecodeResult { 230 public final EapTtlsTypeData eapTypeData; 231 public final EapError eapError; 232 DecodeResult(EapTtlsTypeData eapTypeData)233 public DecodeResult(EapTtlsTypeData eapTypeData) { 234 this.eapTypeData = eapTypeData; 235 this.eapError = null; 236 } 237 DecodeResult(EapError eapError)238 public DecodeResult(EapError eapError) { 239 this.eapTypeData = null; 240 this.eapError = eapError; 241 } 242 243 /** 244 * Checks whether this instance represents a successful decode operation. 245 * 246 * @return true iff this DecodeResult represents a successfully decoded Type Data 247 */ isSuccessfulDecode()248 public boolean isSuccessfulDecode() { 249 return eapTypeData != null; 250 } 251 } 252 } 253 } 254