aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2021-03-04 05:11:34 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2021-03-04 05:11:34 +0000
commit7683e077e4d612988bff5c928455cbe2289ca190 (patch)
tree08e179b6912aebb95c8f882eb93a23709c25e162
parentb73e510b0f8aa35b1c3ae34ea5e3acf480896c65 (diff)
parent1d74d2d9c825b08dd4e5666f443fc6feba75ca77 (diff)
downloadservice_entitlement-7683e077e4d612988bff5c928455cbe2289ca190.tar.gz
Merge "Handle EAP synchronization failure" into sc-dev
-rw-r--r--java/com/android/libraries/entitlement/EapAkaHelper.java8
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementException.java22
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaApi.java99
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaChallenge.java205
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java348
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java92
-rw-r--r--java/com/android/libraries/entitlement/http/HttpClient.java8
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java64
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaChallengeTest.java247
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaResponseTest.java319
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java50
11 files changed, 894 insertions, 568 deletions
diff --git a/java/com/android/libraries/entitlement/EapAkaHelper.java b/java/com/android/libraries/entitlement/EapAkaHelper.java
index 86e3149..05c3964 100644
--- a/java/com/android/libraries/entitlement/EapAkaHelper.java
+++ b/java/com/android/libraries/entitlement/EapAkaHelper.java
@@ -22,6 +22,7 @@ import android.telephony.TelephonyManager;
import androidx.annotation.Nullable;
import com.android.libraries.entitlement.eapaka.EapAkaApi;
+import com.android.libraries.entitlement.eapaka.EapAkaChallenge;
import com.android.libraries.entitlement.eapaka.EapAkaResponse;
/**
@@ -76,8 +77,11 @@ public class EapAkaHelper {
@Nullable
public String getEapAkaChallengeResponse(String challenge) {
try {
- return new EapAkaResponse(challenge)
- .getEapAkaChallengeResponse(mContext, mSimSubscriptionId);
+ EapAkaChallenge eapAkaChallenge = EapAkaChallenge.parseEapAkaChallenge(challenge);
+ EapAkaResponse response =
+ EapAkaResponse.respondToEapAkaChallenge(
+ mContext, mSimSubscriptionId, eapAkaChallenge);
+ return response.response(); // Would be null on synchrinization failure
} catch (ServiceEntitlementException e) {
return null;
}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
index d650726..45b1b9b 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementException.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
@@ -24,33 +24,43 @@ public class ServiceEntitlementException extends Exception {
* Unknown error.
*/
public static final int ERROR_UNKNOWN = 0;
+
+ // Android telephony related failures
/**
* Android telephony is unable to provide info like IMSI, e.g. when modem crashed.
*/
- public static final int ERROR_PHONE_NOT_AVAILABLE = 1;
+ public static final int ERROR_PHONE_NOT_AVAILABLE = 10;
+
+ // EAP-AKA authentication related falures
/**
* SIM not returning a response to the EAP-AKA challenge, e.g. when the challenge is invalid.
* This can happen only when an embedded EAP-AKA challange is conducted, as per GMSA spec TS.43
* section 2.6.1.
*/
- public static final int ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE = 2;
+ public static final int ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE = 20;
+ /**
+ * EAP-AKA synchronization failure that cannot be recoverd even after the "Sequence number
+ * synchronization" procedure as defined in RFC 4187.
+ */
+ public static final int ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE = 21;
+
+ // HTTP related failures
/**
* Cannot connect to the entitlment server, e.g. due to weak mobile network and Wi-Fi
* connection.
*/
- public static final int ERROR_SEVER_NOT_CONNECTABLE = 3;
+ public static final int ERROR_SERVER_NOT_CONNECTABLE = 30;
/**
* HTTP response received with a status code indicating failure, e.g. 4xx and 5xx. Use {@link
* #getHttpStatus} to get the status code and {@link #getMessage} the error message in the
* response body.
*/
- public static final int ERROR_HTTP_STATUS_NOT_SUCCESS = 4;
-
+ public static final int ERROR_HTTP_STATUS_NOT_SUCCESS = 31;
/**
* HTTP response received with a malformed format. e.g. the response with content-type JSON but
* failing JSON parser.
*/
- public static final int MALFORMED_HTTP_RESPONSE = 5;
+ public static final int ERROR_MALFORMED_HTTP_RESPONSE = 32;
/**
* Default HTTP status if not been specified.
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
index 065b556..dd15426 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
@@ -16,7 +16,8 @@
package com.android.libraries.entitlement.eapaka;
-import static com.android.libraries.entitlement.ServiceEntitlementException.MALFORMED_HTTP_RESPONSE;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE;
import android.content.Context;
import android.net.Uri;
@@ -110,6 +111,9 @@ public class EapAkaApi {
private static final String REQUEST_CONTENT_TYPE_JSON =
"application/vnd.gsma.eap-relay.v1.0+json";
+ // In case of EAP-AKA synchronization failure, we try to recover for at most two times.
+ private static final int FOLLOW_SYNC_FAILURE_MAX_COUNT = 2;
+
private final Context mContext;
private final int mSimSubscriptionId;
private final HttpClient mHttpClient;
@@ -146,43 +150,84 @@ public class EapAkaApi {
.setNetwork(carrierConfig.network())
.build();
HttpResponse response = mHttpClient.request(httpRequest);
- if (request.authenticationToken().isEmpty()) {
- // EapAka token challenge for initial AuthN
- if (response.contentType() != ContentType.JSON) {
- throw new ServiceEntitlementException(
- MALFORMED_HTTP_RESPONSE, "Unexpected http ContentType");
- }
+ if (!request.authenticationToken().isEmpty()) {
+ // Fast Re-Authentication flow with pre-existing auth token
+ Log.d(TAG, "Fast Re-Authentication");
+ return response.body();
+ }
+ // Full Authentication flow
+ Log.d(TAG, "Full Authentication");
+ return respondToEapAkaChallenge(carrierConfig, response, FOLLOW_SYNC_FAILURE_MAX_COUNT)
+ .body();
+ }
- Log.d(TAG, "initial AuthN");
- String responseData = "";
- try {
- responseData = new JSONObject(response.body()).getString(
- EAP_CHALLENGE_RESPONSE);
- } catch (JSONException jsonException) {
+ /**
+ * Sends a follow-up HTTP request to the HTTP {@code response} using the same cookie, and
+ * returns the follow-up HTTP response.
+ *
+ * <p>The {@code response} should contain a EAP-AKA challenge from server, and the
+ * follow-up request could contain:
+ *
+ * <ul>
+ * <li>The EAP-AKA response message, and the follow-up response should contain the
+ * service entitlement configuration; or,
+ * <li>The EAP-AKA synchronization failure message, and the follow-up response should
+ * contain the new EAP-AKA challenge. Then this method calls itself to follow-up
+ * the new challenge and return a new response, if {@code followSyncFailureCount}
+ * is greater than zero. When this method call itself {@code followSyncFailureCount} is
+ * reduced by one to prevent infinite loop (unlikely in practice, but just in case).
+ * </ul>
+ */
+ private HttpResponse respondToEapAkaChallenge(
+ CarrierConfig carrierConfig, HttpResponse response, int followSyncFailureCount)
+ throws ServiceEntitlementException {
+ if (response.contentType() != ContentType.JSON) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE, "Unexpected http ContentType");
+ }
+ String eapAkaChallenge;
+ try {
+ eapAkaChallenge = new JSONObject(response.body()).getString(EAP_CHALLENGE_RESPONSE);
+ } catch (JSONException jsonException) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE, "Failed to parse json object", jsonException);
+ }
+ EapAkaChallenge challenge = EapAkaChallenge.parseEapAkaChallenge(eapAkaChallenge);
+ EapAkaResponse eapAkaResponse =
+ EapAkaResponse.respondToEapAkaChallenge(mContext, mSimSubscriptionId, challenge);
+ // This could be a successful authentication, or synchronization failure.
+ if (eapAkaResponse.response() != null) { // successful authentication
+ return challengeResponse(eapAkaResponse.response(), carrierConfig, response.cookie());
+ } else if (eapAkaResponse.synchronizationFailureResponse() != null) {
+ Log.d(TAG, "synchronization failure");
+ HttpResponse newChallenge =
+ challengeResponse(
+ eapAkaResponse.synchronizationFailureResponse(),
+ carrierConfig,
+ response.cookie());
+ if (followSyncFailureCount > 0) {
+ return respondToEapAkaChallenge(
+ carrierConfig, newChallenge, followSyncFailureCount - 1);
+ } else {
throw new ServiceEntitlementException(
- MALFORMED_HTTP_RESPONSE, "Failed to parse json object", jsonException);
+ ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE,
+ "Unable to recover from EAP-AKA synchroinization failure");
}
- return challengeResponse(
- new EapAkaResponse(responseData).getEapAkaChallengeResponse(mContext,
- mSimSubscriptionId), carrierConfig, response.cookie());
- } else {
- // Result of fast AuthN
- Log.d(TAG, "fast AuthN");
- return response.body();
+ } else { // not possible
+ throw new AssertionError("EapAkaResponse invalid.");
}
}
- private String challengeResponse(String akaChallengeResponse,
- CarrierConfig carrierConfig,
- String cookie)
+ private HttpResponse challengeResponse(
+ String eapAkaChallengeResponse, CarrierConfig carrierConfig, String cookie)
throws ServiceEntitlementException {
Log.d(TAG, "challengeResponse");
JSONObject postData = new JSONObject();
try {
- postData.put(EAP_CHALLENGE_RESPONSE, akaChallengeResponse);
+ postData.put(EAP_CHALLENGE_RESPONSE, eapAkaChallengeResponse);
} catch (JSONException jsonException) {
throw new ServiceEntitlementException(
- MALFORMED_HTTP_RESPONSE, "Failed to put post data", jsonException);
+ ERROR_MALFORMED_HTTP_RESPONSE, "Failed to put post data", jsonException);
}
HttpRequest request =
HttpRequest.builder()
@@ -195,7 +240,7 @@ public class EapAkaApi {
.setTimeoutInSec(carrierConfig.timeoutInSec())
.setNetwork(carrierConfig.network())
.build();
- return mHttpClient.request(request).body();
+ return mHttpClient.request(request);
}
String entitlementStatusUrl(
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaChallenge.java b/java/com/android/libraries/entitlement/eapaka/EapAkaChallenge.java
new file mode 100644
index 0000000..6563ede
--- /dev/null
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaChallenge.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.eapaka;
+
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.util.Base64;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+
+/**
+ * Parses EAP-AKA challenge from server. Refer to RFC 4187 Section 8.1 Message
+ * Format/RFC 3748 Session 4 EAP Packet Format.
+ */
+public class EapAkaChallenge {
+ private static final String TAG = "ServiceEntitlement";
+
+ private static final int EAP_AKA_HEADER_LENGTH = 8;
+ private static final byte CODE_REQUEST = 0x01;
+ static final byte TYPE_EAP_AKA = 0x17;
+ static final byte SUBTYPE_AKA_CHALLENGE = 0x01;
+ private static final byte ATTRIBUTE_RAND = 0x01;
+ private static final byte ATTRIBUTE_AUTN = 0x02;
+ private static final int ATTRIBUTE_LENGTH = 20;
+ private static final int RAND_LENGTH = 16;
+ private static final int AUTN_LENGTH = 16;
+
+ // The identifier of Response must same as Request
+ private byte mIdentifier = -1;
+ // The value of AT_AUTN, network authentication token
+ private byte[] mAutn;
+ // The value of AT_RAND, random challenge
+ private byte[] mRand;
+
+ // Base64 encoded 3G security context for SIM Authentication request
+ private String mSimAuthenticationRequest;
+
+ private EapAkaChallenge() {}
+
+ /** Parses a EAP-AKA challenge request message encoded in base64. */
+ public static EapAkaChallenge parseEapAkaChallenge(String challenge)
+ throws ServiceEntitlementException {
+ byte[] data;
+ try {
+ data = Base64.decode(challenge.getBytes(UTF_8), Base64.DEFAULT);
+ } catch (IllegalArgumentException illegalArgumentException) {
+ throw new ServiceEntitlementException(
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
+ "EAP-AKA challenge is not a valid base64!");
+ }
+ EapAkaChallenge result = new EapAkaChallenge();
+ if (result.parseEapAkaHeader(data) && result.parseRandAndAutn(data)) {
+ result.mSimAuthenticationRequest = result.getSimAuthChallengeData();
+ return result;
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
+ "EAP-AKA challenge message is not valid");
+ }
+ }
+
+ /**
+ * Returns the base64 encoded 3G security context for SIM Authentication request,
+ * or {@code null} if the EAP-AKA challenge is not valid.
+ */
+ @Nullable
+ public String getSimAuthenticationRequest() {
+ return mSimAuthenticationRequest;
+ }
+
+ /** Returns the EAP package identifier in the EAP-AKA challenge. */
+ public byte getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * Parses EAP-AKA header, 8 bytes including 2 reserved bytes.
+ *
+ * @return {@code true} if success to parse the header of request data.
+ */
+ private boolean parseEapAkaHeader(byte[] data) {
+ if (data.length < EAP_AKA_HEADER_LENGTH) {
+ return false;
+ }
+ // Code for EAP-request should be CODE_REQUEST
+ byte code = data[0];
+ // EAP package identifier
+ mIdentifier = data[1];
+ // Total length of full EAP-AKA message, include code, identifier, ...
+ int length = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+ // Type for EAP-AKA should be TYPE_EAP_AKA
+ byte type = data[4];
+ // SubType for AKA-Challenge should be SUBTYPE_AKA_CHALLENGE
+ byte subType = data[5];
+
+ // Validate header
+ if (code != CODE_REQUEST
+ || length != data.length
+ || type != TYPE_EAP_AKA
+ || subType != SUBTYPE_AKA_CHALLENGE) {
+ Log.d(
+ TAG,
+ "Invalid EAP-AKA Header, code="
+ + code
+ + ", length="
+ + length
+ + ", real length="
+ + data.length
+ + ", type="
+ + type
+ + ", subType="
+ + subType);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Parses AT_RAND and AT_AUTN. Refer to RFC 4187 section 10.6 AT_RAND/section 10.7 AT_AUTN.
+ *
+ * @return {@code true} if success to parse the RAND and AUTN data.
+ */
+ private boolean parseRandAndAutn(byte[] data) {
+ int index = EAP_AKA_HEADER_LENGTH;
+ while (index < data.length) {
+ int remainsLength = data.length - index;
+ if (remainsLength <= 2) {
+ Log.d(TAG, "Error! remainsLength = " + remainsLength);
+ return false;
+ }
+
+ byte attributeType = data[index];
+
+ // the length of this attribute in multiples of 4 bytes, include attribute type and
+ // length
+ int length = (data[index + 1] & 0xff) * 4;
+ if (length > remainsLength) {
+ Log.d(TAG,
+ "Length Error! length is " + length + " but only remains " + remainsLength);
+ return false;
+ }
+
+ // see RFC 4187 section 11 for attribute type
+ if (attributeType == ATTRIBUTE_RAND) {
+ if (length != ATTRIBUTE_LENGTH) {
+ Log.d(TAG, "AT_RAND length is " + length);
+ return false;
+ }
+ mRand = new byte[RAND_LENGTH];
+ System.arraycopy(data, index + 4, mRand, 0, RAND_LENGTH);
+ } else if (attributeType == ATTRIBUTE_AUTN) {
+ if (length != ATTRIBUTE_LENGTH) {
+ Log.d(TAG, "AT_AUTN length is " + length);
+ return false;
+ }
+ mAutn = new byte[AUTN_LENGTH];
+ System.arraycopy(data, index + 4, mAutn, 0, AUTN_LENGTH);
+ }
+
+ index += length;
+ }
+
+ // check has AT_RAND and AT_AUTH
+ if (mRand == null || mAutn == null) {
+ Log.d(TAG, "Invalid Type Datas!");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns Base64 encoded 3G security context for SIM Authentication request.
+ */
+ @Nullable
+ private String getSimAuthChallengeData() {
+ byte[] challengeData = new byte[RAND_LENGTH + AUTN_LENGTH + 2];
+ challengeData[0] = (byte) RAND_LENGTH;
+ System.arraycopy(mRand, 0, challengeData, 1, RAND_LENGTH);
+ challengeData[RAND_LENGTH + 1] = (byte) AUTN_LENGTH;
+ System.arraycopy(mAutn, 0, challengeData, RAND_LENGTH + 2, AUTN_LENGTH);
+
+ return Base64.encodeToString(challengeData, Base64.NO_WRAP).trim();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java
index 81b8046..12716f5 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java
@@ -17,10 +17,11 @@
package com.android.libraries.entitlement.eapaka;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallenge.SUBTYPE_AKA_CHALLENGE;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallenge.TYPE_EAP_AKA;
import android.content.Context;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -32,262 +33,133 @@ import com.android.libraries.entitlement.utils.BytesConverter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
- * Generate the response of EAP-AKA token challenge. Refer to RFC 4187 Section 8.1 Message
+ * Generates the response of EAP-AKA challenge. Refer to RFC 4187 Section 8.1 Message
* Format/RFC 3748 Session 4 EAP Packet Format.
*/
public class EapAkaResponse {
private static final String TAG = "ServiceEntitlement";
- private static final int EAP_AKA_HEADER_LENGTH = 8;
- private static final byte CODE_REQUEST = 0x01;
private static final byte CODE_RESPONSE = 0x02;
- private static final byte TYPE_EAP_AKA = 0x17;
- private static final byte SUBTYPE_AKA_CHALLENGE = 0x01;
- private static final byte ATTRIBUTE_RAND = 0x01;
- private static final byte ATTRIBUTE_AUTN = 0x02;
+ private static final byte SUBTYPE_SYNC_FAILURE = 0x04;
private static final byte ATTRIBUTE_RES = 0x03;
+ private static final byte ATTRIBUTE_AUTS = 0x04;
private static final byte ATTRIBUTE_MAC = 0x0B;
private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1";
- private static final int ATTRIBUTE_LENGTH = 20;
private static final int SHA1_OUTPUT_LENGTH = 20;
+ private static final int MAC_LENGTH = 16;
- private static final byte RAND_LEN = 0x10;
- private static final byte AUTN_LEN = 0x10;
+ // RFC 4187 Section 9.4 EAP-Response/AKA-Challenge
+ private String mResponse;
+ // RFC 4187 Section 9.6 EAP-Response/AKA-Synchronization-Failure
+ private String mSynchronizationFailureResponse;
- /**
- * {@link #CODE_REQUEST} for Request, {@link #CODE_RESPONSE} for Response
- */
- private byte mCode = -1;
- /**
- * The identifier of Response must same as Request
- */
- private byte mIdentifier = -1;
- /**
- * The total length of full EAP-AKA message, include code, identifier, ...
- */
- private int mLength = -1;
- /**
- * In EAP-AKA, the Type field is set to {@link #TYPE_EAP_AKA}
- */
- private byte mType = -1;
- /**
- * SubType for AKA-Challenge should be {@link #SUBTYPE_AKA_CHALLENGE}
- */
- private byte mSubType = -1;
- /**
- * The value of AT_AUTN, network authentication token
- */
- private byte[] mAutn;
- /**
- * The value of AT_RAND, RAND random number
- */
- private byte[] mRand;
-
- private boolean mValid;
-
- public EapAkaResponse(String eapAkaChallenge) {
- try {
- parseEapAkaChallengeRequest(eapAkaChallenge);
- } catch (Exception e) {
- Log.e(TAG, "parseEapAkaChallengeRequest Exception:", e);
- mValid = false;
- }
- }
-
- private void parseEapAkaChallengeRequest(String request) {
- if (TextUtils.isEmpty(request)) {
- return;
- }
+ private EapAkaResponse() {}
- try {
- byte[] data = Base64.decode(request, Base64.DEFAULT);
- if (parseEapAkaHeader(data) && parseRandAndAutn(data)) {
- mValid = true;
- } else {
- Log.d(TAG, "Invalid data!");
- }
- } catch (IllegalArgumentException illegalArgumentException) {
- Log.e(TAG, "Invalid base-64 content");
- }
- }
-
- /**
- * Parse EAP-AKA header, 8 bytes include 2 reserved bytes.
- *
- * @param data raw bytes of request data.
- * @return {@code true} if success to parse the header of request data.
- */
- private boolean parseEapAkaHeader(byte[] data) {
- if (data.length <= EAP_AKA_HEADER_LENGTH) {
- return false;
- }
- mCode = data[0];
- mIdentifier = data[1];
- mLength = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
- mType = data[4];
- mSubType = data[5];
-
- // valid header
- if (mCode != CODE_REQUEST
- || mLength != data.length
- || mType != TYPE_EAP_AKA
- || mSubType != SUBTYPE_AKA_CHALLENGE) {
- Log.d(
- TAG,
- "Invalid EAP-AKA Header, code="
- + mCode
- + ", length="
- + mLength
- + ", real length="
- + data.length
- + ", type="
- + mType
- + ", subType="
- + mSubType);
- return false;
- }
-
- return true;
+ /** Returns EAP-Response/AKA-Challenge, if authentication success. Otherwise {@code null}. */
+ @Nullable
+ public String response() {
+ return mResponse;
}
/**
- * Refer to RFC 4187 section 10.6 AT_RAND/RFC 4187 section 10.7 AT_AUTN.
- *
- * @param data raw bytes of request data.
- * @return {@code true} if success to parse the RAND and AUTN data.
+ * Returns EAP-Response/AKA-Synchronization-Failure, if synchronization failure detected.
+ * Otherwise {@code null}.
*/
- private boolean parseRandAndAutn(byte[] data) {
- int index = EAP_AKA_HEADER_LENGTH;
- while (index < data.length) {
- int remainsLength = data.length - index;
- if (remainsLength <= 2) {
- Log.d(TAG, "Error! remainsLength = " + remainsLength);
- return false;
- }
-
- byte attributeType = data[index];
-
- // the length of this attribute in multiples of 4 bytes, include attribute type and
- // length
- int length = (data[index + 1] & 0xff) * 4;
- if (length > remainsLength) {
- Log.d(TAG,
- "Length Error! length is " + length + " but only remains " + remainsLength);
- return false;
- }
-
- // see RFC 4187 section 11 for attribute type
- if (attributeType == ATTRIBUTE_RAND) {
- if (length != ATTRIBUTE_LENGTH) {
- Log.d(TAG, "AT_RAND length is " + length);
- return false;
- }
- mRand = new byte[16];
- System.arraycopy(data, index + 4, mRand, 0, 16);
- } else if (attributeType == ATTRIBUTE_AUTN) {
- if (length != ATTRIBUTE_LENGTH) {
- Log.d(TAG, "AT_AUTN length is " + length);
- return false;
- }
- mAutn = new byte[16];
- System.arraycopy(data, index + 4, mAutn, 0, 16);
- }
-
- index += length;
- }
-
- // check has AT_RAND and AT_AUTH
- if (mRand == null || mAutn == null) {
- Log.d(TAG, "Invalid Type Datas!");
- return false;
- }
-
- return true;
+ @Nullable
+ public String synchronizationFailureResponse() {
+ return mSynchronizationFailureResponse;
}
/**
* Returns EAP-AKA challenge response message which generated with SIM EAP-AKA authentication
* with network provided EAP-AKA challenge request message.
*/
- public String getEapAkaChallengeResponse(Context context, int simSubscriptionId)
+ public static EapAkaResponse respondToEapAkaChallenge(
+ Context context, int simSubscriptionId, EapAkaChallenge eapAkaChallenge)
throws ServiceEntitlementException {
- if (!mValid) {
- throw new ServiceEntitlementException(
- ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "EAP-AKA Challenge message not valid!");
- }
-
TelephonyManager telephonyManager =
- context.getSystemService(TelephonyManager.class).createForSubscriptionId(
- simSubscriptionId);
+ context.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(simSubscriptionId);
// process EAP-AKA authentication with SIM
String response =
telephonyManager.getIccAuthentication(
TelephonyManager.APPTYPE_USIM,
TelephonyManager.AUTHTYPE_EAP_AKA,
- getSimAuthChallengeData());
-
- EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(response);
- // RFC 4187, section 7. Key Generation
- // generate master key
- MasterKey mk =
- MasterKey.create(
- EapAkaApi.getImsiEap(telephonyManager.getSimOperator(),
- telephonyManager.getSubscriberId()),
- securityContext.getIk(),
- securityContext.getCk());
- // K_aut is the key used to calculate MAC
- if (mk.getAut() == null) {
+ eapAkaChallenge.getSimAuthenticationRequest());
+ if (response == null) {
throw new ServiceEntitlementException(
- ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "Can't generate K_Aut!");
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "EAP-AKA response is null!");
}
- // generate EAP-AKA Challenge Response message
- byte[] challengeResponse =
- generateEapAkaChallengeResponse(securityContext.getRes(), mk.getAut());
- if (challengeResponse == null) {
- throw new ServiceEntitlementException(
- ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
- "Failed to generate EAP-AKA Challenge Response data!");
- }
+ EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(response);
+ EapAkaResponse result = new EapAkaResponse();
+
+ if (securityContext.getRes() != null
+ && securityContext.getIk() != null
+ && securityContext.getCk() != null) { // Success authentication
+
+ // generate master key - refer to RFC 4187, section 7. Key Generation
+ MasterKey mk =
+ MasterKey.create(
+ EapAkaApi.getImsiEap(telephonyManager.getSimOperator(),
+ telephonyManager.getSubscriberId()),
+ securityContext.getIk(),
+ securityContext.getCk());
+ // K_aut is the key used to calculate MAC
+ if (mk.getAut() == null) {
+ throw new ServiceEntitlementException(
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "Can't generate K_Aut!");
+ }
- return Base64.encodeToString(challengeResponse, Base64.NO_WRAP).trim();
- }
+ // generate EAP-AKA challenge response message
+ byte[] challengeResponse =
+ generateEapAkaChallengeResponse(
+ securityContext.getRes(), eapAkaChallenge.getIdentifier(), mk.getAut());
+ if (challengeResponse == null) {
+ throw new ServiceEntitlementException(
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
+ "Failed to generate EAP-AKA Challenge Response data!");
+ }
+ // base64 encoding
+ result.mResponse = Base64.encodeToString(challengeResponse, Base64.NO_WRAP).trim();
- /**
- * Returns Base64 encoded GSM/3G security context for SIM Authentication request.
- */
- @Nullable
- private String getSimAuthChallengeData() {
- if (!mValid) {
- return null;
- }
+ } else if (securityContext.getAuts() != null) {
+
+ byte[] syncFailure =
+ generateEapAkaSynchronizationFailureResponse(
+ securityContext.getAuts(), eapAkaChallenge.getIdentifier());
+ result.mSynchronizationFailureResponse =
+ Base64.encodeToString(syncFailure, Base64.NO_WRAP).trim();
- byte[] challengeData = new byte[RAND_LEN + AUTN_LEN + 2];
- challengeData[0] = RAND_LEN;
- System.arraycopy(mRand, 0, challengeData, 1, RAND_LEN);
- challengeData[RAND_LEN + 1] = AUTN_LEN;
- System.arraycopy(mAutn, 0, challengeData, RAND_LEN + 2, AUTN_LEN);
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
+ "Invalid SIM EAP-AKA authentication response!");
+ }
- return Base64.encodeToString(challengeData, Base64.NO_WRAP).trim();
+ return result;
}
/**
- * Returns EAP-AKA Challenge response message or {@code null} if failed to generate.
+ * Returns EAP-Response/AKA-Challenge message, or {@code null} if failed to generate.
+ * Refer to RFC 4187 section 9.4 EAP-Response/AKA-Challenge.
*/
@VisibleForTesting
@Nullable
- byte[] generateEapAkaChallengeResponse(@Nullable byte[] res, @Nullable byte[] aut) {
+ static byte[] generateEapAkaChallengeResponse(
+ @Nullable byte[] res, byte identifier, @Nullable byte[] aut) {
if (res == null || aut == null) {
return null;
}
- byte[] message = createEapAkaChallengeResponse(res);
+ byte[] message = createEapAkaChallengeResponse(res, identifier);
// use K_aut as key to calculate mac
byte[] mac = calculateMac(aut, message);
@@ -303,26 +175,60 @@ public class EapAkaResponse {
return message;
}
+ /**
+ * Returns EAP-Response/AKA-Synchronization-Failure, or {@code null} if failed to generate.
+ * Refer to RFC 4187 section 9.6 EAP-Response/AKA-Synchronization-Failure.
+ */
+ @VisibleForTesting
+ @Nullable
+ static byte[] generateEapAkaSynchronizationFailureResponse(
+ @Nullable byte[] auts, byte identifier) {
+ // size = 8 (header) + 2 (attribute & length) + AUTS
+ byte[] message = new byte[10 + auts.length];
+
+ // set up header
+ message[0] = CODE_RESPONSE;
+ // identifier: same as request
+ message[1] = identifier;
+ // length: include entire EAP-AKA message
+ byte[] lengthBytes = BytesConverter.convertIntegerTo4Bytes(message.length);
+ message[2] = lengthBytes[2];
+ message[3] = lengthBytes[3];
+ message[4] = TYPE_EAP_AKA;
+ message[5] = SUBTYPE_SYNC_FAILURE;
+ // reserved: 2 bytes
+ message[6] = 0x00;
+ message[7] = 0x00;
+
+ // set up AT_AUTS. RFC 4187, Section 10.9 AT_AUTS
+ message[8] = ATTRIBUTE_AUTS;
+ // length (in 4-bytes): 4, because AUTS is 14 bytes, plus the attribute (1 byte) and
+ // the length (1 byte).
+ message[9] = 0x04;
+ System.arraycopy(auts, 0, message, 10, auts.length);
+ return message;
+ }
+
// AT_MAC/AT_RES are must included in response message
//
// Reference RFC 4187 Section 8.1 Message Format
// RFC 4187 Section 9.4 EAP-Response/AKA-Challenge
- // RFC 3748, Section 4.1. Request and Response
- private byte[] createEapAkaChallengeResponse(byte[] res) {
+ // RFC 3748 Section 4.1 Request and Response
+ private static byte[] createEapAkaChallengeResponse(byte[] res, byte identifier) {
// size = 8 (header) + resHeader (4) + res.length + AT_MAC (20 bytes)
byte[] message = new byte[32 + res.length];
// set up header
message[0] = CODE_RESPONSE;
- // Identifier need to same with request
- message[1] = mIdentifier;
- // length include entire EAP-AKA message
+ // identifier: same as request
+ message[1] = identifier;
+ // length: include entire EAP-AKA message
byte[] lengthBytes = BytesConverter.convertIntegerTo4Bytes(message.length);
message[2] = lengthBytes[2];
message[3] = lengthBytes[3];
message[4] = TYPE_EAP_AKA;
message[5] = SUBTYPE_AKA_CHALLENGE;
- // Reserved 2 bytes
+ // reserved: 2 bytes
message[6] = 0x00;
message[7] = 0x00;
@@ -330,9 +236,10 @@ public class EapAkaResponse {
// set up AT_RES, RFC 4187, Section 10.8 AT_RES
message[index++] = ATTRIBUTE_RES;
- // The length of the AT_RES attribute must be a multiple of 4 bytes which identifies the
- // exact length of the RES in bits. To pad 4 onto the length to ensure the reserved buffer
- // size large enough after convert to byte count.
+ // Length (in 4-bytes):
+ // The length of the RES should already be a multiple of 4 bytes.
+ // Add 4 to the attribute length to account for the attribute (1 byte), the length (1 byte),
+ // and the length of the RES in bits (2 bytes).
int resLength = (res.length + 4) / 4;
message[index++] = (byte) (resLength & 0xff);
// The value field of this attribute begins with the 2-byte RES Length, which identifies
@@ -343,9 +250,10 @@ public class EapAkaResponse {
System.arraycopy(res, 0, message, index, res.length);
index += res.length;
- // set up AT_MAC, RFC 4187, 10.15 AT_MAC
+ // set up AT_MAC, RFC 4187, Section 10.15 AT_MAC
message[index++] = ATTRIBUTE_MAC;
- // fixed length, 5*4 = 20
+ // length (in 4-bytes): 5, because MAC is 16 bytes, plus the attribute (1 byte),
+ // the length (1 byte), and reserved bytes (2 bytes).
message[index++] = 0x05;
// With two bytes reserved
message[index++] = 0x00;
@@ -354,9 +262,7 @@ public class EapAkaResponse {
// The MAC is calculated over the whole EAP packet and concatenated with optional
// message-specific data, with the exception that the value field of the
// MAC attribute is set to zero when calculating the MAC.
- for (int i = 0; i < 16; i++) {
- message[index++] = 0x00;
- }
+ Arrays.fill(message, index, index + 16, (byte) 0x00);
return message;
}
@@ -369,7 +275,7 @@ public class EapAkaResponse {
// 16 bytes.) The derivation of the authentication key (K_aut) used in
// the calculation of the MAC is specified in Section 7.
@Nullable
- private byte[] calculateMac(byte[] key, byte[] message) {
+ private static byte[] calculateMac(byte[] key, byte[] message) {
try {
Mac mac = Mac.getInstance(ALGORITHM_HMAC_SHA1);
SecretKeySpec secret = new SecretKeySpec(key, ALGORITHM_HMAC_SHA1);
@@ -381,8 +287,8 @@ public class EapAkaResponse {
return null;
}
- byte[] macValue = new byte[16];
- System.arraycopy(output, 0, macValue, 0, 16);
+ byte[] macValue = new byte[MAC_LENGTH];
+ System.arraycopy(output, 0, macValue, 0, MAC_LENGTH);
return macValue;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.e(TAG, "calculateMac failed!", e);
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java b/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java
index cdb9427..145773d 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java
@@ -18,10 +18,13 @@ package com.android.libraries.entitlement.eapaka;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE;
-import android.text.TextUtils;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.util.Base64;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.libraries.entitlement.ServiceEntitlementException;
/**
@@ -33,18 +36,20 @@ class EapAkaSecurityContext {
private static final String TAG = "ServiceEntitlement";
private static final byte RESPONSE_TAG_SUCCESS = (byte) 0xDB;
+ private static final byte RESPONSE_TAG_SYNC_FAILURE = (byte) 0xDC;
private boolean mValid;
- /* Authentication result from SIM */
+ // User response, populated on successful authentication
private byte[] mRes;
- /* Cipher Key */
+ // Cipher Key, populated on successful authentication
private byte[] mCk;
- /* Integrity Key */
+ // Integrity Key, populated on successful authentication
private byte[] mIk;
+ // AUTS, populated on synchronization failure
+ private byte[] mAuts;
- private EapAkaSecurityContext() {
- }
+ private EapAkaSecurityContext() {}
/**
* Provide {@link EapAkaSecurityContext} from response data.
@@ -65,56 +70,59 @@ class EapAkaSecurityContext {
* Parses SIM EAP-AKA Authentication responded data.
*/
private void parseResponseData(String response) {
- if (TextUtils.isEmpty(response)) {
- Log.d(TAG, "parseResponseData but input empty data!");
- return;
- }
+ byte[] data = null;
try {
- byte[] data = Base64.decode(response, Base64.DEFAULT);
- Log.d(TAG, "decoded data length=" + data.length);
-
- if (data.length <= 2) {
- return;
- }
-
- int index = 0;
-
- // check tag
- if (data[index] != RESPONSE_TAG_SUCCESS) {
- Log.d(TAG, "Not successful data, tag=" + data[index]);
- return;
- }
+ data = Base64.decode(response.getBytes(UTF_8), Base64.DEFAULT);
+ Log.d(TAG, "Decoded response data length = " + data.length + " bytes");
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Response is not a valid base-64 content");
+ return;
+ }
+ if (data.length == 0) {
+ return;
+ }
+ // Check tag, the initial byte
+ int index = 0;
+ if (data[index] == RESPONSE_TAG_SUCCESS) {
// Parse RES
index++; // move to RES length byte
mRes = parseTag(index, data);
if (mRes == null) {
- Log.d(TAG, "Invalid data! can't parse RES!");
+ Log.d(TAG, "Invalid data: can't parse RES!");
return;
}
// Parse CK
index += mRes.length + 1; // move to CK length byte
mCk = parseTag(index, data);
if (mCk == null) {
- Log.d(TAG, "Invalid data! can't parse CK!");
+ Log.d(TAG, "Invalid data: can't parse CK!");
return;
}
// Parse IK
index += mCk.length + 1; // move to IK length byte
mIk = parseTag(index, data);
if (mIk == null) {
- Log.d(TAG, "Invalid data! can't parse IK!");
+ Log.d(TAG, "Invalid data: can't parse IK!");
return;
}
-
mValid = true;
- } catch (IllegalArgumentException illegalArgumentException) {
- Log.e(TAG, "Invalid base-64 content");
+ } else if (data[index] == RESPONSE_TAG_SYNC_FAILURE) {
+ // Parse AUTS
+ index++; // move to AUTS length byte
+ mAuts = parseTag(index, data);
+ if (mAuts == null) {
+ Log.d(TAG, "Invalid data: can't parse AUTS!");
+ return;
+ }
+ mValid = true;
+ } else {
+ Log.d(TAG, "Not a valid tag, tag=" + data[index]);
+ return;
}
}
-
private byte[] parseTag(int index, byte[] src) {
// index at the length byte
if (index >= src.length) {
@@ -133,31 +141,39 @@ class EapAkaSecurityContext {
return dest;
}
- /**
- * Returns {@code valid}.
- */
- boolean isValid() {
+ private boolean isValid() {
return mValid;
}
/**
- * Returns {@code res}.
+ * Returns RES, or {@code null} for a synchronization failure.
*/
+ @Nullable
public byte[] getRes() {
return mRes;
}
/**
- * Returns {@code ck}.
+ * Returns CK, or {@code null} for a synchronization failure.
*/
+ @Nullable
public byte[] getCk() {
return mCk;
}
/**
- * Returns {@code ik}.
+ * Returns IK, or {@code null} for a synchronization failure.
*/
+ @Nullable
public byte[] getIk() {
return mIk;
}
+
+ /**
+ * Returns AUTS, or {@code null} for a successful authentication.
+ */
+ @Nullable
+ public byte[] getAuts() {
+ return mAuts;
+ }
}
diff --git a/java/com/android/libraries/entitlement/http/HttpClient.java b/java/com/android/libraries/entitlement/http/HttpClient.java
index 8476164..76eeaec 100644
--- a/java/com/android/libraries/entitlement/http/HttpClient.java
+++ b/java/com/android/libraries/entitlement/http/HttpClient.java
@@ -17,8 +17,8 @@
package com.android.libraries.entitlement.http;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS;
-import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_SEVER_NOT_CONNECTABLE;
-import static com.android.libraries.entitlement.ServiceEntitlementException.MALFORMED_HTTP_RESPONSE;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_SERVER_NOT_CONNECTABLE;
import static com.android.libraries.entitlement.http.HttpConstants.RequestMethod.POST;
import static com.android.libraries.entitlement.utils.DebugUtils.logPii;
@@ -107,7 +107,7 @@ public class HttpClient {
}
} catch (IOException ioe) {
throw new ServiceEntitlementException(
- ERROR_SEVER_NOT_CONNECTABLE, "Configure connection failed!", ioe);
+ ERROR_SERVER_NOT_CONNECTABLE, "Configure connection failed!", ioe);
}
}
@@ -143,7 +143,7 @@ public class HttpClient {
responseBuilder.setBody(responseBody);
} catch (IOException e) {
throw new ServiceEntitlementException(
- MALFORMED_HTTP_RESPONSE, "Read response body/message failed!", e);
+ ERROR_MALFORMED_HTTP_RESPONSE, "Read response body/message failed!", e);
}
return responseBuilder.build();
}
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
index 62297a7..cc80ea0 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
@@ -16,6 +16,11 @@
package com.android.libraries.entitlement.eapaka;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallengeTest.EAP_AKA_CHALLENGE_REQUEST;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallengeTest.EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED;
+import static com.android.libraries.entitlement.eapaka.EapAkaResponseTest.EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS;
+import static com.android.libraries.entitlement.eapaka.EapAkaResponseTest.EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
@@ -58,17 +63,10 @@ import org.mockito.junit.MockitoRule;
public class EapAkaApiTest {
private static final String TEST_URL = "https://test.url/test-path";
private static final String EAP_AKA_CHALLENGE =
- "{\"eap-relay-packet\":\""
- + "AQIAfBcBAAABBQAAXOZSkCjxysgE4"
- + "3GWqHJvgQIFAABrikWGrekAALNU4TxmCDPoCwUAAJT0nqXeAYlqzT0UGXINENWBBQAA7z3fhImk"
- + "q+vcCKWIZBdvuYIJAAAPRUFp7KWFo+Thr78Qj9hEkB2zA0i6KakODsufBC+BJQ==\"}";
+ "{\"eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}";
// com.google.common.net.HttpHeaders.COOKIE
private static final String HTTP_HEADER_COOKIE = "Cookie";
private static final String COOKIE_VALUE = "COOKIE=abcdefg";
- private static final String GSM_SECURITY_CONTEXT_REQUEST =
- "EFzmUpAo8crIBONxlqhyb4EQa4pFhq3pAACzVOE8Zggz6A==";
- private static final String GSM_SECURITY_CONTEXT_RESPONSE =
- "2wjHnwKln8mjjxDzMKJvLBzMHtm0X9SNBsUWEAbEiAdD7xeqqZ7nsXzukRkIhd6SDZ4bj7s=";
private static final String RESPONSE_XML =
"<wap-provisioningdoc version=\"1.1\">\n"
+ " <characteristic type=\"TOKEN\">\n"
@@ -115,11 +113,6 @@ public class EapAkaApiTest {
.thenReturn(mMockTelephonyManagerForSubId);
when(mMockTelephonyManagerForSubId.getSubscriberId()).thenReturn(IMSI);
when(mMockTelephonyManagerForSubId.getSimOperator()).thenReturn(MCCMNC);
- when(mMockTelephonyManagerForSubId.getIccAuthentication(
- TelephonyManager.APPTYPE_USIM,
- TelephonyManager.AUTHTYPE_EAP_AKA,
- GSM_SECURITY_CONTEXT_REQUEST))
- .thenReturn(GSM_SECURITY_CONTEXT_RESPONSE);
}
@Test
@@ -146,6 +139,11 @@ public class EapAkaApiTest {
@Test
public void queryEntitlementStatus_noAuthenticationToken() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
HttpResponse eapChallengeResponse =
HttpResponse
.builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
@@ -216,7 +214,7 @@ public class EapAkaApiTest {
request));
assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.MALFORMED_HTTP_RESPONSE);
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
assertThat(exception.getMessage()).isEqualTo("Unexpected http ContentType");
}
@@ -238,8 +236,44 @@ public class EapAkaApiTest {
request));
assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.MALFORMED_HTTP_RESPONSE);
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
assertThat(exception.getMessage()).isEqualTo("Failed to parse json object");
assertThat(exception.getCause()).isInstanceOf(JSONException.class);
}
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_handleEapAkaSyncFailure()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse
+ .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
+ .setCookie(COOKIE_VALUE).build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String response =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(response).isEqualTo(RESPONSE_XML);
+ // Verify that the 2nd/3rd request has cookie set by the 1st/2nd response
+ verify(mMockHttpClient, times(3)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsEntry(HTTP_HEADER_COOKIE, COOKIE_VALUE);
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).requestProperties())
+ .containsEntry(HTTP_HEADER_COOKIE, COOKIE_VALUE);
+ }
}
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaChallengeTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaChallengeTest.java
new file mode 100644
index 0000000..318c350
--- /dev/null
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaChallengeTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.eapaka;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.util.Base64;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EapAkaChallengeTest {
+ // EAP-AKA challenge request : AQIAfBcBAAABBQAAXOZSkCjxysgE43GWqHJvgQIFAABrikWGrekAALNU4TxmCDPo
+ // CwUAAJT0nqXeAYlqzT0UGXINENWBBQAA7z3fhImkq+vcCKWIZBdvuYIJAAAPRUFp
+ // 7KWFo+Thr78Qj9hEkB2zA0i6KakODsufBC+BJQ==
+ // Base64 decoded : 0102007C17010000010500005CE6529028F1CAC804E37196A8726F81020500006B8A4586ADE
+ // 90000B354E13C660833E80B05000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF
+ // 8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD844901DB3034
+ // 8BA29A90E0ECB9F042F8125
+ // HEADER
+ // Code : 0x01 (Request)
+ // Identifier : 0x02
+ // Length : 0x007C = 124
+ // Type : 0x17=23 (EAP-AKA)
+ // SubType : 0x01 = 1 (AKA Challenge)
+ // Reserved : 0x0000
+ //
+ // Type-DATA
+ //
+ // Attribute Type : 0x01 (AT_RAND)
+ // Length : 0x05 = 5's 4 bytes = 20 bytes
+ // Reserved : 0x0000
+ // RAND value : 0x5CE6529028F1CAC804E37196A8726F81
+ //
+ // Attribute Type : 0x02 (AT_AUTN)
+ // Length : 0x05 = 5's 4 bytes = 20 bytes
+ // Reserved : 0x0000
+ // AUTN value : 0x6B8A4586ADE90000B354E13C660833E8
+ //
+ // Attribute Type : 0x0B (AT_MAC)
+ // Length : 0x05 = 20 bytes
+ // Reserved : 0x0000
+ // MAC value : 0x94F49EA5DE01896ACD3D1419720D10D5
+ //
+ // Attribute Type : 0x81 (AT_IV)
+ // Length : 0x05
+ // Reserved : 0x0000
+ // IV value : 0xEF3DDF8489A4ABEBDC08A58864176FB9
+ //
+ // Attribute Type : 0x82 (AT_ENCR_DATA)
+ // Length : 0x09 = 36 bytes
+ // Reserved : 0x0000
+ // ENCR value : 0x0F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125
+ //
+ // Derived 3G security context for SIM Authentication request
+ // Hex : 105CE6529028F1CAC804E37196A8726F81106B8A4586ADE90000B354E13C660833E8
+ // Base64 encoded : EFzmUpAo8crIBONxlqhyb4EQa4pFhq3pAACzVOE8Zggz6A==
+ public static final String EAP_AKA_CHALLENGE_REQUEST =
+ "AQIAfBcBAAABBQAAXOZSkCjxysgE4"
+ + "3GWqHJvgQIFAABrikWGrekAALNU4TxmCDPoCwUAAJT0nqXeAYlqzT0UGXINENWBBQAA7z3fhImk"
+ + "g+vcCKWIZBdvuYIJAAAPRUFp7KWFo+Thr78Qj9hEkB2zA0i6KakODsufBC+BJQ==";
+ public static final String EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED =
+ "EFzmUpAo8crIBONxlqhyb4EQa4pFhq3pAACzVOE8Zggz6A==";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND =
+ "0102006817010000020500006"
+ + "B8A4586ADE90000B354E13C660833E80B05000094F49EA5DE01896ACD3D1419720D10D58105"
+ + "0000EF3DDF8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD84"
+ + "4901DB30348BA29A90E0ECB9F042F8125";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_AUTN =
+ "0102006817010000010500005"
+ + "CE6529028F1CAC804E37196A8726F810B05000094F49EA5DE01896ACD3D1419720D10D5810"
+ + "50000EF3DDF8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD"
+ + "844901DB30348BA29A90E0ECB9F042F8125";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND_AUTN =
+ "01020054170100000B05"
+ + "000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176F"
+ + "B9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_CODE_RESPONSE =
+ "02020054170100000B0"
+ + "5000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176F"
+ + "B9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH =
+ "01020099170100000B05"
+ + "000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB"
+ + "9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_EAP_TYPE_99 =
+ "01020054990100000B050"
+ + "00094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB9"
+ + "F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125820900000";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_SUBTYPE_99 =
+ "01020054179900000B0500"
+ + "0094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB98"
+ + "454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F812520900000F";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_LENGTH_OVER_DATA =
+ "0102001C17010000010600005CE6529028F1CAC804E37196A8726F81";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_RAND_LENGTH_16 =
+ "0102001817010000010400005CE6529028F1CAC804E37196";
+ private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_AUTN_LENGTH_16 =
+ "0102001817010000020400006B8A4586ADE90000B354E13C";
+
+ @Test
+ public void parseEapAkaChallengeRequest() throws Exception {
+ EapAkaChallenge message =
+ EapAkaChallenge.parseEapAkaChallenge(EAP_AKA_CHALLENGE_REQUEST);
+
+ assertThat(message.getIdentifier()).isEqualTo((byte) 0x02);
+ assertThat(message.getSimAuthenticationRequest())
+ .isEqualTo(EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED);
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withoutRand() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withoutAutn() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_AUTN);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withoutRandAndAutn() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND_AUTN);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withCodeResponse() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_CODE_RESPONSE);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withWrongLength() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withEapType99() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_EAP_TYPE_99);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withSubType99() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_SUBTYPE_99);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withLengthOverData() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_LENGTH_OVER_DATA);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withRandLength16() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_RAND_LENGTH_16);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_withAutnLength16() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_AUTN_LENGTH_16);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ @Test
+ public void parseEapAkaChallengeRequest_notValid_throwException() {
+ byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH);
+ String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+
+ assertThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaChallenge.parseEapAkaChallenge(encodedData));
+ }
+
+ private static byte[] convertHexStringToBytes(String input) {
+ return BaseEncoding.base16().decode(input);
+ }
+}
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaResponseTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaResponseTest.java
index 068346a..073a973 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaResponseTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaResponseTest.java
@@ -16,20 +16,24 @@
package com.android.libraries.entitlement.eapaka;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallengeTest.EAP_AKA_CHALLENGE_REQUEST;
+import static com.android.libraries.entitlement.eapaka.EapAkaChallengeTest.EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.fail;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.telephony.TelephonyManager;
import android.util.Base64;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
-import com.android.libraries.entitlement.ServiceEntitlementException;
-
import com.google.common.io.BaseEncoding;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,250 +43,93 @@ import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
public class EapAkaResponseTest {
- // EAP-AKA Challenge Request : AQIAfBcBAAABBQAAXOZSkCjxysgE43GWqHJvgQIFAABrikWGrekAALNU4TxmCDPo
- // CwUAAJT0nqXeAYlqzT0UGXINENWBBQAA7z3fhImkq+vcCKWIZBdvuYIJAAAPRUFp
- // 7KWFo+Thr78Qj9hEkB2zA0i6KakODsufBC+BJQ==
- // Base64 decoded : 0102007C17010000010500005CE6529028F1CAC804E37196A8726F81020500006B8A4586ADE
- // 90000B354E13C660833E80B05000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF
- // 8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD844901DB3034
- // 8BA29A90E0ECB9F042F8125
- // HEADER
- // Code : 0x01 (Request)
- // Identifier : 0x02
- // Length : 0x007C = 124
- // Type : 0x17=23 (EAP-AKA)
- // SubType : 0x01 = 1 (AKA Challenge)
- // Reserver : 0x0000
- //
- // Type-DATA
- //
- // Attribute Type : 0x01 (AT_RAND)
- // Length : 0x05 = 5's 4 bytes = 20 bytes
- // Reserved : 0x0000
- // RAND value : 0x5CE6529028F1CAC804E37196A8726F81
- //
- // Attribute Type : 0x02 (AT_AUTN)
- // Length : 0x05 = 5's 4 bytes = 20 bytes
- // Reserved : 0x0000
- // AUTN value : 0x6B8A4586ADE90000B354E13C660833E8
- //
- // Attribute Type : 0x0B (AT_MAC)
- // Length : 0x05 = 20 bytes
- // Reserved : 0x0000
- // MAC value : 0x94F49EA5DE01896ACD3D1419720D10D5
+ // EAP_AKA_CHALLENGE_REQUEST:
+ // Identifier :
+ // 0x02
+ // Derived 3G security context for SIM authentication request :
+ // EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED
+
+ // 3G security context for SIM authentication response
//
- // Attribute Type : 0x81 (AT_IV)
- // Length : 0x05
- // Reserved : 0x0000
- // IV value : 0xEF3DDF8489A4ABEBDC08A58864176FB9
+ // Success:
+ // Hex : DB08C79F02A59FC9A38F10F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17
+ // CEE91190885DE920D9E1B8FBB
+ // Base64 encoded : 2wjHnwKln8mjjxDzMKJvLBzMHtm0X9SNBsUWEAbEiAdD7xeqqZ7nsXzukRkIhd6SDZ4bj7s=
//
- // Attribute Type : 0x82 (AT_ENCR_DATA)
- // Length : 0x09 = 36 bytes
- // Reserved : 0x0000
- // ENCR value : 0x0F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125
- private static final String EAP_AKA_CHALLENGE_REQUEST_EXPECTED =
- "AQIAfBcBAAABBQAAXOZSkCjxysgE4"
- + "3GWqHJvgQIFAABrikWGrekAALNU4TxmCDPoCwUAAJT0nqXeAYlqzT0UGXINENWBBQAA7z3fhImk"
- + "g+vcCKWIZBdvuYIJAAAPRUFp7KWFo+Thr78Qj9hEkB2zA0i6KakODsufBC+BJQ==";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND =
- "0102006817010000020500006"
- + "B8A4586ADE90000B354E13C660833E80B05000094F49EA5DE01896ACD3D1419720D10D58105"
- + "0000EF3DDF8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD84"
- + "4901DB30348BA29A90E0ECB9F042F8125";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_AUTN =
- "0102006817010000010500005"
- + "CE6529028F1CAC804E37196A8726F810B05000094F49EA5DE01896ACD3D1419720D10D5810"
- + "50000EF3DDF8489A4ABEBDC08A58864176FB9820900000F454169ECA585A3E4E1AFBF108FD"
- + "844901DB30348BA29A90E0ECB9F042F8125";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND_AUTN =
- "01020054170100000B05"
- + "000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176F"
- + "B9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_CODE_RESPONSE =
- "02020054170100000B0"
- + "5000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176F"
- + "B9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH =
- "01020099170100000B05"
- + "000094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB"
- + "9820900000F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_EAP_TYPE_99 =
- "01020054990100000B050"
- + "00094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB9"
- + "F454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F8125820900000";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_SUBTYPE_99 =
- "01020054179900000B0500"
- + "0094F49EA5DE01896ACD3D1419720D10D581050000EF3DDF8489A4ABEBDC08A58864176FB98"
- + "454169ECA585A3E4E1AFBF108FD844901DB30348BA29A90E0ECB9F042F812520900000F";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_LENGTH_OVER_DATA =
- "0102001C17010000010600005CE6529028F1CAC804E37196A8726F81";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_RAND_LENGTH_16 =
- "0102001817010000010400005CE6529028F1CAC804E37196";
- private static final String EAP_AKA_CHALLENGE_REQUEST_WITH_AUTN_LENGTH_16 =
- "0102001817010000020400006B8A4586ADE90000B354E13C";
-
- // For calculate AKA challenge response
- private static final String RES = "C79F02A59FC9A38F";
- private static final String AUT = "4A2137E6E292679DD4C3FD8AB67F13DA";
- private static final String EXPECTED_AKA_CHALLENGE_RESPONSE =
+ // Synchronization failure:
+ // Hex : DC0E33D58D39619C213367E376115315
+ // Base64 encoded : 3A4z1Y05YZwhM2fjdhFTFQ==
+ public static final String EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS =
+ "2wjHnwKln8mjjxDzMKJvLBzMHtm0X9SNBsUWEAbEiAdD7xeqqZ7nsXzukRkIhd6SDZ4bj7s=";
+ public static final String EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE =
+ "3A4z1Y05YZwhM2fjdhFTFQ==";
+
+ // EAP-AKA challenge response
+ // Identifier : 0x02, same as in EAP_AKA_CHALLENGE_REQUEST
+ // Success
+ private static final String EAP_AKA_CHALLENGE_RESPONSE =
"020200281701000003030040C79F02A59FC9A38F0B0500001C141BDDDE8BEF2E502FB6793808DE7C";
- private static final int SUB_ID = 0;
-
- @Rule
- public final MockitoRule rule = MockitoJUnit.rule();
- @Mock
- Context mMockContext;
-
- @Test
- public void parseEapAkaChallengeRequest_withoutRand() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withoutAutn() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_AUTN);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withoutRandAndAutn() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITHOUT_RAND_AUTN);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withCodeResponse() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_CODE_RESPONSE);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withWrongLength() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
+ // Synchronization failure
+ private static final String EAP_AKA_CHALLENGE_SYNC_FAILURE =
+ "0202001817040000040433D58D39619C213367E376115315";
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withEapType99() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_EAP_TYPE_99);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withSubType99() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_SUBTYPE_99);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withLengthOverData() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_LENGTH_OVER_DATA);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withRandLength16() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_RAND_LENGTH_16);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
- }
-
- @Test
- public void parseEapAkaChallengeRequest_withAutnLength16() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_AUTN_LENGTH_16);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- assertThrows(
- ServiceEntitlementException.class,
- () -> message.getEapAkaChallengeResponse(mMockContext, SUB_ID));
+ private static final int SUB_ID = 0;
+ private static final String IMSI = "234107813240779";
+ private static final String MCCMNC = "23410";
+
+ @Rule public final MockitoRule rule = MockitoJUnit.rule();
+ @Mock private TelephonyManager mMockTelephonyManager;
+ @Mock private TelephonyManager mMockTelephonyManagerForSubId;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(TelephonyManager.class))
+ .thenReturn(mMockTelephonyManager);
+ when(mMockTelephonyManager.createForSubscriptionId(SUB_ID))
+ .thenReturn(mMockTelephonyManagerForSubId);
+ when(mMockTelephonyManagerForSubId.getSubscriberId()).thenReturn(IMSI);
+ when(mMockTelephonyManagerForSubId.getSimOperator()).thenReturn(MCCMNC);
}
@Test
- public void parseEapAkaChallengeRequest_notValid_throwException() {
- byte[] data = convertHexStringToBytes(EAP_AKA_CHALLENGE_REQUEST_WITH_WRONG_LENGTH);
- String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
-
- EapAkaResponse message = new EapAkaResponse(encodedData);
-
- try {
- message.getEapAkaChallengeResponse(mMockContext, SUB_ID);
- fail();
- } catch (ServiceEntitlementException exception) {
- assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE);
- assertThat(exception.getMessage()).isEqualTo("EAP-AKA Challenge message not valid!");
- }
+ public void generateEapAkaChallengeResponse_authSuccess() throws Exception {
+ EapAkaChallenge challenge = EapAkaChallenge.parseEapAkaChallenge(EAP_AKA_CHALLENGE_REQUEST);
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ String expectedResponse =
+ Base64.encodeToString(
+ convertHexStringToBytes(EAP_AKA_CHALLENGE_RESPONSE), Base64.NO_WRAP);
+
+ EapAkaResponse challengeResponse =
+ EapAkaResponse.respondToEapAkaChallenge(mContext, SUB_ID, challenge);
+
+ assertThat(challengeResponse.response()).isEqualTo(expectedResponse);
}
@Test
- public void generateEapAkaChallengeResponse_pass() {
- EapAkaResponse message = new EapAkaResponse(EAP_AKA_CHALLENGE_REQUEST_EXPECTED);
-
- byte[] challengeResponse =
- message.generateEapAkaChallengeResponse(
- convertHexStringToBytes(RES), convertHexStringToBytes(AUT));
-
- assertThat(challengeResponse)
- .isEqualTo(convertHexStringToBytes(EXPECTED_AKA_CHALLENGE_RESPONSE));
+ public void generateEapAkaChallengeResponse_syncFailure() throws Exception {
+ EapAkaChallenge challenge = EapAkaChallenge.parseEapAkaChallenge(EAP_AKA_CHALLENGE_REQUEST);
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE);
+ String expectedResponse =
+ Base64.encodeToString(
+ convertHexStringToBytes(EAP_AKA_CHALLENGE_SYNC_FAILURE), Base64.NO_WRAP);
+
+ EapAkaResponse challengeResponse =
+ EapAkaResponse.respondToEapAkaChallenge(mContext, SUB_ID, challenge);
+
+ assertThat(challengeResponse.synchronizationFailureResponse()).isEqualTo(expectedResponse);
}
- private byte[] convertHexStringToBytes(String input) {
+ private static byte[] convertHexStringToBytes(String input) {
return BaseEncoding.base16().decode(input);
}
}
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java
index 15c4c77..219737d 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java
@@ -19,7 +19,7 @@ package com.android.libraries.entitlement.eapaka;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.fail;
+import static org.testng.Assert.expectThrows;
import android.util.Base64;
@@ -51,10 +51,8 @@ public class EapAkaSecurityContextTest {
private static final String EXPECTED_IK = "06C4880743EF17AAA99EE7B17CEE9119";
private static final String EXPECTED_CK = "F330A26F2C1CCC1ED9B45FD48D06C516";
private static final String EXPECTED_RES = "C79F02A59FC9A38F";
- private static final String GSM_SECURITY_CONTEXT_RESPONSE_TAG_DC =
- "DC08C79F02A59FC9A38F10F330A"
- + "26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE91190885DE920D9E1B"
- + "8FBB";
+ // Invalid data
+ private static final String GSM_SECURITY_CONTEXT_RESPONSE_TAG_DE = "DE08C79F02A59FC9A38F";
private static final String GSM_SECURITY_CONTEXT_RESPONSE_INVALID_RES_LENGTH =
"DC40C79F02A59FC"
+ "9A38F10F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE911908"
@@ -74,13 +72,19 @@ public class EapAkaSecurityContextTest {
private static final String GSM_SECURITY_CONTEXT_RESPONSE_NO_KC_KEY =
"DB08C79F02A59FC9A38F10F3"
+ "30A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE9119";
+ // Base64 data : 3A4z1Y05YZwhM2fjdhFTFQ==
+ // RAW DATA : DC0E33D58D39619C213367E376115315
+ // TAG : DC
+ // AUTS length : 0E
+ // AUTS : 33D58D39619C213367E376115315
+ private static final String RESPONSE_SYNC_FAILURE = "3A4z1Y05YZwhM2fjdhFTFQ==";
+ private static final String EXPECTED_AUTS = "33D58D39619C213367E376115315";
@Test
public void parseResponseData_validResponse_pass() throws Exception {
EapAkaSecurityContext securityContext =
EapAkaSecurityContext.from(GSM_SECURITY_CONTEXT_RESPONSE);
- assertThat(securityContext.isValid()).isTrue();
assertThat(securityContext.getIk()).isEqualTo(convertHexStringToBytes(EXPECTED_IK));
assertThat(securityContext.getCk()).isEqualTo(convertHexStringToBytes(EXPECTED_CK));
assertThat(securityContext.getRes()).isEqualTo(convertHexStringToBytes(EXPECTED_RES));
@@ -88,18 +92,18 @@ public class EapAkaSecurityContextTest {
@Test
public void parseResponseData_invalidWithWrongTag_throwsException() {
- byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_TAG_DC);
+ byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_TAG_DE);
String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim();
- try {
- EapAkaSecurityContext.from(encodedData);
- fail();
- } catch (ServiceEntitlementException exception) {
- assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE);
- assertThat(exception.getMessage()).isEqualTo(
- "Invalid SIM EAP-AKA authentication response!");
- }
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () -> EapAkaSecurityContext.from(encodedData));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Invalid SIM EAP-AKA authentication response!");
}
@Test
@@ -137,7 +141,6 @@ public class EapAkaSecurityContextTest {
EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(encodedData);
- assertThat(securityContext.isValid()).isTrue();
assertThat(securityContext.getIk()).isEqualTo(convertHexStringToBytes(EXPECTED_IK));
assertThat(securityContext.getCk()).isEqualTo(convertHexStringToBytes(EXPECTED_CK));
assertThat(securityContext.getRes()).isEqualTo(convertHexStringToBytes(EXPECTED_RES));
@@ -150,13 +153,22 @@ public class EapAkaSecurityContextTest {
EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(encodedData);
- assertThat(securityContext.isValid()).isTrue();
assertThat(securityContext.getIk()).isEqualTo(convertHexStringToBytes(EXPECTED_IK));
assertThat(securityContext.getCk()).isEqualTo(convertHexStringToBytes(EXPECTED_CK));
assertThat(securityContext.getRes()).isEqualTo(convertHexStringToBytes(EXPECTED_RES));
}
+ @Test
+ public void parseResponseData_syncFailure() throws Exception {
+ EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(RESPONSE_SYNC_FAILURE);
+
+ assertThat(securityContext.getAuts()).isEqualTo(convertHexStringToBytes(EXPECTED_AUTS));
+ assertThat(securityContext.getRes()).isNull();
+ assertThat(securityContext.getCk()).isNull();
+ assertThat(securityContext.getIk()).isNull();
+ }
+
private byte[] convertHexStringToBytes(String input) {
return BaseEncoding.base16().decode(input);
}
-} \ No newline at end of file
+}