diff options
author | Kiwon Park <kiwonp@google.com> | 2023-03-18 00:04:44 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-03-18 00:04:44 +0000 |
commit | 9f6f01d27afb594593222c26272d00f1240f7789 (patch) | |
tree | d851a394fcc6429f9c9b2058cb776df65e71e16c | |
parent | d54d1cda6eae03dad31ff481e4434774cc43bc96 (diff) | |
parent | d6487bda8e2ec3bd64201f99896e406c1bd07c4c (diff) | |
download | service_entitlement-9f6f01d27afb594593222c26272d00f1240f7789.tar.gz |
Respond to second and third challenge during EAP-AKA. am: d6487bda8e
Original change: https://android-review.googlesource.com/c/platform/frameworks/libs/service_entitlement/+/2494696
Change-Id: I3e74094fb53b0cff2ca38c4733acfc823658e8f1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
3 files changed, 406 insertions, 40 deletions
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/android/libraries/entitlement/ServiceEntitlementException.java index 45b1b9b..b1cb50f 100644 --- a/java/com/android/libraries/entitlement/ServiceEntitlementException.java +++ b/java/com/android/libraries/entitlement/ServiceEntitlementException.java @@ -43,6 +43,11 @@ public class ServiceEntitlementException extends Exception { * synchronization" procedure as defined in RFC 4187. */ public static final int ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE = 21; + /** + * EAP-AKA failure that happens when the client fails to authenticate within the maximum number + * of attempts + */ + public static final int ERROR_EAP_AKA_FAILURE = 21; // HTTP related failures /** diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java index 2847c16..dc3c518 100644 --- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java +++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java @@ -16,6 +16,7 @@ package com.android.libraries.entitlement.eapaka; +import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_FAILURE; import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE; import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE; @@ -33,6 +34,7 @@ import com.android.libraries.entitlement.EsimOdsaOperation; import com.android.libraries.entitlement.ServiceEntitlementException; import com.android.libraries.entitlement.ServiceEntitlementRequest; import com.android.libraries.entitlement.http.HttpClient; +import com.android.libraries.entitlement.http.HttpConstants.ContentType; import com.android.libraries.entitlement.http.HttpConstants.RequestMethod; import com.android.libraries.entitlement.http.HttpRequest; import com.android.libraries.entitlement.http.HttpResponse; @@ -91,8 +93,9 @@ public class EapAkaApi { private static final String NETWORK_IDENTIFIER = "network_identifier"; - // 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; + // In case of EAP-AKA synchronization failure or another challenge, we try to authenticate for + // at most three times. + private static final int MAX_EAP_AKA_ATTEMPTS = 3; private final Context mContext; private final int mSimSubscriptionId; @@ -145,10 +148,17 @@ public class EapAkaApi { urlBuilder.toString(), carrierConfig, ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON); + String eapAkaChallenge = getEapAkaChallenge(challengeResponse); + if (eapAkaChallenge == null) { + throw new ServiceEntitlementException( + ERROR_MALFORMED_HTTP_RESPONSE, + "Failed to parse EAP-AKA challenge: " + challengeResponse.body()); + } return respondToEapAkaChallenge( carrierConfig, - challengeResponse, - FOLLOW_SYNC_FAILURE_MAX_COUNT, + eapAkaChallenge, + challengeResponse.cookies(), + MAX_EAP_AKA_ATTEMPTS, request.acceptContentType()) .body(); } @@ -158,62 +168,79 @@ public class EapAkaApi { * 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: + * <p>The {@code eapAkaChallenge} should be the 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). + * <li>The EAP-AKA response message, and the follow-up response should contain the service + * entitlement configuration, or another EAP-AKA challenge in which case the method calls + * if {@code remainingAttempts} is greater than zero (If {@code remainingAttempts} reaches + * 0, the method will throw ServiceEntitlementException) ; 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, as long as {@code remainingAttempts} is greater than zero. * </ul> * * @param response Challenge response from server which its content type is JSON */ private HttpResponse respondToEapAkaChallenge( CarrierConfig carrierConfig, - HttpResponse response, - int followSyncFailureCount, + String eapAkaChallenge, + ImmutableList<String> cookies, + int remainingAttempts, String contentType) throws ServiceEntitlementException { - 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); - } if (!mBypassEapAkaResponse.isEmpty()) { - return challengeResponse( - mBypassEapAkaResponse, - carrierConfig, - response.cookies(), - contentType); + return challengeResponse(mBypassEapAkaResponse, carrierConfig, cookies, contentType); } + 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.cookies(), - contentType); + // This could be a successful authentication, another challenge, or synchronization failure. + if (eapAkaResponse.response() != null) { + HttpResponse response = + challengeResponse( + eapAkaResponse.response(), carrierConfig, cookies, contentType); + String nextEapAkaChallenge = getEapAkaChallenge(response); + // successful authentication + if (nextEapAkaChallenge == null) { + return response; + } + // another challenge + Log.d(TAG, "Received another challenge"); + if (remainingAttempts > 0) { + return respondToEapAkaChallenge( + carrierConfig, + nextEapAkaChallenge, + cookies, + remainingAttempts - 1, + contentType); + } else { + throw new ServiceEntitlementException( + ERROR_EAP_AKA_FAILURE, "Unable to EAP-AKA authenticate"); + } } else if (eapAkaResponse.synchronizationFailureResponse() != null) { Log.d(TAG, "synchronization failure"); HttpResponse newChallenge = challengeResponse( eapAkaResponse.synchronizationFailureResponse(), carrierConfig, - response.cookies(), + cookies, ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON); - if (followSyncFailureCount > 0) { + String nextEapAkaChallenge = getEapAkaChallenge(newChallenge); + if (nextEapAkaChallenge == null) { + throw new ServiceEntitlementException( + ERROR_MALFORMED_HTTP_RESPONSE, + "Failed to parse EAP-AKA challenge: " + newChallenge.body()); + } + if (remainingAttempts > 0) { return respondToEapAkaChallenge( - carrierConfig, newChallenge, followSyncFailureCount - 1, contentType); + carrierConfig, + nextEapAkaChallenge, + cookies, + remainingAttempts - 1, + contentType); } else { throw new ServiceEntitlementException( ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE, @@ -281,10 +308,17 @@ public class EapAkaApi { urlBuilder.toString(), carrierConfig, ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON); + String eapAkaChallenge = getEapAkaChallenge(challengeResponse); + if (eapAkaChallenge == null) { + throw new ServiceEntitlementException( + ERROR_MALFORMED_HTTP_RESPONSE, + "Failed to parse EAP-AKA challenge: " + challengeResponse.body()); + } return respondToEapAkaChallenge( carrierConfig, - challengeResponse, - FOLLOW_SYNC_FAILURE_MAX_COUNT, + eapAkaChallenge, + challengeResponse.cookies(), + MAX_EAP_AKA_ATTEMPTS, request.acceptContentType()) .body(); } @@ -405,6 +439,30 @@ public class EapAkaApi { } } + @Nullable + private String getEapAkaChallenge(HttpResponse response) throws ServiceEntitlementException { + String eapAkaChallenge = null; + String responseBody = response.body(); + if (response.contentType() == ContentType.JSON) { + try { + eapAkaChallenge = + new JSONObject(responseBody).optString(EAP_CHALLENGE_RESPONSE, null); + } catch (JSONException jsonException) { + throw new ServiceEntitlementException( + ERROR_MALFORMED_HTTP_RESPONSE, + "Failed to parse json object", + jsonException); + } + } else if (response.contentType() == ContentType.XML) { + // TODO: possibly support parsing eap-relay-packet in XML format + return null; + } else { + throw new ServiceEntitlementException( + ERROR_MALFORMED_HTTP_RESPONSE, "Unknown HTTP content type"); + } + return eapAkaChallenge; + } + /** * Returns the IMSI EAP value. The resulting realm part of the Root NAI in 3GPP TS 23.003 clause * 19.3.2 will be in the form: diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java index 8e331f3..aff8f4b 100644 --- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java +++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java @@ -67,6 +67,8 @@ public class EapAkaApiTest { private static final String TEST_URL = "https://test.url/test-path"; private static final String EAP_AKA_CHALLENGE = "{\"eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}"; + private static final String INVALID_EAP_AKA_CHALLENGE = + "{\"invalid-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"; @@ -169,8 +171,137 @@ public class EapAkaApiTest { ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request); assertThat(respopnse).isEqualTo(RESPONSE_XML); - // Verify that the 2nd request has cookies set by the 1st response verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture()); + // Verify that the 2nd request has cookies set by the 1st response + assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties()) + .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE, + HTTP_HEADER_COOKIE, COOKIE_VALUE_1); + assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull(); + assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull(); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_invalidChallenge() 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(INVALID_EAP_AKA_CHALLENGE) + .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)) + .build(); + HttpResponse xmlResponse = + HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML) + .build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse).thenReturn(xmlResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + ServiceEntitlementException exception = + expectThrows( + ServiceEntitlementException.class, + () -> + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), + carrierConfig, + request)); + + assertThat(exception.getErrorCode()) + .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE); + assertThat(exception.getMessage()) + .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getHttpStatus()).isEqualTo(0); + assertThat(exception.getRetryAfter()).isEmpty(); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_secondChallenge() 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) + .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)) + .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 respopnse = + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request); + + assertThat(respopnse).isEqualTo(RESPONSE_XML); + // Verify that the subsequent requests have cookies set by the 1st response + verify(mMockHttpClient, times(3)).request(mHttpRequestCaptor.capture()); + assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties()) + .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE, + HTTP_HEADER_COOKIE, COOKIE_VALUE_1); + assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull(); + assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull(); + assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull(); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_thirdChallenge() 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) + .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)) + .build(); + HttpResponse xmlResponse = + HttpResponse.builder() + .setContentType(ContentType.XML) + .setBody(RESPONSE_XML) + .build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(xmlResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + String respopnse = + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request); + + assertThat(respopnse).isEqualTo(RESPONSE_XML); + // Verify that the subsequent requests have cookies set by the 1st response + verify(mMockHttpClient, times(4)).request(mHttpRequestCaptor.capture()); assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties()) .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE, HTTP_HEADER_COOKIE, COOKIE_VALUE_1); @@ -180,6 +311,51 @@ public class EapAkaApiTest { assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec()) .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull(); + assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull(); + assertThat(mHttpRequestCaptor.getAllValues().get(3).timeoutInSec()) + .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); + assertThat(mHttpRequestCaptor.getAllValues().get(3).network()).isNull(); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_fourthChallenge_throwException() + 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) + .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)) + .build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + ServiceEntitlementException exception = + expectThrows( + ServiceEntitlementException.class, + () -> + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), + carrierConfig, + request)); + + assertThat(exception.getErrorCode()) + .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_FAILURE); + assertThat(exception.getMessage()).isEqualTo("Unable to EAP-AKA authenticate"); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getHttpStatus()).isEqualTo(0); + assertThat(exception.getRetryAfter()).isEmpty(); } @Test @@ -266,6 +442,89 @@ public class EapAkaApiTest { } @Test + public void queryEntitlementStatus_noAuthenticationToken_eapAkaSyncFailure_invalidChallenge() + 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); + HttpResponse eapChallengeResponse = + HttpResponse + .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE) + .setCookies(ImmutableList.of(COOKIE_VALUE)).build(); + HttpResponse invalidEapChallengeResponse = + HttpResponse.builder() + .setContentType(ContentType.JSON) + .setBody(INVALID_EAP_AKA_CHALLENGE) + .setCookies(ImmutableList.of(COOKIE_VALUE)) + .build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse) + .thenReturn(invalidEapChallengeResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + ServiceEntitlementException exception = + expectThrows( + ServiceEntitlementException.class, + () -> + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), + carrierConfig, + request)); + + assertThat(exception.getErrorCode()) + .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE); + assertThat(exception.getMessage()) + .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getHttpStatus()).isEqualTo(0); + assertThat(exception.getRetryAfter()).isEmpty(); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_fourthEapAkaSyncFailure() + 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_SYNC_FAILURE) + .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE) + .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE); + HttpResponse eapChallengeResponse = + HttpResponse + .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE) + .setCookies(ImmutableList.of(COOKIE_VALUE)).build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse) + .thenReturn(eapChallengeResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + ServiceEntitlementException exception = + expectThrows( + ServiceEntitlementException.class, + () -> + mEapAkaApi.queryEntitlementStatus( + ImmutableList.of(ServiceEntitlement.APP_VOWIFI), + carrierConfig, + request)); + + assertThat(exception.getErrorCode()) + .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE); + assertThat(exception.getMessage()) + .isEqualTo("Unable to recover from EAP-AKA synchroinization failure"); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getHttpStatus()).isEqualTo(0); + assertThat(exception.getRetryAfter()).isEmpty(); + } + + @Test public void queryEntitlementStatus_hasNoAuthenticationToken_bypassAuthentication() throws Exception { HttpResponse eapChallengeResponse = @@ -398,4 +657,48 @@ public class EapAkaApiTest { assertThat(response).isEqualTo(RESPONSE_XML); verify(mMockHttpClient, times(1)).request(any()); } + + @Test + public void performEsimOdsaOperation_noAuthenticationToken_invalidChallenge() 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(INVALID_EAP_AKA_CHALLENGE) + .setCookies(ImmutableList.of(COOKIE_VALUE)) + .build(); + HttpResponse xmlResponse = + HttpResponse.builder() + .setContentType(ContentType.XML) + .setBody(RESPONSE_XML) + .build(); + when(mMockHttpClient.request(any())) + .thenReturn(eapChallengeResponse) + .thenReturn(xmlResponse); + CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + EsimOdsaOperation operation = EsimOdsaOperation.builder().build(); + + ServiceEntitlementException exception = + expectThrows( + ServiceEntitlementException.class, + () -> + mEapAkaApi.performEsimOdsaOperation( + ServiceEntitlement.APP_ODSA_COMPANION, + carrierConfig, + request, + operation)); + + assertThat(exception.getErrorCode()) + .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE); + assertThat(exception.getMessage()) + .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getHttpStatus()).isEqualTo(0); + assertThat(exception.getRetryAfter()).isEmpty(); + } } |