• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.simaka;
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.exceptions.simaka.EapSimAkaInvalidAtPaddingException;
23 import com.android.internal.net.eap.exceptions.simaka.EapSimAkaInvalidAttributeException;
24 import com.android.internal.net.eap.exceptions.simaka.EapSimInvalidAtRandException;
25 
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 
34 /**
35  * EapSimAkaAttribute represents a single EAP SIM/AKA Attribute.
36  *
37  * @see <a href="https://tools.ietf.org/html/rfc4186">RFC 4186, Extensible Authentication
38  * Protocol for Subscriber Identity Modules (EAP-SIM)</a>
39  * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication
40  * Protocol for Authentication and Key Agreement (EAP-AKA)</a>
41  * @see <a href="https://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml">EAP SIM/AKA
42  * Attributes</a>
43  */
44 public abstract class EapSimAkaAttribute {
45     static final int LENGTH_SCALING = 4;
46 
47     private static final int MIN_ATTR_LENGTH = 4;
48 
49     public static final int SKIPPABLE_ATTRIBUTE_RANGE_START = 128;
50 
51     // EAP non-Skippable Attribute values defined by IANA
52     // https://www.iana.org/assignments/eapsimaka-numbers/eapsimaka-numbers.xhtml
53     public static final int EAP_AT_RAND = 1;
54     public static final int EAP_AT_AUTN = 2;
55     public static final int EAP_AT_RES = 3;
56     public static final int EAP_AT_AUTS = 4;
57     public static final int EAP_AT_PADDING = 6;
58     public static final int EAP_AT_NONCE_MT = 7;
59     public static final int EAP_AT_PERMANENT_ID_REQ = 10;
60     public static final int EAP_AT_MAC = 11;
61     public static final int EAP_AT_NOTIFICATION = 12;
62     public static final int EAP_AT_ANY_ID_REQ = 13;
63     public static final int EAP_AT_IDENTITY = 14;
64     public static final int EAP_AT_VERSION_LIST = 15;
65     public static final int EAP_AT_SELECTED_VERSION = 16;
66     public static final int EAP_AT_FULLAUTH_ID_REQ = 17;
67     public static final int EAP_AT_COUNTER = 19;
68     public static final int EAP_AT_COUNTER_TOO_SMALL = 20;
69     public static final int EAP_AT_NONCE_S = 21;
70     public static final int EAP_AT_CLIENT_ERROR_CODE = 22;
71     public static final int EAP_AT_KDF_INPUT = 23;
72     public static final int EAP_AT_KDF = 24;
73 
74     // EAP Skippable Attribute values defined by IANA
75     // https://www.iana.org/assignments/eapsimaka-numbers/eapsimaka-numbers.xhtml
76     public static final int EAP_AT_IV = 129;
77     public static final int EAP_AT_ENCR_DATA = 130;
78     public static final int EAP_AT_NEXT_PSEUDONYM = 132;
79     public static final int EAP_AT_NEXT_REAUTH_ID = 133;
80     public static final int EAP_AT_CHECKCODE = 134;
81     public static final int EAP_AT_RESULT_IND = 135;
82     public static final int EAP_AT_BIDDING = 136;
83 
84     public static final Map<Integer, String> EAP_ATTRIBUTE_STRING = new HashMap<>();
85     static {
EAP_ATTRIBUTE_STRING.put(EAP_AT_RAND, "AT_RAND")86         EAP_ATTRIBUTE_STRING.put(EAP_AT_RAND, "AT_RAND");
EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTN, "AT_AUTN")87         EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTN, "AT_AUTN");
EAP_ATTRIBUTE_STRING.put(EAP_AT_RES, "AT_RES")88         EAP_ATTRIBUTE_STRING.put(EAP_AT_RES, "AT_RES");
EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTS, "AT_AUTS")89         EAP_ATTRIBUTE_STRING.put(EAP_AT_AUTS, "AT_AUTS");
EAP_ATTRIBUTE_STRING.put(EAP_AT_PADDING, "AT_PADDING")90         EAP_ATTRIBUTE_STRING.put(EAP_AT_PADDING, "AT_PADDING");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_MT, "AT_NONCE_MT")91         EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_MT, "AT_NONCE_MT");
EAP_ATTRIBUTE_STRING.put(EAP_AT_PERMANENT_ID_REQ, "AT_PERMANENT_ID_REQ")92         EAP_ATTRIBUTE_STRING.put(EAP_AT_PERMANENT_ID_REQ, "AT_PERMANENT_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_MAC, "AT_MAC")93         EAP_ATTRIBUTE_STRING.put(EAP_AT_MAC, "AT_MAC");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NOTIFICATION, "AT_NOTIFICATION")94         EAP_ATTRIBUTE_STRING.put(EAP_AT_NOTIFICATION, "AT_NOTIFICATION");
EAP_ATTRIBUTE_STRING.put(EAP_AT_ANY_ID_REQ, "AT_ANY_ID_REQ")95         EAP_ATTRIBUTE_STRING.put(EAP_AT_ANY_ID_REQ, "AT_ANY_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_IDENTITY, "AT_IDENTITY")96         EAP_ATTRIBUTE_STRING.put(EAP_AT_IDENTITY, "AT_IDENTITY");
EAP_ATTRIBUTE_STRING.put(EAP_AT_VERSION_LIST, "AT_VERSION_LIST")97         EAP_ATTRIBUTE_STRING.put(EAP_AT_VERSION_LIST, "AT_VERSION_LIST");
EAP_ATTRIBUTE_STRING.put(EAP_AT_SELECTED_VERSION, "AT_SELECTED_VERSION")98         EAP_ATTRIBUTE_STRING.put(EAP_AT_SELECTED_VERSION, "AT_SELECTED_VERSION");
EAP_ATTRIBUTE_STRING.put(EAP_AT_FULLAUTH_ID_REQ, "AT_FULLAUTH_ID_REQ")99         EAP_ATTRIBUTE_STRING.put(EAP_AT_FULLAUTH_ID_REQ, "AT_FULLAUTH_ID_REQ");
EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER, "AT_COUNTER")100         EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER, "AT_COUNTER");
EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER_TOO_SMALL, "AT_COUNTER_TOO_SMALL")101         EAP_ATTRIBUTE_STRING.put(EAP_AT_COUNTER_TOO_SMALL, "AT_COUNTER_TOO_SMALL");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_S, "AT_NONCE_S")102         EAP_ATTRIBUTE_STRING.put(EAP_AT_NONCE_S, "AT_NONCE_S");
EAP_ATTRIBUTE_STRING.put(EAP_AT_CLIENT_ERROR_CODE, "AT_CLIENT_ERROR_CODE")103         EAP_ATTRIBUTE_STRING.put(EAP_AT_CLIENT_ERROR_CODE, "AT_CLIENT_ERROR_CODE");
EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF_INPUT, "AT_KDF_INPUT")104         EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF_INPUT, "AT_KDF_INPUT");
EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF, "AT_KDF")105         EAP_ATTRIBUTE_STRING.put(EAP_AT_KDF, "AT_KDF");
106 
EAP_ATTRIBUTE_STRING.put(EAP_AT_IV, "AT_IV")107         EAP_ATTRIBUTE_STRING.put(EAP_AT_IV, "AT_IV");
EAP_ATTRIBUTE_STRING.put(EAP_AT_ENCR_DATA, "AT_ENCR_DATA")108         EAP_ATTRIBUTE_STRING.put(EAP_AT_ENCR_DATA, "AT_ENCR_DATA");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_PSEUDONYM, "AT_NEXT_PSEUDONYM")109         EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_PSEUDONYM, "AT_NEXT_PSEUDONYM");
EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_REAUTH_ID, "AT_NEXT_REAUTH_ID")110         EAP_ATTRIBUTE_STRING.put(EAP_AT_NEXT_REAUTH_ID, "AT_NEXT_REAUTH_ID");
EAP_ATTRIBUTE_STRING.put(EAP_AT_CHECKCODE, "AT_CHECKCODE")111         EAP_ATTRIBUTE_STRING.put(EAP_AT_CHECKCODE, "AT_CHECKCODE");
EAP_ATTRIBUTE_STRING.put(EAP_AT_RESULT_IND, "AT_RESULT_IND")112         EAP_ATTRIBUTE_STRING.put(EAP_AT_RESULT_IND, "AT_RESULT_IND");
EAP_ATTRIBUTE_STRING.put(EAP_AT_BIDDING, "AT_BIDDING")113         EAP_ATTRIBUTE_STRING.put(EAP_AT_BIDDING, "AT_BIDDING");
114     }
115 
116     public final int attributeType;
117     public final int lengthInBytes;
118 
EapSimAkaAttribute(int attributeType, int lengthInBytes)119     protected EapSimAkaAttribute(int attributeType, int lengthInBytes)
120             throws EapSimAkaInvalidAttributeException {
121         this.attributeType = attributeType;
122         this.lengthInBytes = lengthInBytes;
123 
124         if (lengthInBytes % LENGTH_SCALING != 0) {
125             throw new EapSimAkaInvalidAttributeException("Attribute length must be multiple of 4");
126         }
127     }
128 
129     /**
130      * Encodes this EapSimAkaAttribute into the given ByteBuffer
131      *
132      * @param byteBuffer the ByteBuffer that this instance will be written to
133      */
encode(ByteBuffer byteBuffer)134     public abstract void encode(ByteBuffer byteBuffer);
135 
136     /**
137      * EapSimAkaReservedBytesAttribute represents any EAP-SIM/AKA attribute that is of the format:
138      *
139      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
140      * |  Attribute Type (1B)  |  Length (1B)  |  Reserved (2B)  |
141      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
142      * |  Value...
143      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
144      *
145      * <p>Note: This Attribute type ignores (but preserves) the Reserved bytes. This is needed for
146      * calculating MACs in EAP-SIM/AKA.
147      */
148     protected abstract static class EapSimAkaReservedBytesAttribute extends EapSimAkaAttribute {
149         protected static final int RESERVED_BYTES_LEN = 2;
150 
151         @VisibleForTesting public final byte[] reservedBytes = new byte[RESERVED_BYTES_LEN];
152 
EapSimAkaReservedBytesAttribute( int attributeType, int lengthInBytes, ByteBuffer buffer)153         protected EapSimAkaReservedBytesAttribute(
154                 int attributeType, int lengthInBytes, ByteBuffer buffer)
155                 throws EapSimAkaInvalidAttributeException {
156             super(attributeType, lengthInBytes);
157 
158             try {
159                 buffer.get(reservedBytes);
160             } catch (BufferUnderflowException e) {
161                 throw new EapSimAkaInvalidAttributeException("Invalid attribute length", e);
162             }
163         }
164 
EapSimAkaReservedBytesAttribute(int attributeType, int lengthInBytes)165         protected EapSimAkaReservedBytesAttribute(int attributeType, int lengthInBytes)
166                 throws EapSimAkaInvalidAttributeException {
167             super(attributeType, lengthInBytes);
168         }
169 
EapSimAkaReservedBytesAttribute( int attributeType, int lengthInBytes, byte[] reservedBytes)170         protected EapSimAkaReservedBytesAttribute(
171                 int attributeType, int lengthInBytes, byte[] reservedBytes)
172                 throws EapSimAkaInvalidAttributeException {
173             this(attributeType, lengthInBytes);
174 
175             if (reservedBytes.length != RESERVED_BYTES_LEN) {
176                 throw new EapSimAkaInvalidAttributeException("Invalid attribute length");
177             }
178             System.arraycopy(
179                     reservedBytes,
180                     0 /* srcPos */,
181                     this.reservedBytes,
182                     0 /* destPos */,
183                     RESERVED_BYTES_LEN);
184         }
185 
186         @Override
encode(ByteBuffer buffer)187         public void encode(ByteBuffer buffer) {
188             encodeAttributeHeader(buffer);
189 
190             buffer.put(reservedBytes);
191         }
192     }
193 
encodeAttributeHeader(ByteBuffer byteBuffer)194     protected void encodeAttributeHeader(ByteBuffer byteBuffer) {
195         byteBuffer.put((byte) attributeType);
196         byteBuffer.put((byte) (lengthInBytes / LENGTH_SCALING));
197     }
198 
consumePadding(int bytesUsed, ByteBuffer byteBuffer)199     void consumePadding(int bytesUsed, ByteBuffer byteBuffer) {
200         int paddingRemaining = lengthInBytes - bytesUsed;
201         byteBuffer.get(new byte[paddingRemaining]);
202     }
203 
addPadding(int bytesUsed, ByteBuffer byteBuffer)204     void addPadding(int bytesUsed, ByteBuffer byteBuffer) {
205         int paddingNeeded = lengthInBytes - bytesUsed;
206         byteBuffer.put(new byte[paddingNeeded]);
207     }
208 
209     /**
210      * EapSimAkaUnsupportedAttribute represents any unsupported, skippable EAP-SIM attribute.
211      */
212     public static class EapSimAkaUnsupportedAttribute extends EapSimAkaAttribute {
213         // Attribute Type (1B) + Attribute Length (1B) = 2B Header
214         private static final int HEADER_BYTES = 2;
215 
216         public final byte[] data;
217 
EapSimAkaUnsupportedAttribute( int attributeType, int lengthInBytes, ByteBuffer byteBuffer)218         public EapSimAkaUnsupportedAttribute(
219                 int attributeType,
220                 int lengthInBytes,
221                 ByteBuffer byteBuffer) throws EapSimAkaInvalidAttributeException {
222             super(attributeType, lengthInBytes);
223 
224             // Attribute not supported, but remaining attribute still needs to be saved
225             int remainingBytes = lengthInBytes - HEADER_BYTES;
226             data = new byte[remainingBytes];
227             byteBuffer.get(data);
228         }
229 
230         @VisibleForTesting
EapSimAkaUnsupportedAttribute(int attributeType, int lengthInBytes, byte[] data)231         public EapSimAkaUnsupportedAttribute(int attributeType, int lengthInBytes, byte[] data)
232                 throws EapSimAkaInvalidAttributeException {
233             super(attributeType, lengthInBytes);
234             this.data = data;
235         }
236 
237         @Override
encode(ByteBuffer byteBuffer)238         public void encode(ByteBuffer byteBuffer) {
239             encodeAttributeHeader(byteBuffer);
240             byteBuffer.put(data);
241         }
242     }
243 
244     /**
245      * AtVersionList represents the AT_VERSION_LIST attribute defined in RFC 4186#10.2
246      */
247     public static class AtVersionList extends EapSimAkaAttribute {
248         private static final int BYTES_PER_VERSION = 2;
249 
250         public final List<Integer> versions = new ArrayList<>();
251 
AtVersionList(int lengthInBytes, ByteBuffer byteBuffer)252         public AtVersionList(int lengthInBytes, ByteBuffer byteBuffer)
253                 throws EapSimAkaInvalidAttributeException {
254             super(EAP_AT_VERSION_LIST, lengthInBytes);
255 
256             // number of bytes used to represent list (RFC 4186 Section 10.2)
257             int bytesInList = Short.toUnsignedInt(byteBuffer.getShort());
258             if (bytesInList % BYTES_PER_VERSION != 0) {
259                 throw new EapSimAkaInvalidAttributeException(
260                         "Actual Version List Length must be multiple of 2");
261             }
262 
263             int numVersions =  bytesInList / BYTES_PER_VERSION;
264             for (int i = 0; i < numVersions; i++) {
265                 versions.add(Short.toUnsignedInt(byteBuffer.getShort()));
266             }
267 
268             int bytesUsed = MIN_ATTR_LENGTH + (BYTES_PER_VERSION * versions.size());
269             consumePadding(bytesUsed, byteBuffer);
270         }
271 
272         @VisibleForTesting
AtVersionList(int lengthInBytes, int... versions)273         public AtVersionList(int lengthInBytes, int... versions)
274                 throws EapSimAkaInvalidAttributeException {
275             super(EAP_AT_VERSION_LIST, lengthInBytes);
276             for (int version : versions) {
277                 this.versions.add(version);
278             }
279         }
280 
281         @Override
encode(ByteBuffer byteBuffer)282         public void encode(ByteBuffer byteBuffer) {
283             encodeAttributeHeader(byteBuffer);
284 
285             byteBuffer.putShort((short) (versions.size() * BYTES_PER_VERSION));
286             for (int i : versions) {
287                 byteBuffer.putShort((short) i);
288             }
289 
290             int bytesUsed = MIN_ATTR_LENGTH + (BYTES_PER_VERSION * versions.size());
291             addPadding(bytesUsed, byteBuffer);
292         }
293     }
294 
295     /**
296      * AtSelectedVersion represents the AT_SELECTED_VERSION attribute defined in RFC 4186#10.3
297      */
298     public static class AtSelectedVersion extends EapSimAkaAttribute {
299         private static final String TAG = AtSelectedVersion.class.getSimpleName();
300         private static final int LENGTH = LENGTH_SCALING;
301 
302         public static final int SUPPORTED_VERSION = 1;
303 
304         public final int selectedVersion;
305 
AtSelectedVersion(int lengthInBytes, int selectedVersion)306         public AtSelectedVersion(int lengthInBytes, int selectedVersion)
307                 throws EapSimAkaInvalidAttributeException {
308             super(EAP_AT_SELECTED_VERSION, LENGTH);
309             this.selectedVersion = selectedVersion;
310 
311             if (lengthInBytes != LENGTH) {
312                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
313             }
314         }
315 
316         @VisibleForTesting
AtSelectedVersion(int selectedVersion)317         public AtSelectedVersion(int selectedVersion) throws EapSimAkaInvalidAttributeException {
318             super(EAP_AT_SELECTED_VERSION, LENGTH);
319             this.selectedVersion = selectedVersion;
320         }
321 
322         @Override
encode(ByteBuffer byteBuffer)323         public void encode(ByteBuffer byteBuffer) {
324             encodeAttributeHeader(byteBuffer);
325             byteBuffer.putShort((short) selectedVersion);
326         }
327 
328         /**
329          * Constructs and returns an AtSelectedVersion for the only supported version of EAP-SIM
330          *
331          * @return an AtSelectedVersion for the supported version (1) of EAP-SIM
332          */
getSelectedVersion()333         public static AtSelectedVersion getSelectedVersion() {
334             try {
335                 return new AtSelectedVersion(LENGTH, SUPPORTED_VERSION);
336             } catch (EapSimAkaInvalidAttributeException ex) {
337                 // this should never happen
338                 LOG.wtf(TAG,
339                         "Error thrown while creating AtSelectedVersion with correct length", ex);
340                 throw new AssertionError("Impossible exception encountered", ex);
341             }
342         }
343     }
344 
345     /**
346      * AtNonceMt represents the AT_NONCE_MT attribute defined in RFC 4186#10.4
347      */
348     public static class AtNonceMt extends EapSimAkaReservedBytesAttribute {
349         private static final int LENGTH = 5 * LENGTH_SCALING;
350 
351         public static final int NONCE_MT_LENGTH = 16;
352 
353         public final byte[] nonceMt = new byte[NONCE_MT_LENGTH];
354 
AtNonceMt(int lengthInBytes, ByteBuffer byteBuffer)355         public AtNonceMt(int lengthInBytes, ByteBuffer byteBuffer)
356                 throws EapSimAkaInvalidAttributeException {
357             super(EAP_AT_NONCE_MT, LENGTH, byteBuffer);
358             if (lengthInBytes != LENGTH) {
359                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
360             }
361 
362             byteBuffer.get(nonceMt);
363         }
364 
365         @VisibleForTesting
AtNonceMt(byte[] nonceMt)366         public AtNonceMt(byte[] nonceMt) throws EapSimAkaInvalidAttributeException {
367             super(EAP_AT_NONCE_MT, LENGTH);
368 
369             if (nonceMt.length != NONCE_MT_LENGTH) {
370                 throw new EapSimAkaInvalidAttributeException("NonceMt length must be 16B");
371             }
372             System.arraycopy(nonceMt, 0, this.nonceMt, 0, NONCE_MT_LENGTH);
373         }
374 
375         @Override
encode(ByteBuffer byteBuffer)376         public void encode(ByteBuffer byteBuffer) {
377             super.encode(byteBuffer);
378 
379             byteBuffer.put(nonceMt);
380         }
381     }
382 
383     private abstract static class AtIdReq extends EapSimAkaReservedBytesAttribute {
384         private static final int ATTR_LENGTH = LENGTH_SCALING;
385 
AtIdReq(int lengthInBytes, int attributeType, ByteBuffer byteBuffer)386         protected AtIdReq(int lengthInBytes, int attributeType, ByteBuffer byteBuffer)
387                 throws EapSimAkaInvalidAttributeException {
388             super(attributeType, ATTR_LENGTH, byteBuffer);
389 
390             if (lengthInBytes != ATTR_LENGTH) {
391                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
392             }
393         }
394 
395         @VisibleForTesting
AtIdReq(int attributeType)396         protected AtIdReq(int attributeType) throws EapSimAkaInvalidAttributeException {
397             super(attributeType, ATTR_LENGTH);
398         }
399     }
400 
401     /**
402      * AtPermanentIdReq represents the AT_PERMANENT_ID_REQ attribute defined in RFC 4186#10.5 and
403      * RFC 4187#10.2
404      */
405     public static class AtPermanentIdReq extends AtIdReq {
AtPermanentIdReq(int lengthInBytes, ByteBuffer byteBuffer)406         public AtPermanentIdReq(int lengthInBytes, ByteBuffer byteBuffer)
407                 throws EapSimAkaInvalidAttributeException {
408             super(lengthInBytes, EAP_AT_PERMANENT_ID_REQ, byteBuffer);
409         }
410 
411         @VisibleForTesting
AtPermanentIdReq()412         public AtPermanentIdReq() throws EapSimAkaInvalidAttributeException {
413             super(EAP_AT_PERMANENT_ID_REQ);
414         }
415     }
416 
417     /**
418      * AtAnyIdReq represents the AT_ANY_ID_REQ attribute defined in RFC 4186#10.6 and RFC 4187#10.3
419      */
420     public static class AtAnyIdReq extends AtIdReq {
AtAnyIdReq(int lengthInBytes, ByteBuffer byteBuffer)421         public AtAnyIdReq(int lengthInBytes, ByteBuffer byteBuffer)
422                 throws EapSimAkaInvalidAttributeException {
423             super(lengthInBytes, EAP_AT_ANY_ID_REQ, byteBuffer);
424         }
425 
426         @VisibleForTesting
AtAnyIdReq()427         public AtAnyIdReq() throws EapSimAkaInvalidAttributeException {
428             super(EAP_AT_ANY_ID_REQ);
429         }
430     }
431 
432     /**
433      * AtFullauthIdReq represents the AT_FULLAUTH_ID_REQ attribute defined in RFC 4186#10.7 and RFC
434      * 4187#10.4
435      */
436     public static class AtFullauthIdReq extends AtIdReq {
AtFullauthIdReq(int lengthInBytes, ByteBuffer byteBuffer)437         public AtFullauthIdReq(int lengthInBytes, ByteBuffer byteBuffer)
438                 throws EapSimAkaInvalidAttributeException {
439             super(lengthInBytes, EAP_AT_FULLAUTH_ID_REQ, byteBuffer);
440         }
441 
442         @VisibleForTesting
AtFullauthIdReq()443         public AtFullauthIdReq() throws EapSimAkaInvalidAttributeException {
444             super(EAP_AT_FULLAUTH_ID_REQ);
445         }
446     }
447 
448     /**
449      * AtIdentity represents the AT_IDENTITY attribute defined in RFC 4186#10.8 and RFC 4187#10.5
450      */
451     public static class AtIdentity extends EapSimAkaAttribute {
452         public final byte[] identity;
453 
AtIdentity(int lengthInBytes, ByteBuffer byteBuffer)454         public AtIdentity(int lengthInBytes, ByteBuffer byteBuffer)
455                 throws EapSimAkaInvalidAttributeException {
456             super(EAP_AT_IDENTITY, lengthInBytes);
457 
458             int identityLength = Short.toUnsignedInt(byteBuffer.getShort());
459             identity = new byte[identityLength];
460             byteBuffer.get(identity);
461 
462             int bytesUsed = MIN_ATTR_LENGTH + identityLength;
463             consumePadding(bytesUsed, byteBuffer);
464         }
465 
466         @VisibleForTesting
AtIdentity(int lengthInBytes, byte[] identity)467         public AtIdentity(int lengthInBytes, byte[] identity)
468                 throws EapSimAkaInvalidAttributeException {
469             super(EAP_AT_IDENTITY, lengthInBytes);
470             this.identity = identity;
471         }
472 
473         @Override
encode(ByteBuffer byteBuffer)474         public void encode(ByteBuffer byteBuffer) {
475             encodeAttributeHeader(byteBuffer);
476             byteBuffer.putShort((short) identity.length);
477             byteBuffer.put(identity);
478 
479             int bytesUsed = MIN_ATTR_LENGTH + identity.length;
480             addPadding(bytesUsed, byteBuffer);
481         }
482 
483         /**
484          * Creates and returns an AtIdentity instance for the given identity.
485          *
486          * @param identity byte-array representing the identity for the AtIdentity
487          * @return AtIdentity instance for the given identity byte-array
488          */
getAtIdentity(byte[] identity)489         public static AtIdentity getAtIdentity(byte[] identity)
490                 throws EapSimAkaInvalidAttributeException {
491             int lengthInBytes = MIN_ATTR_LENGTH + identity.length;
492             if (lengthInBytes % LENGTH_SCALING != 0) {
493                 lengthInBytes += LENGTH_SCALING - (lengthInBytes % LENGTH_SCALING);
494             }
495 
496             return new AtIdentity(lengthInBytes, identity);
497         }
498     }
499 
500     /**
501      * AtRandSim represents the AT_RAND attribute for EAP-SIM defined in RFC 4186#10.9
502      */
503     public static class AtRandSim extends EapSimAkaReservedBytesAttribute {
504         private static final int RAND_LENGTH = 16;
505         private static final int MIN_RANDS = 2;
506         private static final int MAX_RANDS = 3;
507 
508         public final List<byte[]> rands = new ArrayList<>(MAX_RANDS);
509 
AtRandSim(int lengthInBytes, ByteBuffer byteBuffer)510         public AtRandSim(int lengthInBytes, ByteBuffer byteBuffer)
511                 throws EapSimAkaInvalidAttributeException {
512             super(EAP_AT_RAND, lengthInBytes, byteBuffer);
513 
514             int numRands = (lengthInBytes - MIN_ATTR_LENGTH) / RAND_LENGTH;
515             if (!isValidNumRands(numRands)) {
516                 throw new EapSimInvalidAtRandException("Unexpected number of rands: " + numRands);
517             }
518 
519             for (int i = 0; i < numRands; i++) {
520                 byte[] rand = new byte[RAND_LENGTH];
521                 byteBuffer.get(rand);
522 
523                 // check for rand being unique (RFC 4186 Section 10.9)
524                 for (int j = 0; j < i; j++) {
525                     byte[] otherRand = rands.get(j);
526                     if (Arrays.equals(rand, otherRand)) {
527                         throw new EapSimAkaInvalidAttributeException("Received identical RANDs");
528                     }
529                 }
530                 rands.add(rand);
531             }
532         }
533 
534         @VisibleForTesting
AtRandSim(int lengthInBytes, byte[]... rands)535         public AtRandSim(int lengthInBytes, byte[]... rands)
536                 throws EapSimAkaInvalidAttributeException {
537             super(EAP_AT_RAND, lengthInBytes);
538 
539             if (!isValidNumRands(rands.length)) {
540                 throw new EapSimInvalidAtRandException("Unexpected number of rands: "
541                         + rands.length);
542             }
543             for (byte[] rand : rands) {
544                 this.rands.add(rand);
545             }
546         }
547 
isValidNumRands(int numRands)548         private boolean isValidNumRands(int numRands) {
549             // numRands is valid iff 2 <= numRands <= 3
550             return MIN_RANDS <= numRands && numRands <= MAX_RANDS;
551         }
552 
553         @Override
encode(ByteBuffer byteBuffer)554         public void encode(ByteBuffer byteBuffer) {
555             super.encode(byteBuffer);
556 
557             for (byte[] rand : rands) {
558                 byteBuffer.put(rand);
559             }
560         }
561     }
562 
563     /**
564      * AtRandAka represents the AT_RAND attribute for EAP-AKA defined in RFC 4187#10.6
565      */
566     public static class AtRandAka extends EapSimAkaReservedBytesAttribute {
567         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
568         private static final int RAND_LENGTH = 16;
569 
570         public final byte[] rand = new byte[RAND_LENGTH];
571 
AtRandAka(int lengthInBytes, ByteBuffer byteBuffer)572         public AtRandAka(int lengthInBytes, ByteBuffer byteBuffer)
573                 throws EapSimAkaInvalidAttributeException {
574             super(EAP_AT_RAND, lengthInBytes, byteBuffer);
575 
576             if (lengthInBytes != ATTR_LENGTH) {
577                 throw new EapSimAkaInvalidAttributeException("Length must be 20B");
578             }
579 
580             byteBuffer.get(rand);
581         }
582 
583         @VisibleForTesting
AtRandAka(byte[] rand)584         public AtRandAka(byte[] rand)
585                 throws EapSimAkaInvalidAttributeException {
586             super(EAP_AT_RAND, ATTR_LENGTH);
587 
588             if (rand.length != RAND_LENGTH) {
589                 throw new EapSimAkaInvalidAttributeException("Rand must be 16B");
590             }
591 
592             System.arraycopy(rand, 0, this.rand, 0, RAND_LENGTH);
593         }
594 
595         @Override
encode(ByteBuffer byteBuffer)596         public void encode(ByteBuffer byteBuffer) {
597             super.encode(byteBuffer);
598 
599             byteBuffer.put(rand);
600         }
601     }
602 
603     /**
604      * AtPadding represents the AT_PADDING attribute defined in RFC 4186#10.12 and RFC 4187#10.12
605      */
606     public static class AtPadding extends EapSimAkaAttribute {
607         private static final int ATTR_HEADER = 2;
608 
AtPadding(int lengthInBytes, ByteBuffer byteBuffer)609         public AtPadding(int lengthInBytes, ByteBuffer byteBuffer)
610                 throws EapSimAkaInvalidAttributeException {
611             super(EAP_AT_PADDING, lengthInBytes);
612 
613             int remainingBytes = lengthInBytes - ATTR_HEADER;
614             for (int i = 0; i < remainingBytes; i++) {
615                 // Padding must be checked to all be 0x00 bytes (RFC 4186 Section 10.12)
616                 if (byteBuffer.get() != 0) {
617                     throw new EapSimAkaInvalidAtPaddingException("Padding bytes must all be 0x00");
618                 }
619             }
620         }
621 
622         @VisibleForTesting
AtPadding(int lengthInBytes)623         public AtPadding(int lengthInBytes) throws EapSimAkaInvalidAttributeException {
624             super(EAP_AT_PADDING, lengthInBytes);
625         }
626 
627         @Override
encode(ByteBuffer byteBuffer)628         public void encode(ByteBuffer byteBuffer) {
629             encodeAttributeHeader(byteBuffer);
630 
631             addPadding(ATTR_HEADER, byteBuffer);
632         }
633     }
634 
635     /**
636      * AtMac represents the AT_MAC attribute defined in RFC 4186#10.14 and RFC 4187#10.15
637      */
638     public static class AtMac extends EapSimAkaReservedBytesAttribute {
639         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
640 
641         public static final int MAC_LENGTH = 4 * LENGTH_SCALING;
642 
643         public final byte[] mac;
644 
AtMac(int lengthInBytes, ByteBuffer byteBuffer)645         public AtMac(int lengthInBytes, ByteBuffer byteBuffer)
646                 throws EapSimAkaInvalidAttributeException {
647             super(EAP_AT_MAC, lengthInBytes, byteBuffer);
648 
649             if (lengthInBytes != ATTR_LENGTH) {
650                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
651             }
652 
653             mac = new byte[MAC_LENGTH];
654             byteBuffer.get(mac);
655         }
656 
657         // Constructs an AtMac with an empty MAC and empty RESERVED bytes. Should only be used for
658         // calculating MACs in outbound messages.
AtMac()659         public AtMac() throws EapSimAkaInvalidAttributeException {
660             this(new byte[MAC_LENGTH]);
661         }
662 
AtMac(byte[] mac)663         public AtMac(byte[] mac) throws EapSimAkaInvalidAttributeException {
664             this(new byte[RESERVED_BYTES_LEN], mac);
665         }
666 
667         @VisibleForTesting
AtMac(byte[] reservedBytes, byte[] mac)668         public AtMac(byte[] reservedBytes, byte[] mac) throws EapSimAkaInvalidAttributeException {
669             super(EAP_AT_MAC, ATTR_LENGTH, reservedBytes);
670 
671             if (mac.length != MAC_LENGTH) {
672                 throw new EapSimAkaInvalidAttributeException("Invalid length for MAC");
673             }
674             this.mac = mac;
675         }
676 
677         @Override
encode(ByteBuffer byteBuffer)678         public void encode(ByteBuffer byteBuffer) {
679             super.encode(byteBuffer);
680 
681             byteBuffer.put(mac);
682         }
683 
684         /**
685          * Returns a copy of this AtMac with the MAC cleared (and the reserved bytes preserved).
686          *
687          * <p>Per RFC 4186 Section 10.14, the MAC should be calculated over the entire packet, with
688          * the value field of the MAC attribute set to zero.
689          */
getAtMacWithMacCleared()690         public AtMac getAtMacWithMacCleared() throws EapSimAkaInvalidAttributeException {
691             return new AtMac(reservedBytes, new byte[MAC_LENGTH]);
692         }
693     }
694 
695     /**
696      * AtCounter represents the AT_COUNTER attribute defined in RFC 4186#10.15 and RFC 4187#10.16
697      */
698     public static class AtCounter extends EapSimAkaAttribute {
699         private static final int ATTR_LENGTH = LENGTH_SCALING;
700 
701         public final int counter;
702 
AtCounter(int lengthInBytes, ByteBuffer byteBuffer)703         public AtCounter(int lengthInBytes, ByteBuffer byteBuffer)
704                 throws EapSimAkaInvalidAttributeException {
705             super(EAP_AT_COUNTER, lengthInBytes);
706 
707             if (lengthInBytes != ATTR_LENGTH) {
708                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
709             }
710 
711             this.counter = Short.toUnsignedInt(byteBuffer.getShort());
712         }
713 
714         @VisibleForTesting
AtCounter(int counter)715         public AtCounter(int counter) throws EapSimAkaInvalidAttributeException {
716             super(EAP_AT_COUNTER, ATTR_LENGTH);
717             this.counter = counter;
718         }
719 
720         @Override
encode(ByteBuffer byteBuffer)721         public void encode(ByteBuffer byteBuffer) {
722             encodeAttributeHeader(byteBuffer);
723             byteBuffer.putShort((short) counter);
724         }
725     }
726 
727 
728     /**
729      * AtCounterTooSmall represents the AT_COUNTER_TOO_SMALL attribute defined in RFC 4186#10.16 and
730      * RFC 4187#10.17
731      */
732     public static class AtCounterTooSmall extends EapSimAkaAttribute {
733         private static final int ATTR_LENGTH = LENGTH_SCALING;
734         private static final int ATTR_HEADER = 2;
735 
AtCounterTooSmall(int lengthInBytes, ByteBuffer byteBuffer)736         public AtCounterTooSmall(int lengthInBytes, ByteBuffer byteBuffer)
737                 throws EapSimAkaInvalidAttributeException {
738             super(EAP_AT_COUNTER_TOO_SMALL, lengthInBytes);
739 
740             if (lengthInBytes != ATTR_LENGTH) {
741                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
742             }
743             consumePadding(ATTR_HEADER, byteBuffer);
744         }
745 
AtCounterTooSmall()746         public AtCounterTooSmall() throws EapSimAkaInvalidAttributeException {
747             super(EAP_AT_COUNTER_TOO_SMALL, ATTR_LENGTH);
748         }
749 
750         @Override
encode(ByteBuffer byteBuffer)751         public void encode(ByteBuffer byteBuffer) {
752             encodeAttributeHeader(byteBuffer);
753             addPadding(ATTR_HEADER, byteBuffer);
754         }
755     }
756 
757     /**
758      * AtNonceS represents the AT_NONCE_S attribute defined in RFC 4186#10.17 and RFC 4187#10.18
759      *
760      * <p>This Nonce is generated by the server and used for fast re-authentication only.
761      */
762     public static class AtNonceS extends EapSimAkaReservedBytesAttribute {
763         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
764         private static final int NONCE_S_LENGTH = 4 * LENGTH_SCALING;
765 
766         public final byte[] nonceS = new byte[NONCE_S_LENGTH];
767 
AtNonceS(int lengthInBytes, ByteBuffer byteBuffer)768         public AtNonceS(int lengthInBytes, ByteBuffer byteBuffer)
769                 throws EapSimAkaInvalidAttributeException {
770             super(EAP_AT_NONCE_S, lengthInBytes, byteBuffer);
771 
772             if (lengthInBytes != ATTR_LENGTH) {
773                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
774             }
775 
776             byteBuffer.get(nonceS);
777         }
778 
779         @VisibleForTesting
AtNonceS(byte[] nonceS)780         public AtNonceS(byte[] nonceS) throws EapSimAkaInvalidAttributeException {
781             super(EAP_AT_NONCE_S, ATTR_LENGTH);
782 
783             if (nonceS.length != NONCE_S_LENGTH) {
784                 throw new EapSimAkaInvalidAttributeException("NonceS length must be 16B");
785             }
786 
787             System.arraycopy(nonceS, 0, this.nonceS, 0, NONCE_S_LENGTH);
788         }
789 
790         @Override
encode(ByteBuffer byteBuffer)791         public void encode(ByteBuffer byteBuffer) {
792             super.encode(byteBuffer);
793 
794             byteBuffer.put(nonceS);
795         }
796     }
797 
798     /**
799      * AtNotification represents the AT_NOTIFICATION attribute defined in RFC 4186#10.18 and RFC
800      * 4187#10.19
801      */
802     public static class AtNotification extends EapSimAkaAttribute {
803         private static final int ATTR_LENGTH = 4;
804         private static final int SUCCESS_MASK = 0x8000;
805         private static final int PRE_SUCCESSFUL_CHALLENGE_MASK = 0x4000;
806 
807         // Notification codes defined in RFC 4186 Section 10.18
808         public static final int GENERAL_FAILURE_POST_CHALLENGE = 0;
809         public static final int GENERAL_FAILURE_PRE_CHALLENGE = 16384; // 0x4000
810         public static final int SUCCESS = 32768; // 0x8000
811         public static final int DENIED_ACCESS_POST_CHALLENGE = 1026;
812         public static final int USER_NOT_SUBSCRIBED_POST_CHALLENGE = 1031;
813 
814         private static final Map<Integer, String> CODE_DEFS = loadCodeDefs();
815 
816         public final boolean isSuccessCode;
817         public final boolean isPreSuccessfulChallenge;
818         public final int notificationCode;
819 
AtNotification(int lengthInBytes, ByteBuffer byteBuffer)820         public AtNotification(int lengthInBytes, ByteBuffer byteBuffer)
821                 throws EapSimAkaInvalidAttributeException {
822             super(EAP_AT_NOTIFICATION, lengthInBytes);
823 
824             if (lengthInBytes != ATTR_LENGTH) {
825                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
826             }
827 
828             notificationCode = Short.toUnsignedInt(byteBuffer.getShort());
829 
830             // If Success bit == 0, failure is implied
831             isSuccessCode = (notificationCode & SUCCESS_MASK) != 0;
832 
833             // if Phase bit == 0, notification code can only be used after a successful
834             isPreSuccessfulChallenge = (notificationCode & PRE_SUCCESSFUL_CHALLENGE_MASK) != 0;
835 
836             if (isSuccessCode && isPreSuccessfulChallenge) {
837                 throw new EapSimAkaInvalidAttributeException("Invalid state specified");
838             }
839         }
840 
841         @VisibleForTesting
AtNotification(int notificationCode)842         public AtNotification(int notificationCode) throws EapSimAkaInvalidAttributeException {
843             super(EAP_AT_NOTIFICATION, ATTR_LENGTH);
844             this.notificationCode = notificationCode;
845 
846             // If Success bit == 0, failure is implied
847             isSuccessCode = (notificationCode & SUCCESS_MASK) != 0;
848 
849             // if Phase bit == 0, notification code can only be used after a successful challenge
850             isPreSuccessfulChallenge = (notificationCode & PRE_SUCCESSFUL_CHALLENGE_MASK) != 0;
851 
852             if (isSuccessCode && isPreSuccessfulChallenge) {
853                 throw new EapSimAkaInvalidAttributeException("Invalid state specified");
854             }
855         }
856 
857         @Override
encode(ByteBuffer byteBuffer)858         public void encode(ByteBuffer byteBuffer) {
859             encodeAttributeHeader(byteBuffer);
860             byteBuffer.putShort((short) notificationCode);
861         }
862 
863         @Override
toString()864         public String toString() {
865             String description = CODE_DEFS.getOrDefault(notificationCode, "Code not recognized");
866             return "{Notification Code=" + notificationCode + ", descr=" + description + "}";
867         }
868 
loadCodeDefs()869         private static Map<Integer, String> loadCodeDefs() {
870             Map<Integer, String> defs = new HashMap<>();
871             defs.put(GENERAL_FAILURE_POST_CHALLENGE,
872                     "General failure after authentication. (Implies failure, used after successful"
873                     + " authentication.)");
874             defs.put(GENERAL_FAILURE_PRE_CHALLENGE,
875                     "General failure. (Implies failure, used before authentication.)");
876             defs.put(SUCCESS,
877                     "Success.  User has been successfully authenticated. (Does not imply failure,"
878                     + " used after successful authentication).");
879             defs.put(DENIED_ACCESS_POST_CHALLENGE,
880                     "User has been temporarily denied access to the requested service. (Implies"
881                     + " failure, used after successful authentication.)");
882             defs.put(USER_NOT_SUBSCRIBED_POST_CHALLENGE,
883                     "User has not subscribed to the requested service.  (Implies failure, used"
884                     + " after successful authentication.)");
885             return defs;
886         }
887     }
888 
889     /**
890      * AtClientErrorCode represents the AT_CLIENT_ERROR_CODE attribute defined in RFC 4186#10.19 and
891      * RFC 4187#10.20
892      */
893     public static class AtClientErrorCode extends EapSimAkaAttribute {
894         private static final String TAG = AtClientErrorCode.class.getSimpleName();
895         private static final int ATTR_LENGTH = 4;
896 
897         // Error codes defined in RFC 4186 Section 10.19
898         public static final AtClientErrorCode UNABLE_TO_PROCESS = getClientErrorCode(0);
899         public static final AtClientErrorCode UNSUPPORTED_VERSION = getClientErrorCode(1);
900         public static final AtClientErrorCode INSUFFICIENT_CHALLENGES = getClientErrorCode(2);
901         public static final AtClientErrorCode STALE_RANDS = getClientErrorCode(3);
902 
903         public final int errorCode;
904 
AtClientErrorCode(int lengthInBytes, int errorCode)905         public AtClientErrorCode(int lengthInBytes, int errorCode)
906                 throws EapSimAkaInvalidAttributeException {
907             super(EAP_AT_CLIENT_ERROR_CODE, lengthInBytes);
908 
909             if (lengthInBytes != ATTR_LENGTH) {
910                 throw new EapSimAkaInvalidAttributeException("Invalid Length specified");
911             }
912 
913             this.errorCode = errorCode;
914         }
915 
916         @Override
encode(ByteBuffer byteBuffer)917         public void encode(ByteBuffer byteBuffer) {
918             encodeAttributeHeader(byteBuffer);
919             byteBuffer.putShort((short) errorCode);
920         }
921 
getClientErrorCode(int errorCode)922         private static AtClientErrorCode getClientErrorCode(int errorCode) {
923             try {
924                 return new AtClientErrorCode(ATTR_LENGTH, errorCode);
925             } catch (EapSimAkaInvalidAttributeException exception) {
926                 LOG.wtf(TAG, "Exception thrown while making AtClientErrorCodeConstants");
927                 return null;
928             }
929         }
930     }
931 
932     /**
933      * AtAutn represents the AT_AUTN attribute defined in RFC 4187#10.7
934      */
935     public static class AtAutn extends EapSimAkaReservedBytesAttribute {
936         private static final int ATTR_LENGTH = 5 * LENGTH_SCALING;
937         private static final int AUTN_LENGTH = 16;
938 
939         public final byte[] autn = new byte[AUTN_LENGTH];
940 
AtAutn(int lengthInBytes, ByteBuffer byteBuffer)941         public AtAutn(int lengthInBytes, ByteBuffer byteBuffer)
942                 throws EapSimAkaInvalidAttributeException {
943             super(EAP_AT_AUTN, lengthInBytes, byteBuffer);
944 
945             if (lengthInBytes != ATTR_LENGTH) {
946                 throw new EapSimAkaInvalidAttributeException("Length must be 20B");
947             }
948 
949             byteBuffer.get(autn);
950         }
951 
952         @VisibleForTesting
AtAutn(byte[] autn)953         public AtAutn(byte[] autn) throws EapSimAkaInvalidAttributeException {
954             super(EAP_AT_AUTN, ATTR_LENGTH);
955 
956             if (autn.length != AUTN_LENGTH) {
957                 throw new EapSimAkaInvalidAttributeException("Autn must be 16B");
958             }
959 
960             System.arraycopy(autn, 0, this.autn, 0, AUTN_LENGTH);
961         }
962 
963         @Override
encode(ByteBuffer byteBuffer)964         public void encode(ByteBuffer byteBuffer) {
965             super.encode(byteBuffer);
966 
967             byteBuffer.put(autn);
968         }
969     }
970 
971     /**
972      * AtRes respresents the AT_RES attribute defined in RFC 4187#10.8
973      */
974     public static class AtRes extends EapSimAkaAttribute {
975         private static final int BITS_PER_BYTE = 8;
976         private static final int MIN_RES_LEN_BYTES = 4;
977         private static final int MAX_RES_LEN_BYTES = 16;
978 
979         public final byte[] res;
980 
AtRes(int lengthInBytes, ByteBuffer byteBuffer)981         public AtRes(int lengthInBytes, ByteBuffer byteBuffer)
982                 throws EapSimAkaInvalidAttributeException {
983             super(EAP_AT_RES, lengthInBytes);
984 
985             // RES length is in bits (RFC 4187#10.8).
986             // RES length should be a multiple of 8 bits (TS 133 105#5.1.7.8)
987             int resLength = Short.toUnsignedInt(byteBuffer.getShort());
988             if (resLength % BITS_PER_BYTE != 0) {
989                 throw new EapSimAkaInvalidAttributeException("RES length must be multiple of 8");
990             }
991             int resLengthBytes = resLength / BITS_PER_BYTE;
992             if (resLengthBytes < MIN_RES_LEN_BYTES || resLengthBytes > MAX_RES_LEN_BYTES) {
993                 throw new EapSimAkaInvalidAttributeException(
994                         "RES length must be: 4B <= len <= 16B");
995             }
996 
997             res = new byte[resLengthBytes];
998             byteBuffer.get(res);
999 
1000             int bytesUsed = MIN_ATTR_LENGTH + resLengthBytes;
1001             consumePadding(bytesUsed, byteBuffer);
1002         }
1003 
1004         @VisibleForTesting
AtRes(int lengthInBytes, byte[] res)1005         public AtRes(int lengthInBytes, byte[] res) throws EapSimAkaInvalidAttributeException {
1006             super(EAP_AT_RES, lengthInBytes);
1007 
1008             if (res.length < MIN_RES_LEN_BYTES || res.length > MAX_RES_LEN_BYTES) {
1009                 throw new EapSimAkaInvalidAttributeException(
1010                         "RES length must be: 4B <= len <= 16B");
1011             }
1012 
1013             this.res = res;
1014         }
1015 
1016         @Override
encode(ByteBuffer byteBuffer)1017         public void encode(ByteBuffer byteBuffer) {
1018             encodeAttributeHeader(byteBuffer);
1019 
1020             int resLenBits = res.length * BITS_PER_BYTE;
1021             byteBuffer.putShort((short) resLenBits);
1022             byteBuffer.put(res);
1023 
1024             int bytesUsed = MIN_ATTR_LENGTH + res.length;
1025             addPadding(bytesUsed, byteBuffer);
1026         }
1027 
1028         /**
1029          * Creates and returns an AtRes instance with the given res value.
1030          *
1031          * @param res byte-array RES value to be used for this
1032          * @return AtRes instance for the given RES value
1033          * @throws EapSimAkaInvalidAttributeException if the given res value has an invalid length
1034          */
getAtRes(byte[] res)1035         public static AtRes getAtRes(byte[] res) throws EapSimAkaInvalidAttributeException {
1036             // Attributes must be 4B-aligned, so there can be 0 to 3 padding bytes added
1037             int resLenBytes = MIN_ATTR_LENGTH + res.length;
1038             if (resLenBytes % LENGTH_SCALING != 0) {
1039                 resLenBytes += LENGTH_SCALING - (resLenBytes % LENGTH_SCALING);
1040             }
1041 
1042             return new AtRes(resLenBytes, res);
1043         }
1044 
1045         /**
1046          * Checks whether the given RES length is valid.
1047          *
1048          * @param resLenBytes the RES length to be checked
1049          * @return true iff the given resLen is valid
1050          */
isValidResLen(int resLenBytes)1051         public static boolean isValidResLen(int resLenBytes) {
1052             return resLenBytes >= MIN_RES_LEN_BYTES && resLenBytes <= MAX_RES_LEN_BYTES;
1053         }
1054     }
1055 
1056     /**
1057      * AtAuts represents the AT_AUTS attribute defined in RFC 4187#10.9
1058      */
1059     public static class AtAuts extends EapSimAkaAttribute {
1060         private static final int ATTR_LENGTH = 4 * LENGTH_SCALING;
1061         public static final int AUTS_LENGTH = 14;
1062 
1063         public final byte[] auts = new byte[AUTS_LENGTH];
1064 
AtAuts(int lengthInBytes, ByteBuffer byteBuffer)1065         public AtAuts(int lengthInBytes, ByteBuffer byteBuffer)
1066                 throws EapSimAkaInvalidAttributeException {
1067             super(EAP_AT_AUTS, lengthInBytes);
1068 
1069             if (lengthInBytes != ATTR_LENGTH) {
1070                 throw new EapSimAkaInvalidAttributeException("Length must be 16B");
1071             }
1072 
1073             byteBuffer.get(auts);
1074         }
1075 
AtAuts(byte[] auts)1076         public AtAuts(byte[] auts) throws EapSimAkaInvalidAttributeException {
1077             super(EAP_AT_AUTS, ATTR_LENGTH);
1078 
1079             if (auts.length != AUTS_LENGTH) {
1080                 throw new EapSimAkaInvalidAttributeException("Auts must be 14B");
1081             }
1082 
1083             System.arraycopy(auts, 0, this.auts, 0, AUTS_LENGTH);
1084         }
1085 
1086         @Override
encode(ByteBuffer byteBuffer)1087         public void encode(ByteBuffer byteBuffer) {
1088             encodeAttributeHeader(byteBuffer);
1089 
1090             byteBuffer.put(auts);
1091         }
1092     }
1093 
1094     /**
1095      * AtKdfInput represents the AT_KDF_INPUT attribute defined in RFC 5448#3.1
1096      */
1097     public static class AtKdfInput extends EapSimAkaAttribute {
1098         public final byte[] networkName;
1099 
AtKdfInput(int lengthInBytes, ByteBuffer byteBuffer)1100         public AtKdfInput(int lengthInBytes, ByteBuffer byteBuffer)
1101                 throws EapSimAkaInvalidAttributeException {
1102             super(EAP_AT_KDF_INPUT, lengthInBytes);
1103 
1104             int networkNameLength = Short.toUnsignedInt(byteBuffer.getShort());
1105             networkName = new byte[networkNameLength];
1106             byteBuffer.get(networkName);
1107 
1108             int bytesUsed = MIN_ATTR_LENGTH + networkNameLength;
1109             consumePadding(bytesUsed, byteBuffer);
1110         }
1111 
1112         @VisibleForTesting
AtKdfInput(int lengthInbytes, byte[] networkName)1113         public AtKdfInput(int lengthInbytes, byte[] networkName)
1114                 throws EapSimAkaInvalidAttributeException {
1115             super(EAP_AT_KDF_INPUT, lengthInbytes);
1116 
1117             this.networkName = networkName;
1118         }
1119 
1120         @Override
encode(ByteBuffer byteBuffer)1121         public void encode(ByteBuffer byteBuffer) {
1122             encodeAttributeHeader(byteBuffer);
1123             byteBuffer.putShort((short) networkName.length);
1124             byteBuffer.put(networkName);
1125 
1126             int bytesUsed = MIN_ATTR_LENGTH + networkName.length;
1127             addPadding(bytesUsed, byteBuffer);
1128         }
1129     }
1130 
1131     /**
1132      * AdKdf represents the AT_KDF attribute defined in RFC 5448#3.2
1133      */
1134     public static class AtKdf extends EapSimAkaAttribute {
1135         private static final int ATTR_LENGTH = MIN_ATTR_LENGTH;
1136 
1137         public final int kdf;
1138 
AtKdf(int lengthInBytes, ByteBuffer buffer)1139         public AtKdf(int lengthInBytes, ByteBuffer buffer)
1140                 throws EapSimAkaInvalidAttributeException {
1141             super(EAP_AT_KDF, lengthInBytes);
1142 
1143             if (lengthInBytes != ATTR_LENGTH) {
1144                 throw new EapSimAkaInvalidAttributeException("AtKdf length must be 4B");
1145             }
1146 
1147             kdf = Short.toUnsignedInt(buffer.getShort());
1148         }
1149 
1150         @VisibleForTesting
AtKdf(int kdf)1151         public AtKdf(int kdf) throws EapSimAkaInvalidAttributeException {
1152             super(EAP_AT_KDF, ATTR_LENGTH);
1153 
1154             this.kdf = kdf;
1155         }
1156 
1157         @Override
encode(ByteBuffer byteBuffer)1158         public void encode(ByteBuffer byteBuffer) {
1159             encodeAttributeHeader(byteBuffer);
1160 
1161             byteBuffer.putShort((short) kdf);
1162         }
1163     }
1164 
1165     /**
1166      * AtBidding represents the AT_BIDDING attribute defined in RFC 5448#4
1167      */
1168     public static class AtBidding extends EapSimAkaAttribute {
1169         private static final int ATTR_LENGTH = MIN_ATTR_LENGTH;
1170         private static final int SUPPORTS_EAP_AKA_PRIME_MASK = 0x8000;
1171 
1172         public final boolean doesServerSupportEapAkaPrime;
1173 
AtBidding(int lengthInBytes, ByteBuffer buffer)1174         public AtBidding(int lengthInBytes, ByteBuffer buffer)
1175                 throws EapSimAkaInvalidAttributeException {
1176             super(EAP_AT_BIDDING, lengthInBytes);
1177 
1178             if (lengthInBytes != ATTR_LENGTH) {
1179                 throw new EapSimAkaInvalidAttributeException("AtBidding length must be 4B");
1180             }
1181 
1182             int serverFlag = Short.toUnsignedInt(buffer.getShort());
1183             doesServerSupportEapAkaPrime = (serverFlag & SUPPORTS_EAP_AKA_PRIME_MASK) != 0;
1184         }
1185 
1186         @VisibleForTesting
AtBidding(boolean doesServerSupportEapAkaPrime)1187         public AtBidding(boolean doesServerSupportEapAkaPrime)
1188                 throws EapSimAkaInvalidAttributeException {
1189             super(EAP_AT_BIDDING, ATTR_LENGTH);
1190 
1191             this.doesServerSupportEapAkaPrime = doesServerSupportEapAkaPrime;
1192         }
1193 
1194         @Override
encode(ByteBuffer byteBuffer)1195         public void encode(ByteBuffer byteBuffer) {
1196             encodeAttributeHeader(byteBuffer);
1197 
1198             int flagToWrite = doesServerSupportEapAkaPrime ? SUPPORTS_EAP_AKA_PRIME_MASK : 0;
1199             byteBuffer.putShort((short) flagToWrite);
1200         }
1201     }
1202 }
1203