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