• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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