diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-01-20 02:10:09 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-01-20 02:10:09 +0000 |
commit | 9f160604a2fd31811a1dcce06b5402f4335ee8af (patch) | |
tree | 361cea2304fa6b365422eeac4061360a79c30a13 | |
parent | b5e74658f8b8668d58bba3762594c2131dab1c88 (diff) | |
parent | 9aec82c7937ac1ccf2365da970b7e8023591321f (diff) | |
download | service_entitlement-9f160604a2fd31811a1dcce06b5402f4335ee8af.tar.gz |
Snap for 7090944 from 9aec82c7937ac1ccf2365da970b7e8023591321f to sc-d1-release
Change-Id: Idb6bb95c68ba14956cc44d965bd642ae2fbd9319
11 files changed, 740 insertions, 10 deletions
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlement.java b/java/com/android/libraries/entitlement/ServiceEntitlement.java index c196783..abf6ace 100644 --- a/java/com/android/libraries/entitlement/ServiceEntitlement.java +++ b/java/com/android/libraries/entitlement/ServiceEntitlement.java @@ -20,6 +20,8 @@ import android.content.Context; import com.android.libraries.entitlement.eapaka.EapAkaApi; +import com.google.common.annotations.VisibleForTesting; + import java.util.List; import androidx.annotation.Nullable; @@ -41,8 +43,8 @@ public class ServiceEntitlement { public static final String APP_ODSA_PRIMARY = "ap2009"; private final Context context; - private final int simSubscriptionId; private final CarrierConfig carrierConfig; + private final EapAkaApi eapAkaApi; /** * Creates an instance for service entitlement configuration query and operation for the @@ -58,8 +60,15 @@ public class ServiceEntitlement { */ public ServiceEntitlement(Context context, CarrierConfig carrierConfig, int simSubscriptionId) { this.context = context; - this.simSubscriptionId = simSubscriptionId; this.carrierConfig = carrierConfig; + this.eapAkaApi = new EapAkaApi(context, simSubscriptionId); + } + + @VisibleForTesting + ServiceEntitlement(Context context, CarrierConfig carrierConfig, EapAkaApi eapAkaApi) { + this.context = context; + this.carrierConfig = carrierConfig; + this.eapAkaApi = eapAkaApi; } /** @@ -105,7 +114,6 @@ public class ServiceEntitlement { @Nullable public String queryEntitlementStatus(String appId, ServiceEntitlementRequest request) throws ServiceEntitlementException { - EapAkaApi eapAkaApi = new EapAkaApi(context, simSubscriptionId); return eapAkaApi.queryEntitlementStatus(appId, carrierConfig.serverUrl(), request); } diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java index 898b782..b9eaadd 100644 --- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java +++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java @@ -35,6 +35,7 @@ import org.json.JSONObject; import androidx.annotation.Nullable; +import com.google.common.annotations.VisibleForTesting; import com.google.common.net.HttpHeaders; import java.net.CookieHandler; @@ -86,6 +87,13 @@ public class EapAkaApi { this.httpClient = new HttpClient(); } + @VisibleForTesting + EapAkaApi(Context context, int simSubscriptionId, HttpClient httpClient) { + this.context = context; + this.simSubscriptionId = simSubscriptionId; + this.httpClient = httpClient; + } + /** * Retrieves raw entitlement configuration doc though EAP-AKA authentication. * @@ -160,11 +168,12 @@ public class EapAkaApi { return response.body(); } - private String entitlementStatusUrl( + @VisibleForTesting + String entitlementStatusUrl( String appId, String serverUrl, ServiceEntitlementRequest request) { TelephonyManager telephonyManager = - context.getSystemService(TelephonyManager.class).createForSubscriptionId( - simSubscriptionId); + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager = telephonyManager.createForSubscriptionId(simSubscriptionId); Uri.Builder urlBuilder = Uri.parse(serverUrl).buildUpon(); if (TextUtils.isEmpty(request.authenticationToken())) { // EAP_ID required for initial AuthN diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java index 5021317..8f4f138 100644 --- a/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java +++ b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java @@ -25,6 +25,8 @@ import android.util.Log; import com.android.libraries.entitlement.ServiceEntitlementException; import com.android.libraries.entitlement.eapaka.utils.BytesConverter; +import com.google.common.annotations.VisibleForTesting; + import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -73,6 +75,9 @@ class EapAkaResponse { private boolean valid; + @VisibleForTesting + static String challengeResponseForTesting; + public EapAkaResponse(String eapAkaChallenge) { try { parseEapAkaChallengeRequest(eapAkaChallenge); @@ -201,6 +206,10 @@ class EapAkaResponse { */ public String getEapAkaChallengeResponse(Context context, int simSubscriptionId) throws ServiceEntitlementException { + if(challengeResponseForTesting != null) { + return challengeResponseForTesting; + } + if (!valid) { throw new ServiceEntitlementException("EAP-AKA Challenge message not valid!"); } diff --git a/java/com/android/libraries/entitlement/eapaka/MasterKey.java b/java/com/android/libraries/entitlement/eapaka/MasterKey.java index 652fa65..57b8ab3 100644 --- a/java/com/android/libraries/entitlement/eapaka/MasterKey.java +++ b/java/com/android/libraries/entitlement/eapaka/MasterKey.java @@ -93,7 +93,7 @@ class MasterKey { messageDigest.update(data); masterKey = messageDigest.digest(); } catch (NoSuchAlgorithmException e) { - Log.d(TAG, "process sHA-1 failed", e); + Log.d(TAG, "process SHA-1 failed", e); } // Generate TEKs diff --git a/java/com/android/libraries/entitlement/testing/FakeURLStreamHandler.java b/java/com/android/libraries/entitlement/testing/FakeURLStreamHandler.java new file mode 100644 index 0000000..0763747 --- /dev/null +++ b/java/com/android/libraries/entitlement/testing/FakeURLStreamHandler.java @@ -0,0 +1,176 @@ +/* + * 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.testing; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import javax.net.ssl.HttpsURLConnection; + +/** + * Fakes {@linkplain java.net.URLStreamHandler} which is used to set URLStreamHandlerFactory for URL + * as {@linkplain java.net.URL} is a final class and cannot be mocked using mockito. + */ +public class FakeURLStreamHandler extends URLStreamHandler implements URLStreamHandlerFactory { + + private Map<String, FakeResponse> response; + + private static final String ACCESS_TOKEN = "8dGozfI6%2FEaSsE7LaTfJKwdy"; + private static final String LOCATION = "Location"; + private static final String CONTENT_TYPE = "Content-Type"; + + @Override + public URLConnection openConnection(URL u) { + FakeHttpsURLConnection connection = new FakeHttpsURLConnection(u); + return connection; + } + + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + return this; + } + + public FakeURLStreamHandler stubResponse(Map<String, FakeResponse> response) { + this.response = response; + return this; + } + + /** Fakes {@linkplain java.net.HttpURLConnection} to avoid making any network connection. */ + public class FakeHttpsURLConnection extends HttpsURLConnection { + + public ByteArrayOutputStream outputStream; + + private final String urlString; + + protected FakeHttpsURLConnection(URL url) { + super(url); + this.urlString = url.toString(); + } + + @Override + public InputStream getInputStream() throws IOException { + InputStream inputStream = new ByteArrayInputStream(response.get(urlString).responseBody()); + if (inputStream == null) { + throw new IOException(); + } + return inputStream; + } + + @Override + public OutputStream getOutputStream() { + outputStream = new ByteArrayOutputStream(); + return outputStream; + } + + @Override + public int getResponseCode() { + return response.get(urlString).responseCode(); + } + + @Override + public Map<String, List<String>> getHeaderFields() { + List<String> locationList = new ArrayList<>(); + locationList.add("access_token=" + ACCESS_TOKEN); + return ImmutableMap.of("Location", locationList); + } + + @Override + public String getHeaderField(String name) { + switch (name) { + case LOCATION: + return "Location: " + response.get(urlString).responseLocation(); + case CONTENT_TYPE: + return response.get(urlString).contentType(); + default: + return null; + } + } + + @Override + public void connect() {} + + @Override + public void disconnect() {} + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public String getCipherSuite() { + return null; + } + + @Override + public Certificate[] getLocalCertificates() { + return null; + } + + @Override + public Certificate[] getServerCertificates() { + return null; + } + } + + @AutoValue + public abstract static class FakeResponse { + public abstract int responseCode(); + + @Nullable + public abstract String responseLocation(); + + @SuppressWarnings("mutable") // For test only + public abstract byte[] responseBody(); + + public abstract String contentType(); + + public static Builder builder() { + return new AutoValue_FakeURLStreamHandler_FakeResponse.Builder() + .setResponseBody(new byte[] {}) + .setContentType("") + .setResponseCode(0) + .setResponseLocation(""); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setResponseCode(int value); + + public abstract Builder setResponseLocation(String value); + + public abstract Builder setResponseBody(byte[] value); + + public abstract Builder setContentType(String contentType); + + public abstract FakeResponse build(); + } + } +}
\ No newline at end of file diff --git a/tests/Android.bp b/tests/Android.bp index 82b7b4d..10a044b 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -24,6 +24,7 @@ android_test { "androidx.test.rules", "mockito-target-minus-junit4", "platform-test-annotations", + "service-entitlement", "testables", "testng", "truth-prebuilt", diff --git a/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java b/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java index 2619036..af18e1e 100644 --- a/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java +++ b/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java @@ -18,16 +18,59 @@ package com.android.libraries.entitlement; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import android.content.Context; + import androidx.test.runner.AndroidJUnit4; +import com.android.libraries.entitlement.eapaka.EapAkaApi; + +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; -// TODO(b/173450048) add tests @RunWith(AndroidJUnit4.class) public class ServiceEntitlementTest { + private static final String QUERY_APP_VOLTE_RESULT = "QUERY_APP_VOLTE_RESULT"; + private static final String QUERY_APP_VOWIFI_RESULT = "QUERY_APP_VOWIFI_RESULT"; + private static final String TEST_URL = "https://test.url"; + + @Rule public final MockitoRule rule = MockitoJUnit.rule(); + @Mock Context mockContext; + CarrierConfig carrierConfig; + @Mock EapAkaApi mockEapAkaApi; + + private ServiceEntitlement serviceEntitlement; + + @Before + public void setUp() { + carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build(); + serviceEntitlement = new ServiceEntitlement(mockContext, carrierConfig, mockEapAkaApi); + } + + @Test + public void queryEntitlementStatus_appVolte_returnResult() throws Exception { + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + when(mockEapAkaApi.queryEntitlementStatus(ServiceEntitlement.APP_VOLTE, TEST_URL, request)) + .thenReturn(QUERY_APP_VOLTE_RESULT); + + assertThat(serviceEntitlement.queryEntitlementStatus(ServiceEntitlement.APP_VOLTE, request)) + .isEqualTo(QUERY_APP_VOLTE_RESULT); + } + @Test - public void test() { - assertThat(1).isEqualTo(1); + public void queryEntitlementStatus_appVowifi_returnResult() throws Exception { + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + when(mockEapAkaApi.queryEntitlementStatus(ServiceEntitlement.APP_VOWIFI, TEST_URL, request)) + .thenReturn(QUERY_APP_VOWIFI_RESULT); + + assertThat( + serviceEntitlement.queryEntitlementStatus(ServiceEntitlement.APP_VOWIFI, request)) + .isEqualTo(QUERY_APP_VOWIFI_RESULT); } } diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java new file mode 100644 index 0000000..ea18be8 --- /dev/null +++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java @@ -0,0 +1,140 @@ +/* + * 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.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +import com.android.libraries.entitlement.ServiceEntitlement; +import com.android.libraries.entitlement.ServiceEntitlementRequest; +import com.android.libraries.entitlement.http.HttpClient; +import com.android.libraries.entitlement.http.HttpResponse; +import com.android.libraries.entitlement.http.HttpConstants.ContentType; + +import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION; + +import android.telephony.TelephonyManager; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +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\"}"; + private static final String EAP_AKA_CHALLENGE_RESPONSE = "EAP_AKA_CHALLENGE_RESPONSE"; + private static final String RESPONSE_XML = + "<wap-provisioningdoc version=\"1.1\">\n" + + " <characteristic type=\"TOKEN\">\n" + + " <parm name=\"token\" value=\"kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX\"/>\n" + + " <parm name=\"validity\" value=\"3600\"/>\n" + + " </characteristic>\n" + + " <characteristic type=\"APPLICATION\">\n" + + " <parm name=\"EntitlementStatus\" value=\"0\"/>\n" + + " <parm name=\"AddrStatus\" value=\"0\"/>\n" + + " <parm name=\"TC_Status\" value=\"2\"/>\n" + + " <parm name=\"ProvStatus\" value=\"2\"/>\n" + + " <parm name=\"ServiceFlow_URL\"" + + " value=\"http://vm-host:8180/self-prov-websheet/rcs\"/>\n" + + " <parm name=\"ServiceFlow_UserData\"" + + " value=\"token=test_token\"/>\n" + + " </characteristic>\n" + + "</wap-provisioningdoc>\n"; + private static final String TOKEN = "kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX"; + private static final String IMSI = "TEST_IMSI"; + private static final String IMEI = "TEST_IMEI"; + private static final String MCCMNC = "10010"; + private static final String ENTITLEMENT_URL_WITH_TOKEN = + TEST_URL + + "?IMSI=" + + IMSI + + "&token=" + + TOKEN + + "&terminal_id=" + + IMEI + + "&terminal_vendor=" + + Build.MANUFACTURER + + "&terminal_model=" + + Build.MODEL + + "&terminal_sw_version=" + + VERSION.BASE_OS + + "&app=" + + ServiceEntitlement.APP_VOWIFI + + "&vers=0" + + "&entitlement_version=2.0"; + private static final int SUB_ID = 0; + + @Rule public final MockitoRule rule = MockitoJUnit.rule(); + @Mock Context mockContext; + @Mock HttpClient mockHttpClient; + @Mock TelephonyManager mockTelephonyManager; + @Mock TelephonyManager mockTelephonyManagerForSubId; + + private EapAkaApi eapAkaApi; + + @Before + public void setUp() { + eapAkaApi = new EapAkaApi(mockContext, SUB_ID, mockHttpClient); + when(mockContext.getSystemService(Context.TELEPHONY_SERVICE)) + .thenReturn(mockTelephonyManager); + when(mockTelephonyManager.createForSubscriptionId(SUB_ID)) + .thenReturn(mockTelephonyManagerForSubId); + } + + @Test + public void queryEntitlementStatus_hasAuthenticationToken_fastAuthN() throws Exception { + HttpResponse response = + HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML).build(); + when(mockHttpClient.request(any())).thenReturn(response); + + ServiceEntitlementRequest request = + ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build(); + + assertThat(eapAkaApi.queryEntitlementStatus( + ServiceEntitlement.APP_VOWIFI, TEST_URL, request)) + .isEqualTo(RESPONSE_XML); + } + + @Test + public void queryEntitlementStatus_noAuthenticationToken_initialAuthN() throws Exception { + HttpResponse eapChallengeResponse = + HttpResponse + .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE).build(); + HttpResponse xmlResponse = + HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML).build(); + when(mockHttpClient.request(any())) + .thenReturn(eapChallengeResponse).thenReturn(xmlResponse); + EapAkaResponse.challengeResponseForTesting = EAP_AKA_CHALLENGE_RESPONSE; + + ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build(); + + assertThat( + eapAkaApi.queryEntitlementStatus(ServiceEntitlement.APP_VOWIFI, TEST_URL, request)) + .isEqualTo(RESPONSE_XML); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java new file mode 100644 index 0000000..2ca7a6a --- /dev/null +++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaSecurityContextTest.java @@ -0,0 +1,153 @@ +/* + * 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 EapAkaSecurityContextTest { + // Base64 data : 2wjHnwKln8mjjxDzMKJvLBzMHtm0X9SNBsUWEAbEiAdD7xeqqZ7nsXzukRkIhd6SDZ4bj7s= + // RAW DATA : DB08C79F02A59FC9A38F10F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17 + // CEE91190885DE920D9E1B8FBB + // TAG : DB + // RES length : 08 + // RES : C79F02A59FC9A38F + // CK Length : 10 + // CK : F330A26F2C1CCC1ED9B45FD48D06C516 + // IK Length : 10 + // IK : 06C4880743EF17AAA99EE7B17CEE9119 + // KC Length : 08 + // KC : 85DE920D9E1B8FBB + private static final String GSM_SECURITY_CONTEXT_RESPONSE = + "2wjHnwKln8mjjxDzMKJvLBzMHtm0X9SNBsUWEAbEiAdD7xeqqZ7nsXzukRkIhd6SDZ4bj7s="; + 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" + + "26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE91190885DE920D9E1B8FBB"; + private static final String GSM_SECURITY_CONTEXT_RESPONSE_INVALID_RES_LENGTH = + "DC40C79F02A59FC" + + "9A38F10F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE91190885DE920D9" + + "E1B8FBB"; + private static final String GSM_SECURITY_CONTEXT_RESPONSE_INVALID_CK_LENGTH = + "DB08C79F02A59FC9" + + "A38F40F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE91190885DE920D9E" + + "1B8FBB"; + private static final String GSM_SECURITY_CONTEXT_RESPONSE_INVALID_IK_LENGTH = + "DB08C79F02A59FC9" + + "A38F10F330A26F2C1CCC1ED9B45FD48D06C5164006C4880743EF17AAA99EE7B17CEE91190885DE920D9E" + + "1B8FBB"; + private static final String GSM_SECURITY_CONTEXT_RESPONSE_INVALID_KC_LENGTH = + "DB08C79F02A59FC9" + + "A38F10F330A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE91191085DE920D9E" + + "1B8FBB"; + private static final String GSM_SECURITY_CONTEXT_RESPONSE_NO_KC_KEY = + "DB08C79F02A59FC9A38F10F3" + + "30A26F2C1CCC1ED9B45FD48D06C5161006C4880743EF17AAA99EE7B17CEE9119"; + + @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)); + } + + @Test + public void parseResponseData_invalidWithWrongTag_throwsException() { + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_TAG_DC); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + assertThrows( + ServiceEntitlementException.class, () -> EapAkaSecurityContext.from(encodedData)); + } + + @Test + public void parseResponseData_invalidWithWrongResLength_throwsException() { + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_INVALID_RES_LENGTH); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + assertThrows( + ServiceEntitlementException.class, () -> EapAkaSecurityContext.from(encodedData)); + } + + @Test + public void parseResponseData_invalidWithWrongCkLength_throwsException() { + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_INVALID_CK_LENGTH); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + assertThrows( + ServiceEntitlementException.class, () -> EapAkaSecurityContext.from(encodedData)); + } + + @Test + public void parseResponseData_invalidWithWrongIkLength_throwsException() { + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_INVALID_IK_LENGTH); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + assertThrows( + ServiceEntitlementException.class, () -> EapAkaSecurityContext.from(encodedData)); + } + + @Test + public void parseResponseData_validWithWrongKcLength() throws Exception { + // Because we don't parse KC, invalid KC length doesn't hurt + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_INVALID_KC_LENGTH); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + 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_noKcKey() throws Exception { + byte[] data = convertHexStringToBytes(GSM_SECURITY_CONTEXT_RESPONSE_NO_KC_KEY); + String encodedData = Base64.encodeToString(data, Base64.NO_WRAP).trim(); + + 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)); + } + + private byte[] convertHexStringToBytes(String input) { + return BaseEncoding.base16().decode(input); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/libraries/entitlement/eapaka/MasterKeyTest.java b/tests/src/com/android/libraries/entitlement/eapaka/MasterKeyTest.java new file mode 100644 index 0000000..7caa8fa --- /dev/null +++ b/tests/src/com/android/libraries/entitlement/eapaka/MasterKeyTest.java @@ -0,0 +1,82 @@ +/* + * 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 androidx.test.runner.AndroidJUnit4; + +import com.google.common.io.BaseEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class MasterKeyTest { + private static final String EXPECTED_IK = "06C4880743EF17AAA99EE7B17CEE9119"; + private static final String EXPECTED_CK = "F330A26F2C1CCC1ED9B45FD48D06C516"; + private static final String IMSI_EAP = "0234107813240779@nai.epc.mnc010.mcc234.3gppnetwork.org"; + private static final String EXPECTED_AUT = "4A2137E6E292679DD4C3FD8AB67F13DA"; + + @Test + public void generateTransientEapKeys_getAutPassed() throws Exception { + MasterKey masterKey = + MasterKey.create( + IMSI_EAP, + convertHexStringToBytes(EXPECTED_IK), + convertHexStringToBytes(EXPECTED_CK)); + + assertThat(masterKey.getAut()).isEqualTo(convertHexStringToBytes(EXPECTED_AUT)); + } + + @Test + public void generateTransientEapKeys_withoutImsiEap_getNull() throws Exception { + MasterKey masterKey = + MasterKey.create( + null, + convertHexStringToBytes(EXPECTED_IK), + convertHexStringToBytes(EXPECTED_CK)); + + assertThat(masterKey).isNull(); + } + + @Test + public void generateTransientEapKeys_withoutIk_getNull() throws Exception { + MasterKey masterKey = + MasterKey.create( + IMSI_EAP, + null, + convertHexStringToBytes(EXPECTED_CK)); + + assertThat(masterKey).isNull(); + } + + @Test + public void generateTransientEapKeys_withoutCk_getNull() throws Exception { + MasterKey masterKey = + MasterKey.create( + IMSI_EAP, + convertHexStringToBytes(EXPECTED_IK), + null); + + assertThat(masterKey).isNull(); + } + + private byte[] convertHexStringToBytes(String input) { + return BaseEncoding.base16().decode(input); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java b/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java new file mode 100644 index 0000000..77461f3 --- /dev/null +++ b/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java @@ -0,0 +1,109 @@ +/* + * 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.http; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.android.libraries.entitlement.http.HttpClient; +import com.android.libraries.entitlement.http.HttpConstants.ContentType; +import com.android.libraries.entitlement.http.HttpConstants.RequestMethod; + +import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION; + +import android.telephony.TelephonyManager; + +import com.android.libraries.entitlement.ServiceEntitlementException; +import com.android.libraries.entitlement.testing.FakeURLStreamHandler; +import com.android.libraries.entitlement.testing.FakeURLStreamHandler.FakeResponse; + +import com.google.common.collect.ImmutableMap; + +import androidx.test.runner.AndroidJUnit4; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class HttpClientTest { + private static final String TEST_URL = "https://test.url"; + private static final String TEST_RESPONSE_BODY = "TEST_RESPONSE_BODY"; + private static final String CONTENT_TYPE_STRING_JSON = "application/json"; + + private static FakeURLStreamHandler fakeURLStreamHandler; + + private final HttpClient httpClient = new HttpClient(); + + @BeforeClass + public static void setupURLStreamHandlerFactory() { + fakeURLStreamHandler = new FakeURLStreamHandler(); + URL.setURLStreamHandlerFactory(fakeURLStreamHandler); + } + + @Test + public void request_contentTypeXml_returnsXmlBody() throws Exception { + FakeResponse responseContent = + FakeResponse.builder() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setResponseLocation(null) + .setResponseBody(TEST_RESPONSE_BODY.getBytes(UTF_8)) + .setContentType(CONTENT_TYPE_STRING_JSON) + .build(); + HttpRequest request = + HttpRequest.builder().setUrl(TEST_URL).setRequestMethod(RequestMethod.GET).build(); + Map<String, FakeResponse> response = ImmutableMap.of(TEST_URL, responseContent); + fakeURLStreamHandler.stubResponse(response); + + HttpResponse httpResponse = httpClient.request(request); + + assertThat(httpResponse.contentType()).isEqualTo(ContentType.JSON); + assertThat(httpResponse.body()).isEqualTo(TEST_RESPONSE_BODY); + assertThat(httpResponse.responseCode()).isEqualTo(HttpURLConnection.HTTP_OK); + } + + @Test + public void request_httpGetResponseBadRequest_throwsException() { + FakeResponse responseContent = + FakeResponse.builder() + .setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST) + .setResponseLocation(null) + .setResponseBody(TEST_RESPONSE_BODY.getBytes(UTF_8)) + .setContentType(CONTENT_TYPE_STRING_JSON) + .build(); + HttpRequest request = + HttpRequest.builder().setUrl(TEST_URL).setRequestMethod(RequestMethod.GET).build(); + Map<String, FakeResponse> response = ImmutableMap.of(TEST_URL, responseContent); + fakeURLStreamHandler.stubResponse(response); + + assertThrows(ServiceEntitlementException.class, () -> httpClient.request(request)); + } +}
\ No newline at end of file |