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